一種可以避免數據遷移的分庫分表scale-out擴容模式


轉自:

http://jm.taobao.org/

 

一種可以避免數據遷移的分庫分表scale-out擴容方式

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

  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的指數級倍分庫分表,這樣方便以后切割。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM