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

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

目前絕大多數應用採起的兩種分庫分表規則算法

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

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

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

 

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

 

須要將原先以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表明百萬數據)spa

 

或者以時間字段爲例,好比一個字段表示記錄的建立時間,以此字段的時間段分庫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、一、二、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_一、t1_1,原數據不變,分別搬遷到DB1,和DB2
20M以上的以mod3平均分配到3個DB庫的t_0、t_二、t_3表中
這樣DB1包含最老的兩個表,和最新的1/3數據。DB1和DB2都分表包含次新的兩個舊錶t0_一、t1_1和最新的1/3數據。
新舊數據讀寫均可達到均勻分佈。

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

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

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

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

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