天天看點

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

原文位址:http://jm-blog.aliapp.com/?p=590

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

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

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

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

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

那麼随着資料量的增大,每個表或庫的資料量都是各自增長。當一個表或庫的資料量增長到了一個極限,要加庫或加表的時候,

介于這種分庫分表算法的離散性,必需要做資料遷移才能完成。例如從3個擴充到5個的時候:

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

需要将原先以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代表百萬資料)

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

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

range

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

這樣的方式下,在資料量再增加達到前幾個庫/表的上限時,則繼續水準增加庫表,原先的資料就不需要遷移了

但是這樣的方式會帶來一個熱點問題:目前的資料量達到某個庫表的範圍時,所有的插入操作,都集中在這個庫/表了。

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

1. 資料遷移

2. 熱點

如何既能避免資料遷移又能避免插入更新的熱點問題呢?

結合離散分庫/分表和連續分庫/分表的優點,如果一定要寫熱點和新資料均勻配置設定在每個庫,同時又保證易于水準擴充,可以考慮這樣的模式:

【水準擴充scale-out方案模式一】

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

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

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

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

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

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

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

dbRule: “DB” + (id % 4)

tbRule: “t”  + (id % 4)

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

這樣3個階段的擴充方案中,每次次擴容隻需要做一次停機釋出,不需要做資料遷移。停機釋出中隻需要做整表搬遷。

這個相對于每個表中的資料重新配置設定來說,不管是開發做,還是DBA做都會簡單很多。

如果更進一步資料庫的設計和部署上能做到每個表一個硬碟,那麼擴容的過程隻要把原有機器的某一塊硬碟拔下來,

插入到新的機器上,就完成整表搬遷了!可以大大縮短停機時間。

具體在mysql上可以以庫為表。開始一個實體機上啟動4個資料庫執行個體,每次倍增機器,直接将庫搬遷到新的機器上。

這樣從始至終規則都不需要變化,一直都是:

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

上述方案有一個缺點,就是在從一個庫到4個庫的過程中,單表的資料量一直在增長。當單表的資料量超過一定範圍時,可能會帶來性能問題。比如索引的問題,曆史資料清理的問題。

另外當開始預留的表個數用盡,到了4實體庫每庫1個表的階段,再進行擴容的話,不可避免的要從表上下手。那麼我們來考慮表内資料上限不增長的方案:

【水準擴充scale-out方案模式二】

階段一:一個資料庫,兩個表,rule0 = id % 2

分庫規則dbRule: “DB0″

分表規則tbRule: “t” + (id %

2)

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

階段二:當單庫的資料量接近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);

    }

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

這樣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個庫中:

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

新的分庫分表規則如下:

  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);

  }

  if(id < 2千萬){       

//2千萬之前的資料,表規則和原先完全一樣,參見階段二

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

          return "t"+ (id % 2)

+"_1"; //1千萬之後的資料,仍然放在t0_1和t1_1表

      return "t"+ (id %

4)+"_2";      //超過2千萬的資料分為4個表t0_2,t1_2,t2_2,t3_2

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

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

【水準擴充scale-out方案模式三】

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

遷移前:

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

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

t0_1整表遷移到DB1

t1_1整表遷移到DB2

遷移後:

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

這時DB0退化為舊資料的讀庫和更新庫。新增資料的熱點均勻分布在DB1和DB2

4無法整除3,是以如果從4表2庫擴充到3個庫,不做行級别的遷移而又保證熱點均勻分布看似無法完成。

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

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

小于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的指數級倍分庫分表,這樣友善以後切割。