long 雪花算法_海量数据分库分表方案(一)算法方案
背景
隨著業(yè)務(wù)量遞增,數(shù)據(jù)量遞增,一個表將會存下大量數(shù)據(jù),在一個表有一千萬行數(shù)據(jù)時,通過sql優(yōu)化、提升機(jī)器性能還能承受。為了未來長遠(yuǎn)角度應(yīng)在一定程度時進(jìn)行分庫分表,如出現(xiàn)數(shù)據(jù)庫性能瓶頸、增加字段時需要耗時比較長的時間的情況下。解決獨(dú)立節(jié)點(diǎn)承受所有數(shù)據(jù)的壓力,分布多個節(jié)點(diǎn),提供容錯性,不必一個掛整個系統(tǒng)不能訪問。
目的
本文講述的分庫分表的方案,是基于水平分割的情況下,選擇不同的規(guī)則,比較規(guī)則的優(yōu)缺點(diǎn)。 一般網(wǎng)上就前三種,正常一點(diǎn)的會說第四種,但不是很完美,前面幾種遷移數(shù)據(jù)都會很大影響,推薦我認(rèn)為比較好的方案五。
- 方案一:對Key取模,除數(shù)逐步遞增
- 方案二:按時間劃分
- 方案三:按數(shù)值范圍
- 方案四:一致性Hash理念——平均分布方案(大眾點(diǎn)評用這種,200G并且一步到位)
- 方案五:一致性Hash理念——按迭代增加節(jié)點(diǎn)(為了方便增量遷移)
- 方案六:一致性Hash理念——按范圍分庫(迭代遷移)
Version:1.0 StartHTML:000000208 EndHTML:000021485 StartFragment:000004002 EndFragment:000021399 StartSelection:000004002 EndSelection:000021395 SourceURL:https://my.oschina.net/u/4200053/blog/4255903 window.__STATIC_DOMAIN = "https://static.oschina.net";
方案選擇
方案一:對Key取模,除數(shù)逐步遞增
公式:key mod x (x為自然數(shù))
Key可以為主鍵,也可以為訂單號,也可以為用戶id,這個需要根據(jù)場景決定,哪個作為查詢條件概率多用哪個。優(yōu)點(diǎn):
- 按需增加庫、表,逐步增加
- 分布均勻,每一片差異不多
缺點(diǎn):
- 很多時候會先從2開始分兩個庫逐級遞增,然后分3個、4個、5個。如在mod 3 變 mod 5的情況下,取模后的大部分的數(shù)據(jù)的取模結(jié)果會變化,如key=3時,mod 3=0,突然改變?yōu)閙od 5=3,則將會從第0表遷移到第3表,將會造成很多數(shù)據(jù)多重復(fù)移動位置。
- 會重復(fù)遷移數(shù)據(jù),當(dāng)分2個時,有數(shù)據(jù)A在第0號表,分3個時數(shù)據(jù)A去了第1號表、到分4個時數(shù)據(jù)A會回到第0號表
方案二:按時間劃分
可以按日、按月、按季度。
tb_20190101 tb_20190102 tb_20190103 ……
這個算法要求在訂單號、userId上添加年月日或者時間戳,或者查詢接口帶上年月日,才能定位在哪個分片。優(yōu)點(diǎn):
- 數(shù)據(jù)按時間連續(xù)
- 看數(shù)據(jù)增長比較直觀
缺點(diǎn):
- 因?yàn)榭紤]到歷史數(shù)據(jù)一開始沒分庫分表后續(xù)進(jìn)行分庫分表時,歷史數(shù)據(jù)的訂單號不一定有時間戳,歷史數(shù)據(jù)可能為自增或者自定義算法得出的分布式主鍵,導(dǎo)致查詢時必須要上游系統(tǒng)傳訂單號、創(chuàng)建時間兩個字段。
- 若上游系統(tǒng)沒有傳時間,或者上游系統(tǒng)的創(chuàng)建時間與當(dāng)前系統(tǒng)對應(yīng)訂單的創(chuàng)建時間不在同一天的情況下,則當(dāng)前數(shù)據(jù)庫表的數(shù)據(jù)記錄需要有時間字段。因?yàn)樯嫌蜗到y(tǒng)只傳訂單號,這個時候需要獲取創(chuàng)建時間,當(dāng)前系統(tǒng)就必須要有一個主表維護(hù)訂單號和創(chuàng)建時間的關(guān)系,并且每次查詢時都需要先查當(dāng)前系統(tǒng)主表,再查具體表,這樣就會消耗性能。
- 分布不一定均勻:每月增長數(shù)據(jù)不一樣,可能會有些月份多有些月份少
推薦使用場景:日志記錄
方案三:按數(shù)值范圍
表0 [0,10000000) 表1 [10000000,20000000) 表2 [20000000,30000000) 表3 [30000000,40000000) ……優(yōu)點(diǎn):
- 分布均勻
缺點(diǎn):
- 因?yàn)槲粗畲笾?#xff0c;所以無法用時間戳作為key,這個方法不能用表的自增主鍵,因?yàn)槊總€表都自增數(shù)量不是統(tǒng)一維護(hù)。所以需要有一個發(fā)號器或發(fā)號系統(tǒng)做統(tǒng)一維護(hù)key自增的地方。
說后續(xù)推薦的方案中先簡單說說一致性hash
先說一下一致性hash,有些文章說一致性Hash是一種算法,我認(rèn)為它并不是具體的計算公式,而是一個設(shè)定的思路。
1.先假定一個環(huán)形Hash空間,環(huán)上有固定最大值和最小值,頭尾相連,形成一個閉環(huán),如int,long的最大值和最小值。 很多文章會假定2^32個位置,最大值為2^32-1最小值為0,即0~(2^32)-1的數(shù)字空間,他們只是按照常用的hash算法舉例,真實(shí)分庫分表的情況下不是用這個數(shù)字,所以我才會認(rèn)為一致性hash算法其實(shí)是一個理念,并不是真正的計算公式。 如下圖
2. 設(shè)計一個公式函數(shù) value = hash(key),這個公式將會有最大值和最小值,如 key mod 64 = value; 這個公式最大為64,最小為0。然后把數(shù)據(jù)都落在環(huán)上。
3. 設(shè)定節(jié)點(diǎn)node。設(shè)定節(jié)點(diǎn)的方式如對ip進(jìn)行hash,或者自定義固定值(后續(xù)方案是使用固定值)。然后node逆時針走,直到前一個節(jié)點(diǎn)為止,途經(jīng)value=hash(key)的所有數(shù)據(jù)的都?xì)w這個節(jié)點(diǎn)管。 如 hash(node1)=10,則hash(key)=0~10的數(shù)據(jù)都?xì)wnode1管。
概括
這里不詳細(xì)說明這個理論,它主要表達(dá)的意思是固定好最大值,就不再修改最大值到最小值范圍,后續(xù)只修改節(jié)點(diǎn)node的位置和增加node來達(dá)到減少每個node要管的數(shù)據(jù),以達(dá)到減少壓力。
備注: * 不推薦對ip進(jìn)行hash,因?yàn)榭赡軙?dǎo)致hash(ip)得出的結(jié)果很大,例如得出60,若這個節(jié)點(diǎn)的前面沒有節(jié)點(diǎn),則60號位置的這個節(jié)點(diǎn)需要管大部分的數(shù)據(jù)了。 * 最好生成key的方式用雪花算法snowFlake來做,至少要是不重復(fù)的數(shù)字,也不要用自增的形式。 * 推薦閱讀銅板街的方案 訂單號末尾添加user%64
方案四:一致性Hash理念——平均分布方案
利用一致性hash理論,分庫選擇hash(key)的公式基準(zhǔn)為 value= key mod 64,分表公式value= key / 64 mod 64,key為訂單號或者userId等這類經(jīng)常查詢的主要字段。(后續(xù)會對這個公式有變化) 我們假定上述公式,則可以分64個庫,每個庫64個表,假設(shè)一個表1千萬行記錄。則最大64 * 64 * 1000萬數(shù)據(jù),我相信不會有這一天的到來,所以我們以這個作為最大值比較合理,甚至選擇32 * 32都可以。
因?yàn)榍捌谟貌簧线@么多個表,一開始建立這么多表每個表都insert數(shù)據(jù),會造成浪費(fèi)機(jī)器,所以在我們已知最大值的情況下,我們從小的數(shù)字開始使用,所以我們將對上述計算得出的value進(jìn)行分組。
分組公式:64 = 每組多少個count * group需要分組的個數(shù) 數(shù)據(jù)所在環(huán)的位置(也就是在哪個庫中):value = key mode 64 / count * count
以下舉例為16組,也就是16個庫,group=16 這個時候庫的公式為value = key mode 64 / 4 * 4,除以4后,會截取小數(shù)位得出一個整數(shù),然后 * 4倍,就是數(shù)據(jù)所在位置。
// 按4個為一組,分兩個表 count = 4: Integer dbValue = userId % 64 / count * count ;
hash(key)在0~3之間在第0號庫 hash(key)在4~7之間在第4號庫 hash(key)在8~11之間在第8號庫 ……
備注:其實(shí)一開始可以64個為一組就是一個庫,后續(xù)變化32個為一組就是兩個庫,從一個庫到兩個庫,再到4個庫,逐步遞進(jìn)。
從分1庫開始擴(kuò)容的迭代:
下圖中舉例分16組后,變?yōu)榉值?2組,需要每個庫都拿出一半的數(shù)據(jù)遷移到新數(shù)據(jù),擴(kuò)容直到分64個組。
可以看到當(dāng)需要進(jìn)行擴(kuò)容一倍時需要遷移一半的數(shù)據(jù)量,以2^n遞增,所以進(jìn)行影響范圍會比較大。優(yōu)點(diǎn):
- 如果直接拆分32組,那么就比較一勞永逸
- 如果數(shù)據(jù)量比較大,未做過分表可以用一勞永逸方式。
- 分布均勻
- 遷移數(shù)據(jù)時不需要像方案一那樣大部分的數(shù)據(jù)都需要進(jìn)行遷移并有重復(fù)遷移,只需要遷移一半
缺點(diǎn):
- 可以擴(kuò)展,但是影響范圍大。
- 遷移的數(shù)據(jù)量比較大,雖然不像方案一那樣大部分?jǐn)?shù)據(jù)遷移,當(dāng)前方案每個表或庫都需要一半數(shù)據(jù)的遷移。
- 若要一勞永逸,則需要整體停機(jī)來遷移數(shù)據(jù)
方案五:一致性Hash理念——按迭代增加節(jié)點(diǎn)
(我認(rèn)為比較好的方案)
一致性hash方案結(jié)合比較范圍方案,也就是方案三和方案四的結(jié)合。
解析方案四問題所在
方案四是設(shè)定最大范圍64,按2^n指數(shù)形式從1增加庫或者表數(shù)量,這樣帶來的是每次拆分進(jìn)行遷移時會影響當(dāng)總體數(shù)據(jù)量的1/2的數(shù)據(jù),影響范圍比較大,所以要么就直接拆分到32組、64組一勞永逸,要么每次1/2遷移。
方案四對應(yīng)遷移方案:
方案五詳解
現(xiàn)在我想方法時,保持一致性hash理念,1個1個節(jié)點(diǎn)來增加,而不是方案四的每次增加2^n-n個節(jié)點(diǎn)。但是代碼上就需要進(jìn)行對新節(jié)點(diǎn)內(nèi)的數(shù)據(jù)hash值判斷。
我們基于已經(jīng)發(fā)生過1次迭代分了兩個庫的情況來做后續(xù)迭代演示,首先看看已經(jīng)拆分兩個庫的情況:
數(shù)據(jù)落在第64號庫名為db64和第32號庫名為db32
迭代二: 區(qū)別與方案四直接增加兩個節(jié)點(diǎn),我們只增加一個節(jié)點(diǎn),這樣遷移數(shù)據(jù)時由原本影響1/2的用戶,將會只影響1/4的用戶。
在代碼中,我們先把分組從32個一組改為16個一組,再給代碼特殊處理 0~16的去到新的節(jié)點(diǎn) 16~32走回原來的32號節(jié)點(diǎn) 32~63走回原來64號節(jié)點(diǎn) 所以下面就要對節(jié)點(diǎn)特殊if else
// 按32改為16個為一組,分兩變?yōu)?個庫 count = 16; Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ // 上一個迭代這些數(shù)據(jù)落在db32中,現(xiàn)在走新增節(jié)點(diǎn)名為db16號的那個庫 dbValue = 16; return dbValue; } else { // 按原來規(guī)則走 return dbValue; }
迭代三:
這樣就可以分迭代完成方案四種的一輪的遷移
遷移前可以先上線,增加一段開關(guān)代碼,請求接口特殊處理hash值小于16的訂單號或者用戶號,這樣就只會影響1/4的人
// 在請求接口中增加邏輯 public void doSomeService(Integer userId){ if(遷移是否完成的開關(guān)){ // 如果未完成 Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ //這部分用戶暫時不能走下面的邏輯 return ; } } return dbValue; } }
// 在分片時按32個為一組,分兩個庫 count = 16; Integer dbValue = userId % 64 / count * count ; if(dbValue<16){ // 上一個迭代這些數(shù)據(jù)落在db32中,有一半需要走新增節(jié)點(diǎn)名為db16號的那個庫 if(遷移是否完成的開關(guān)){ // 如果已經(jīng)完成,就去db16的庫 dbValue = 16; } return dbValue; } else { // 按原來規(guī)則走 return dbValue; }
如此類推,下一輪總共8個節(jié)點(diǎn)時,每次遷移只需要遷移1/8。其實(shí)也可以在第一個迭代時,不選擇dbValue小于16號的來做。直接8個分一組,只選擇dbValue<8的來做,這樣第一個迭代的影響范圍也會比較案例中小。上述案例用16只是比較好演示優(yōu)點(diǎn):
- 易于擴(kuò)展
- 數(shù)據(jù)逐漸增大過程中,慢慢增加節(jié)點(diǎn)
- 影響用戶數(shù)量少
- 按迭代進(jìn)行,減少風(fēng)險
- 遷移時間短,如敏捷迭代思想
缺點(diǎn):
- 一段時間下不均勻
方案六:一致性Hash理念——按范圍分庫(迭代遷移)
如同上述方案五是方案四+方案一,可以達(dá)到逐步遷移數(shù)據(jù),還有一種方案。就是方案四+方案三,只是不用取模后分組。
userId % 64 / count * count
因?yàn)樯鲜龉?#xff0c;得出結(jié)果中,不一定每一片數(shù)據(jù)都是平均分布的。其實(shí)我們可以取模后,按范圍劃分分片,如下公式。
第一片 0<userId % 64<15 第二片 16<userId % 64<31 第三片 32<userId % 64<47 第四片 48<userId % 64<63
當(dāng)然范圍可以自定義,看取模后落入哪個值的數(shù)量比較多,就切某一片數(shù)據(jù)就好了,具體就不畫圖了,跟方案四類似。
因?yàn)檫w移數(shù)據(jù)的原因,方案四中,如果數(shù)據(jù)量大,達(dá)到1000萬行記錄,每次遷移都需要遷移很多的數(shù)據(jù),所以很多公司會盡早分庫分表。
但是在業(yè)務(wù)優(yōu)先情況下,一直迭代業(yè)務(wù),數(shù)據(jù)一進(jìn)達(dá)到很多的情況下16分支一也是很多的數(shù)據(jù)時,我們就可以用一致性Hash理念--按范圍分庫
總結(jié)
以上是生活随笔為你收集整理的long 雪花算法_海量数据分库分表方案(一)算法方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: comsol分析时总位移代表什么_【仿真
- 下一篇: 家常红烧豆腐的做法?