本文主要描述分庫分表的算法方案、按什麼規則劃分。按部就班比較目前出現的幾種規則方式,最後第五種增量遷移方案是我設想和推薦的方式。後續章再講述技術選型和分庫分表後帶來的問題。java
隨着業務量遞增,數據量遞增,一個表將會存下大量數據,在一個表有一千萬行數據時,經過sql優化、提高機器性能還能承受。爲了將來長遠角度應在必定程度時進行分庫分表,如出現數據庫性能瓶頸、增長字段時須要耗時比較長的時間的狀況下。解決獨立節點承受全部數據的壓力,分佈多個節點,提供容錯性,沒必要一個掛整個系統不能訪問。node
本文講述的分庫分表的方案,是基於水平分割的狀況下,選擇不一樣的規則,比較規則的優缺點。 通常網上就前三種,正常一點的會說第四種,但不是很完美,前面幾種遷移數據都會很大影響,推薦我認爲比較好的方案五。git
公式:key mod x (x爲天然數)算法
Key能夠爲主鍵,也能夠爲訂單號,也能夠爲用戶id,這個須要根據場景決定,哪一個做爲查詢條件機率多用哪一個。sql
優勢:數據庫
缺點:bash
能夠按日、按月、按季度。服務器
tb_20190101
tb_20190102
tb_20190103
……
複製代碼
這個算法要求在訂單號、userId上添加年月日或者時間戳,或者查詢接口帶上年月日,才能定位在哪一個分片。分佈式
優勢:函數
缺點:
推薦使用場景:日誌記錄
表0 [0,10000000)
表1 [10000000,20000000)
表2 [20000000,30000000)
表3 [30000000,40000000)
……
複製代碼
優勢:
缺點:
先說一下一致性hash,有些文章說一致性Hash是一種算法,我認爲它並非具體的計算公式,而是一個設定的思路。
1.先假定一個環形Hash空間,環上有固定最大值和最小值,頭尾相連,造成一個閉環,如int,long的最大值和最小值。 不少文章會假定2^32個位置,最大值爲2^32-1最小值爲0,即0~(2^32)-1的數字空間,他們只是按照經常使用的hash算法舉例,真實分庫分表的狀況下不是用這個數字,因此我纔會認爲一致性hash算法實際上是一個理念,並非真正的計算公式。 以下圖
這裏不詳細說明這個理論,它主要表達的意思是固定好最大值,就再也不修改最大值到最小值範圍,後續只修改節點node的位置和增長node來達到減小每一個node要管的數據,以達到減小壓力。
備註:
* 不推薦對ip進行hash,由於可能會致使hash(ip)得出的結果很大,例如得出60,若這個節點的前面沒有節點,則60號位置的這個節點須要管大部分的數據了。
* 最好生成key的方式用雪花算法snowFlake來作,至少要是不重複的數字,也不要用自增的形式。
* 推薦閱讀銅板街的方案 訂單號末尾添加user%64
複製代碼
利用一致性hash理論,分庫選擇hash(key)的公式基準爲 value= key mod 64,分表公式value= key / 64 mod 64,key爲訂單號或者userId等這類常常查詢的主要字段。(後續會對這個公式有變化) 咱們假定上述公式,則能夠分64個庫,每一個庫64個表,假設一個表1千萬行記錄。則最大64 * 64 * 1000萬數據,我相信不會有這一天的到來,因此咱們以這個做爲最大值比較合理,甚至選擇32 * 32均可以。
由於前期用不上這麼多個表,一開始創建這麼多表每一個表都insert數據,會形成浪費機器,因此在咱們已知最大值的狀況下,咱們從小的數字開始使用,因此咱們將對上述計算得出的value進行分組。
分組公式:64 = 每組多少個count * group須要分組的個數
數據所在環的位置(也就是在哪一個庫中):value = key mode 64 / count * count
複製代碼
如下舉例爲16組,也就是16個庫,group=16 這個時候庫的公式爲value = key mode 64 / 4 * 4,除以4後,會截取小數位得出一個整數,而後 * 4倍,就是數據所在位置。
// 按4個爲一組,分兩個表
count = 4:
Integer dbValue = userId % 64 / count * count ;
複製代碼
hash(key)在0~3之間在第0號庫
hash(key)在4~7之間在第4號庫
hash(key)在8~11之間在第8號庫
……
複製代碼
備註:其實一開始能夠64個爲一組就是一個庫,後續變化32個爲一組就是兩個庫,從一個庫到兩個庫,再到4個庫,逐步遞進。
下圖中舉例分16組後,變爲分到32組,須要每一個庫都拿出一半的數據遷移到新數據,擴容直到分64個組。
能夠看到當須要進行擴容一倍時須要遷移一半的數據量,以2^n遞增,因此進行影響範圍會比較大。
優勢:
缺點:
(我認爲比較好的方案)
一致性hash方案結合比較範圍方案,也就是方案三和方案四的結合。
方案四是設定最大範圍64,按2^n指數形式從1增長庫或者表數量,這樣帶來的是每次拆分進行遷移時會影響當整體數據量的1/2的數據,影響範圍比較大,因此要麼就直接拆分到32組、64組一勞永逸,要麼每次1/2遷移。
方案四對應遷移方案:
如今我想方法時,保持一致性hash理念,1個1個節點來增長,而不是方案四的每次增長2^n-n個節點。可是代碼上就須要進行對新節點內的數據hash值判斷。
咱們基於已經發生過1次迭代分了兩個庫的狀況來作後續迭代演示,首先看看已經拆分兩個庫的狀況:
數據落在第64號庫名爲db64和第32號庫名爲db32
迭代二: 區別與方案四直接增長兩個節點,咱們只增長一個節點,這樣遷移數據時由本來影響1/2的用戶,將會隻影響1/4的用戶。
// 按32改成16個爲一組,分兩變爲4個庫
count = 16;
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
// 上一個迭代這些數據落在db32中,如今走新增節點名爲db16號的那個庫
dbValue = 16;
return dbValue;
} else {
// 按原來規則走
return dbValue;
}
複製代碼
迭代三:
遷移前能夠先上線,增長一段開關代碼,請求接口特殊處理hash值小於16的訂單號或者用戶號,這樣就只會影響1/4的人
// 在請求接口中增長邏輯
public void doSomeService(Integer userId){
if(遷移是否完成的開關){
// 若是未完成
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
//這部分用戶暫時不能走下面的邏輯
return ;
}
}
return dbValue;
}
}
複製代碼
// 在分片時按32個爲一組,分兩個庫
count = 16;
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
// 上一個迭代這些數據落在db32中,有一半須要走新增節點名爲db16號的那個庫
if(遷移是否完成的開關){
// 若是已經完成,就去db16的庫
dbValue = 16;
}
return dbValue;
} else {
// 按原來規則走
return dbValue;
}
複製代碼
如此類推,下一輪總共8個節點時,每次遷移只須要遷移1/8。
其實也能夠在第一個迭代時,不選擇dbValue小於16號的來作。直接8個分一組,只選擇dbValue<8的來作,這樣第一個迭代的影響範圍也會比較案例中小。上述案例用16只是比較好演示
優勢:
缺點:
如同上述方案五是方案四+方案一,能夠達到逐步遷移數據,還有一種方案。就是方案四+方案三,只是不用取模後分組。
userId % 64 / count * count
由於上述公式,得出結果中,不必定每一片數據都是平均分佈的。其實咱們能夠取模後,按範圍劃分分片,以下公式。
第一片 0<userId % 64<15
第二片 16<userId % 64<31
第三片 32<userId % 64<47
第四片 48<userId % 64<63
複製代碼
固然範圍能夠自定義,看取模後落入哪一個值的數量比較多,就切某一片數據就行了,具體就不畫圖了,跟方案四相似。
由於遷移數據的緣由,方案四中,若是數據量大,達到1000萬行記錄,每次遷移都須要遷移不少的數據,因此不少公司會盡早分庫分表。
可是在業務優先狀況下,一直迭代業務,數據一進達到不少的狀況下16分支一也是不少的數據時,咱們就能夠用一致性Hash理念--按範圍分庫
個人公衆號 :地藏思惟
掘金:地藏Kelvin
簡書:地藏Kelvin
CSDN:地藏Kelvin
個人Gitee: 地藏Kelvin gitee.com/dizang-kelv…