原文地址:http://jm-blog.aliapp.com/?p=590mysql
目前絕大多數應用採起的兩種分庫分表規則算法
這兩種方式有個本質的特色,就是離散性加週期性。sql
例如以一個表的主鍵對3取餘數的方式分庫或分表:數據庫
那麼隨着數據量的增大,每一個表或庫的數據量都是各自增加。當一個表或庫的數據量增加到了一個極限,要加庫或加表的時候,
介於這種分庫分表算法的離散性,必須要作數據遷移才能完成。例如從3個擴展到5個的時候:app
須要將原先以mod3分類的數據,從新以mod5分類,不可避免的帶來數據遷移。每一個表的數據都要被從新分配到多個新的表
類似的例子好比從dayofweek分的7個庫/表,要擴張爲以dayofmonth分的31張庫/表,一樣須要進行數據遷移。函數
數據遷移帶來的問題是工具
如何在數據量擴張到現有庫表極限,加庫加表時避免數據遷移呢?
一般的數據增加每每是隨着時間的推移增加的。隨着業務的開展,時間的推移,數據量不斷增長。(不隨着時間增加的狀況,
例如某天忽然須要從另外一個系統導入大量數據,這種狀況徹底能夠由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數據。
新舊數據讀寫均可達到均勻分佈。
總而言之:
兩種規則映射(函數):
離散映射和連續映射這兩種相輔相成的映射規則,正好解決熱點和遷移這一對相互矛盾的問題。
咱們以前只運用了離散映射,引入連續映射規則後,二者結合,精心設計,
應該能夠設計出知足避免熱點和減小遷移之間任意權衡取捨的規則。
基於以上考量,分庫分表規則的設計和配置,長遠說來必須知足如下要求