分庫分表的原則
- 當(dāng)數(shù)據(jù)量較大時(shí)芭商,需要分庫分表。通常為保證某個(gè)用戶的數(shù)據(jù)在同一個(gè)庫同一個(gè)表中,都是按照customerId進(jìn)行分庫分表的再愈。
- 有時(shí)每天產(chǎn)生大量數(shù)據(jù),這些數(shù)據(jù)只是為了對(duì)賬护戳,也可以按月分庫翎冲,按天分表。比如分為12個(gè)庫媳荒,366張表(考慮閏年)抗悍,但數(shù)據(jù)每天用完后,可以把這些數(shù)據(jù)再遷移到歷史表中钳枕,或者類似hdfs這樣的表中缴渊。
- 分庫分表后考慮數(shù)據(jù)庫的擴(kuò)容問題,同時(shí)還要保證在業(yè)務(wù)系統(tǒng)不停機(jī)的時(shí)候繼續(xù)正常的讀寫數(shù)據(jù)鱼炒,需要保證
3.1 如何保證業(yè)務(wù)系統(tǒng)不停機(jī)衔沼?
3.2 如何保證擴(kuò)容時(shí)候盡量少的遷移數(shù)據(jù)?
分庫分表的流水號(hào)
一般情況下昔瞧,我們使用customerId作為分庫分表鍵指蚁,但有時(shí)我們也會(huì)產(chǎn)生一個(gè)業(yè)務(wù)ID,并把該業(yè)務(wù)ID暴露出去自晰,供其他業(yè)務(wù)系統(tǒng)來查詢凝化。這就要求暴露出去的業(yè)務(wù)ID也是一個(gè)分庫分表鍵,這里只需要將分庫分表的信息記錄在該ID中即可酬荞。
比如ID為:01110001002817070500000001036815
這個(gè)ID一共32位缘圈,由以下幾個(gè)含義組成
01:Version
11:區(qū)域信息
0001:代表這是某個(gè)類型的數(shù)據(jù)
0028:代表數(shù)據(jù)位于第28張表,
170705:數(shù)據(jù)產(chǎn)生與17年7月5號(hào)
00000001036815:Sequence睦尽,全局唯一的流水號(hào)
我們可以看到酒贬,0028 這個(gè)字段就含有分庫分表信息八毯。通過算法可以推導(dǎo)出位于第0000庫的第0028表乓梨。
一致性hash
概述
假如我們用sharding-jdbc分了15張表,之后業(yè)務(wù)需要擴(kuò)展到20張表遣疯,那問題就來了雄可,之前根據(jù)order_id取模15后的數(shù)據(jù)分散在了各個(gè)表中,現(xiàn)在需要重新對(duì)所有數(shù)據(jù)重新取模20來分配數(shù)據(jù)缠犀,工作量太大数苫,有沒有更好的方法呢?答案是有的:一致性hash辨液。
先了解下一致性hash, 例子中四個(gè)節(jié)點(diǎn)一般都是用節(jié)點(diǎn)前綴 +(ip+端口).hahcode%n作為名稱虐急。
一致性hash 是個(gè)可以理解為圓形,這個(gè)圓形稱為hash環(huán)滔迈,環(huán)上可以最多建立2的32次方減1個(gè)節(jié)點(diǎn)止吁。
存數(shù)據(jù): 一般根據(jù)key.hashcode%n=k,如果k 的范圍 1<k<2100,按照順時(shí)針方向燎悍,把數(shù)據(jù)放在node_2100這個(gè)節(jié)點(diǎn)上敬惦。
查找數(shù)據(jù):用同樣方式取模key.hashcode%n得到的值,然后順時(shí)針查找谈山,剛好卡在1到2100之間俄删,那就去獲node_2100上取數(shù)據(jù),如果沒找到奏路,就去第一個(gè)節(jié)點(diǎn)上找畴椰。這里的key就是表里的某個(gè)屬性。例如user表用戶id作為分表策略就是1001.hashcode%n鸽粉。
userId | userName | Sex |
---|---|---|
1001 | 張三 | 0 |
1002 | 李四 | 1 |
假如查找數(shù)據(jù)的時(shí)候根據(jù)key.hashcode%n=1000,那順時(shí)針找1<1000<2100,數(shù)據(jù)就落在了node_2100這個(gè)節(jié)點(diǎn)上斜脂,存取數(shù)據(jù)都是這個(gè)規(guī)則。
擴(kuò)容節(jié)點(diǎn)
假如我們現(xiàn)在需要增加第五個(gè)節(jié)點(diǎn)潜叛,這時(shí)候只需要把緊鄰第五個(gè)節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)重新做一次hashcode%5取模就行,不用改動(dòng)其它節(jié)點(diǎn)數(shù)據(jù)壶硅。
原來應(yīng)該落在node_7100和node_1之間的數(shù)據(jù)威兜,現(xiàn)在中間多了一個(gè)node_7200,數(shù)據(jù)就有可能被分配到node_7200上庐椒,然后我們?nèi)?shù)據(jù)會(huì)發(fā)現(xiàn)有時(shí)候7200>key.hashcode%5>7100的數(shù)據(jù)取不到椒舵,因?yàn)槔蠑?shù)據(jù)在node_1節(jié)點(diǎn)上,那怎么辦呢约谈?很簡單只需要對(duì)該節(jié)點(diǎn)重新key.hashcode%5取模分配就好笔宿,其它節(jié)點(diǎn)數(shù)據(jù)不用動(dòng)犁钟。
一致性hash的優(yōu)勢(shì)
分庫分表的算法,通過一致性hash泼橘,擴(kuò)容數(shù)據(jù)只需要遷移一半的數(shù)據(jù)
比如我們有32個(gè)庫涝动,每庫32張表,則
0庫:存在下標(biāo)為0,1,2......31的表
1庫:存在下標(biāo)為32,33,34......63的表
.......
31庫:存在下標(biāo)為992,993,994,1023的表
對(duì)于這樣的分庫分表炬灭,假定按照customerId來分庫分表醋粟,請(qǐng)看分庫分表的算法
//取得customerId的hashCode
int customerIdHashCode = Math.abs(customerId.hashCode());
//使用一致性Hash算法取得customerId應(yīng)該位于的數(shù)據(jù)庫
int dbIndex = (customerIdHashCode % 4096) / (4096 / 32);
//假定每庫為32張表
int tableCountPerDb = 32;
//取得customerId位于的數(shù)據(jù)庫的啟始表索引
int tableStartIndex = dbIndex * tableCountPerDb;
//用customerId % 每庫表的數(shù)量,得到customerId的步長
int tableStep = customerIdHashCode % tableCountPerDb;
//表的起始索引+步長就是customerId應(yīng)該位于的數(shù)據(jù)表
int tableIndex = tableStartIndex + tableStep;
return tableIndex
弊端:數(shù)據(jù)傾斜容易造成雪崩
節(jié)點(diǎn)不是均勻分配的重归,這樣我們會(huì)造成數(shù)據(jù)分配不均(傾斜)米愿,比如上圖中node_7100和node_4100之間的距離比較大,那落在這一區(qū)間的概率也比較大鼻吮,造成node_7100的數(shù)據(jù)量過大育苟,請(qǐng)求量過大導(dǎo)致雪崩。
解決辦法就是建立虛擬節(jié)點(diǎn)
虛擬節(jié)點(diǎn)并非是一個(gè)真實(shí)的節(jié)點(diǎn)椎木,而是和真實(shí)節(jié)點(diǎn)之間的一個(gè)映射违柏,落在虛擬節(jié)點(diǎn)上的數(shù)據(jù)最終會(huì)被轉(zhuǎn)移到真實(shí)節(jié)點(diǎn)上。
虛擬節(jié)點(diǎn)的重建
- node_6100和node_8100就是虛擬節(jié)點(diǎn)拓哺。這兩個(gè)節(jié)點(diǎn)并非真實(shí)節(jié)點(diǎn)勇垛,而是我們自己添加的節(jié)點(diǎn)。
- 當(dāng)有數(shù)據(jù)落在node_6100和node_4100之間時(shí)士鸥,數(shù)據(jù)會(huì)落在node_6100上闲孤,這個(gè)時(shí)候我們做判斷發(fā)現(xiàn)node_6100是虛擬節(jié)點(diǎn)并且映射到了node_4100上,然后我們把數(shù)據(jù)直接放在node_4100上就可以了烤礁。
- node_8100也是虛擬節(jié)點(diǎn)操作和node_6100一樣被映射到了node_2100上讼积。
那虛擬節(jié)點(diǎn)什么時(shí)候加呢?
肯定不是在后期加的脚仔,盡量在真實(shí)節(jié)點(diǎn)確認(rèn)后就設(shè)定虛擬節(jié)點(diǎn)勤众,虛擬節(jié)點(diǎn)沒有數(shù)量限制,原則上設(shè)定的越多分配就越越均勻鲤脏。不過太多也不便于管理们颜。
這個(gè)一致性分表也一樣,如果我們key.hashcode%3 我們會(huì)創(chuàng)建table_1,table_2,table_3三張表猎醇,三張表是后綴是連續(xù)的窥突,所以我們盡量不要這么做,而是在2^32-1的范圍內(nèi)自己計(jì)算好硫嘶,均勻的起名均勻的分布阻问。
文章轉(zhuǎn)載自:
一致性hash與分庫分表
高可用性實(shí)現(xiàn)方式:一致性hash
一致性哈希算法的應(yīng)用及其優(yōu)化