1. 程式人生 > >數據庫Sharding的基本思想和切分策略

數據庫Sharding的基本思想和切分策略

生成策略 UNC 第一個 這一 分配 方案 減少 能夠 開發

目前絕大多數應用采取的兩種分庫分表規則

  1. mod方式
  2. dayofweek系列日期方式(所有星期1的數據在一個庫/表,或所有?月份的數據在一個庫表)

這兩種方式有個本質的特點,就是離散性加周期性。

例如以一個表的主鍵對3取余數的方式分庫或分表:

技術分享圖片

那麽隨著數據量的增大,每個表或庫的數據量都是各自增長。當一個表或庫的數據量增長到了一個極限,要加庫或加表的時候,
介於這種分庫分表算法的離散性,必需要做數據遷移才能完成。例如從3個擴展到5個的時候:

技術分享圖片

需要將原先以mod3分類的數據,重新以mod5分類,不可避免的帶來數據遷移。每個表的數據都要被重新分配到多個新的表
相似的例子比如從dayofweek分的7個庫/表,要擴張為以dayofmonth分的31張庫/表,同樣需要進行數據遷移。

數據遷移帶來的問題是

  1. 業務至少要兩次發布
  2. 要專門寫工具來導數據。由於各業務之間的差別,很難做出統一的工具。目前幾乎都是每個業務寫一套
  3. 要解決增量、全量、時間點,數據不一致等問題

如何在數據量擴張到現有庫表極限,加庫加表時避免數據遷移呢?
通常的數據增長往往是隨著時間的推移增長的。隨著業務的開展,時間的推移,數據量不斷增加。(不隨著時間增長的情況,
例如某天突然需要從另一個系統導入大量數據,這種情況完全可以由dba依據現有的分庫分表規則來導入,因此不考慮這種問題。)

考慮到數據增長的特點,如果我們以代表時間增長的字段,按遞增的範圍分庫,則可以避免數據遷移
例如,如果id是隨著時間推移而增長的全局sequence,則可以以id的範圍來分庫:(全局sequence可以用tddl現在的方式也可以用ZooKeeper實現)
id在 0–100萬在第一個庫中,100-200萬在第二個中,200-300萬在第3個中 (用M代表百萬數據)

技術分享圖片

或者以時間字段為例,比如一個字段表示記錄的創建時間,以此字段的時間段分庫gmt_create_time in range

技術分享圖片

這樣的方式下,在數據量再增加達到前幾個庫/表的上限時,則繼續水平增加庫表,原先的數據就不需要遷移了
但是這樣的方式會帶來一個熱點問題:當前的數據量達到某個庫表的範圍時,所有的插入操作,都集中在這個庫/表了。

所以在滿足基本業務功能的前提下,分庫分表方案應該盡量避免的兩個問題:

1. 數據遷移
2. 熱點

如何既能避免數據遷移又能避免插入更新的熱點問題呢?
結合離散分庫/分表和連續分庫/分表的優點,如果一定要寫熱點和新數據均勻分配在每個庫,同時又保證易於水平擴展,可以考慮這樣的模式:

【水平擴展scale-out方案模式一】

階段一:一個庫DB0之內分4個表,id%4 :

技術分享圖片

階段二:增加db1庫,t2和t3整表搬遷到db1

技術分享圖片

階段三:增加DB2和DB3庫,t1整表搬遷到DB2,t3整表搬遷的DB3:

技術分享圖片

為了規則表達,通過內部名稱映射或其他方式,我們將DB1和DB2的名稱和位置互換得到下圖:

dbRule: “DB” + (id % 4)
tbRule: “t” + (id % 4)

技術分享圖片

這樣3個階段的擴展方案中,每次次擴容只需要做一次停機發布,不需要做數據遷移。停機發布中只需要做整表搬遷。
這個相對於每個表中的數據重新分配來說,不管是開發做,還是DBA做都會簡單很多。

如果更進一步數據庫的設計和部署上能做到每個表一個硬盤,那麽擴容的過程只要把原有機器的某一塊硬盤拔下來,
插入到新的機器上,就完成整表搬遷了!可以大大縮短停機時間。

具體在mysql上可以以庫為表。開始一個物理機上啟動4個數據庫實例,每次倍增機器,直接將庫搬遷到新的機器上。
這樣從始至終規則都不需要變化,一直都是:

dbRule: “DB” + (id % 4)
tbRule: “t” + (id % 4)

即邏輯上始終保持4庫4表,每個表一個庫。這種做法也是目前店鋪線圖片空間采用的做法。

上述方案有一個缺點,就是在從一個庫到4個庫的過程中,單表的數據量一直在增長。當單表的數據量超過一定範圍時,可能會帶來性能問題。比如索引的問題,歷史數據清理的問題。
另外當開始預留的表個數用盡,到了4物理庫每庫1個表的階段,再進行擴容的話,不可避免的要從表上下手。那麽我們來考慮表內數據上限不增長的方案:

【水平擴展scale-out方案模式二】

階段一:一個數據庫,兩個表,rule0 = id % 2

分庫規則dbRule: “DB0″
分表規則tbRule: “t” + (id % 2)

技術分享圖片

階段二:當單庫的數據量接近1千萬,單表的數據量接近500萬時,進行擴容(數據量只是舉例,具體擴容量要根據數據庫和實際壓力狀況決定):
增加一個數據庫DB1,將DB0.t1整表遷移到新庫DB1。
每個庫各增加1個表,未來10M-20M的數據mod2分別寫入這2個表:t0_1,t1_1:

分庫規則dbRule:

“DB” + (id % 2)

分表規則tbRule:

if(id < 1千萬){

return "t"+ (id % 2); //1千萬之前的數據,仍然放在t0和t1表。t1表從DB0搬遷到DB1庫

}else if(id < 2千萬){

return "t"+ (id % 2) +"_1"; //1千萬之後的數據,各放到兩個庫的兩個表中: t0_1,t1_1

}else{

throw new IllegalArgumentException("id outof range[20000000]:" + id);

}

技術分享圖片

這樣10M以後的新生數據會均勻分布在DB0和DB1; 插入更新和查詢熱點仍然能夠在每個庫中均勻分布。
每個庫中同時有老數據和不斷增長的新數據。每表的數據仍然控制在500萬以下。

階段三:當兩個庫的容量接近上限繼續水平擴展時,進行如下操作:
新增加兩個庫:DB2和DB3. 以id % 4分庫。余數0、1、2、3分別對應DB的下標. t0和t1不變,
將DB0.t0_1整表遷移到DB2; 將DB1.t1_1整表遷移到DB3
20M-40M的數據mod4分為4個表:t0_2,t1_2,t2_2,t3_2,分別放到4個庫中:

技術分享圖片

新的分庫分表規則如下:

分庫規則dbRule:

if(id < 2千萬){

//2千萬之前的數據,4個表分別放到4個庫

if(id < 1千萬){

return "db"+ (id % 2); //原t0表仍在db0, t1表仍在db1

}else{

return "db"+ ((id % 2) +2); //原t0_1表從db0搬遷到db2; t1_1表從db1搬遷到db3

}

}else if(id < 4千萬){

return "db"+ (id % 4); //超過2千萬的數據,平均分到4個庫

}else{

throw new IllegalArgumentException("id out of range. id:"+id);

}

分表規則tbRule:

if(id < 2千萬){ //2千萬之前的數據,表規則和原先完全一樣,參見階段二

if(id < 1千萬){

return "t"+ (id % 2); //1千萬之前的數據,仍然放在t0和t1表

}else{

return "t"+ (id % 2) +"_1"; //1千萬之後的數據,仍然放在t0_1和t1_1表

}

}else if(id < 4千萬){

return "t"+ (id % 4)+"_2"; //超過2千萬的數據分為4個表t0_2,t1_2,t2_2,t3_2

}else{

throw new IllegalArgumentException("id out of range. id:"+id);

}

隨著時間的推移,當第一階段的t0/t1,第二階段的t0_1/t1_1逐漸成為歷史數據,不再使用時,可以直接truncate掉整個表。省去了歷史數據遷移的麻煩。

上述3個階段的分庫分表規則在TDDL2.x中已經全部支持,具體請咨詢TDDL團隊。

【水平擴展scale-out方案模式三】

非倍數擴展:如果從上文的階段二到階段三不希望一下增加兩個庫呢?嘗試如下方案:

遷移前:

技術分享圖片

新增庫為DB2,t0、t1都放在DB0,

t0_1整表遷移到DB1
t1_1整表遷移到DB2

遷移後:

技術分享圖片

這時DB0退化為舊數據的讀庫和更新庫。新增數據的熱點均勻分布在DB1和DB2
4無法整除3,因此如果從4表2庫擴展到3個庫,不做行級別的遷移而又保證熱點均勻分布看似無法完成。

當然如果不限制每庫只有兩個表,也可以如下實現:

技術分享圖片

小於10M的t0和t1都放到DB0,以mod2分為兩個表,原數據不變
10M-20M的,以mod2分為兩個表t0_1、t1_1,原數據不變,分別搬遷到DB1,和DB2
20M以上的以mod3平均分配到3個DB庫的t_0、t_2、t_3表中
這樣DB1包含最老的兩個表,和最新的1/3數據。DB1和DB2都分表包含次新的兩個舊表t0_1、t1_1和最新的1/3數據。
新舊數據讀寫都可達到均勻分布。

總而言之:
兩種規則映射(函數):

  1. 離散映射:如mod或dayofweek, 這種類型的映射能夠很好的解決熱點問題,但帶來了數據遷移和歷史數據問題。
  2. 連續映射;如按id或gmt_create_time的連續範圍做映射。這種類型的映射可以避免數據遷移,但又帶來熱點問題。

離散映射和連續映射這兩種相輔相成的映射規則,正好解決熱點和遷移這一對相互矛盾的問題。
我們之前只運用了離散映射,引入連續映射規則後,兩者結合,精心設計,
應該可以設計出滿足避免熱點和減少遷移之間任意權衡取舍的規則。

基於以上考量,分庫分表規則的設計和配置,長遠說來必須滿足以下要求

    1. 可以動態推送修改
    2. 規則可以分層級疊加,舊規則可以在新規則下繼續使用,新規則是舊規則在更寬尺度上的拓展,以此支持新舊規則的兼容,避免數據遷移
    3. 用mod方式時,最好選2的指數級倍分庫分表,這樣方便以後切割。

數據庫分庫分表(sharding)系列(五) 一種支持自由規劃無須數據遷移和修改路由代碼的Sharding擴容方案

數據庫分庫分表(sharding)系列(四) 多數據源的事務處理

數據庫分庫分表(sharding)系列(三) 關於使用框架還是自主開發以及sharding實現層面的考量

數據庫分庫分表(sharding)系列(二) 全局主鍵生成策略

數據庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示

關於垂直切分Vertical Sharding的粒度

數據庫Sharding的基本思想和切分策略

數據庫Sharding的基本思想和切分策略