分庫分表之拆分鍵設計(基因算法)

1. 背景

在關系數據庫中猜揪,當單個庫的負載、連接數拴念、并發(fā)數等達到數據庫的最大上限時政鼠,就得考慮做數據庫和表的拆分。如一個簡單的電商數據庫官帘,在業(yè)務初期,為了快速驗證業(yè)務模式涌哲,把用戶阀圾、商品、訂單都放到一個數據庫中账月,隨著業(yè)務的發(fā)展及用戶量的增長,單數據庫逐漸不能支撐業(yè)務(MySQL中單記錄容量超過1K時抓歼,單表數據量建議不超過一千萬條)谣妻,這時就得考慮把數據庫和表做出拆分。

2. 垂直拆分

所謂垂直拆分减江,就是將數據庫及表由一個拆分為多個,即垂直拆分為用戶數據庫巡莹、商品數據庫和訂單數據庫(垂直拆庫),訂單表可以垂直拆分為訂單基本信息表钉鸯,訂單收貨地址表贸营、訂單商品表(垂直拆表)等钞脂,每一個表里保存了一個訂單的一部分數據

image.png

3. 水平拆分

所謂垂直拆分刘莹,就是說在垂直拆庫/表后点弯,單庫/表容量還是太大狼钮,那就將一個庫、一個表水平擴展為多個庫猛蔽,多個表,每一個拆分后的表中保存的依然是一個訂單的完整信息毁枯。如電商數據庫,我們按水平拆分數據庫和表后赂韵,每一個拆分后的數據庫表與現有未拆分前的都保持一致。

image.png

4. 拆分鍵

隨著業(yè)務的發(fā)展及用戶量的增長质涛,分庫分表是必然的選擇稠歉,但是分庫分表后,容易遇到的問題汇陆,就是拆分鍵的設計怒炸。

一般情況下,拆分鍵的選取遵循以什么維度進行查詢就選取該維度為拆分鍵毡代。如:訂單表就以訂單號作為拆分鍵,商品表就以商品編號作為拆分鍵教寂。拆分鍵選取后灯蝴,對于一些非拆分鍵的單條件查詢,我們需要怎么支持呢孝宗?

4.1 等值法

對于非拆分鍵的單條件查詢,對這一個單條件的賦值耕肩,可以將其值與拆分鍵保持一致因妇。比如在電商場景中,用戶下訂單后猿诸,需要通過物流給用戶把商品送到用戶手上婚被。對于用戶來說僅能看到訂單信息,訂單上展示的物流信息用戶也是通過訂單號查詢而來梳虽;但對于物流系統(tǒng)來說址芯,其系統(tǒng)里的業(yè)務主鍵(拆分鍵)是運單號,此時窜觉,運單號如果和訂單號相同谷炸,即可完美解決這一問題。訂單表和運單表的基本數據模型如下:

訂單表

image.png

運單表

image.png

在訂單表中禀挫,拆分鍵order_id與運單表中的拆分鍵waybill_code值相同旬陡,當按訂單號查詢運單表里的運單信息時,可以直接查詢拆分鍵waybill_code獲取訂單對應的運單信息语婴。

小公司可以直接這樣設計描孟,因為訂單和運單都是同一個部門處理,但是稍微大點的公司就不太可能用這種方案砰左,因為需要跨部門協(xié)作匿醒,不同部門不同業(yè)務,很難使用同一個單號來當訂單號和運單號缠导,通常是會有個流水號作為全鏈路唯一廉羔。

4.2 索引法

對于常用的非拆分鍵,我們可以將其與拆分鍵之間建立一個索引關系僻造,當按該條件進行查詢時蜜另,先查詢對應的拆分鍵适室,再通過拆分鍵查詢對應的數據信息。

image.png

當查詢用戶(user001)的下單記錄時举瑰,通過用戶編碼先查詢索引表捣辆,查詢出user001的所有下單的訂單號(10001),再通過訂單號查詢訂單表獲取用戶的訂單信息此迅;同理汽畴,根據運單號(Y00232)查詢訂單信息時,在索引表里先查詢到對應的訂單號耸序,再根據訂單號查詢對應的訂單信息忍些。

缺點:

  1. 需要先從索引表反查一次;
  2. 這張索引表會很大坎怪,有時候索引表也需要分表罢坝,

4.3 基因法

基因法是對上面等值法的優(yōu)化,等值法要求不同表的拆分鍵值保持一致搅窿,但是實際上不同業(yè)務表的拆分鍵值是有其業(yè)務意義的嘁酿。

但是我們可以讓不同拆分鍵存在相同規(guī)則的部分,且這部分用來做拆分鍵男应,從而定位庫表闹司。

4.1 理論基礎

如果我們對一個10進制的數字按10取模,取模的結果與這串數的前面所有位都沒有任何關系沐飘,最后1位決定取模結果:

image.png

同理游桩,按100(102)取模,最后2位決定取模結果耐朴,按1000(103)取模借卧,最后3位決定取模結果:

image.png

同理:一個二進制的值,按2^n取模筛峭,也是最后n位決定取模結果:

image.png

那么我們在生成訂單號的時候谓娃,只要把order_no二進制的最后(n+1)位的二進制數設置為user_id的最后(n)位,那么我們對user_id/order_no取余都能得到相同的結果了蜒滩。(原理:比n位高的值滨达,都是b數的倍數,取余時直接歸零俯艰,所以取余就是取二進制最后n位)

4.2 應用

假如我們現在現有16個庫捡遍,128個表,定位的流程是hash(user_id)% 16 定位庫的位置竹握, hash(user_id)% 128 定位表的位置画株;

所以在我們生成訂單號時,先對user_id提取一個基因, 也就是二進制最后 7位谓传,然后這個最后7位二進制也作為order_no的二進制最后7位蜈项,這樣就能保證order_no的路由結果與user_id完全一致;

通過基因法续挟,我們可以通過非拆分鍵也能快速定位到具體的表紧卒。

4.3 代碼實現

備注:這個demo有點問題

    public static void main(String[] args) {
        SnowFlakeIdGenerator snowFlakeIdGenerator = new SnowFlakeIdGenerator();
 
        for (int i = 1; i < 1000; i++) {
            //生成userId和提取基因
            long userId = snowFlakeIdGenerator.generateId();
            String userIdGene = fetchGene(userId, 128);
            
            //利用基因生成orderId
            long rawOrderId = snowFlakeIdGenerator.generateId();
            Long orderId = generateWithGene(rawOrderId, userIdGene);
 
            System.out.println("userId: " + userId + ",余數:" + userId % 128);
            System.out.println("orderId: " + rawOrderId + ",余數:" + orderId % 128);
        }
    }
 
    /**
     * 抽取基因
     *
     * @param id    id
     * @param index 16/128 需要取余的數
     * @return {@link String}
     */
    public static String fetchGene(Long id, Integer index) {
        return String.format("%07d", Integer.valueOf(Long.toBinaryString(id % index)));
    }
 
    /**
     * 根據基因,生成id
     *
     * @param rawId        原始id
     * @param binarySuffix 二進制后綴
     * @return {@link Long}
     */
    public static Long generateWithGene(Long rawId, String binarySuffix) {
        String s = Long.toBinaryString(rawId);
        String substring = s.substring(0, s.length() - binarySuffix.length() + 1);
        String newBinaryString = substring + binarySuffix;
        return Long.parseLong(newBinaryString, 2);
    }

分庫分表后诗祸,我們通常會采用雪花算法來生成分片鍵跑芳,比如訂單號,商品編號直颅,
我們在雪花算法的圖下加入了訂單id生成的示意圖博个, 比如我們有16個庫,128張表功偿,則需要截取二進制訂單id的最后LOG(128,2)=7位盆佣,作為分庫/分表基因。

image.png

然后對訂單id用hash生成60位械荷,加上從用戶id那邊獲取的4位基因共耍,形成最終的訂單id。其他業(yè)務id也使用相同的辦法處理养葵。

分庫/分表策略時,直接設定使用該id進行水平切分瘩缆。由于所有業(yè)務都有相同的最后4位关拒,這樣sharding時都會進入相同的庫/表

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庸娱,隨后出現的幾起案子着绊,更是在濱河造成了極大的恐慌,老刑警劉巖熟尉,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件归露,死亡現場離奇詭異,居然都是意外死亡斤儿,警方通過查閱死者的電腦和手機剧包,發(fā)現死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往果,“玉大人疆液,你說我怎么就攤上這事∩轮” “怎么了堕油?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我掉缺,道長卜录,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任眶明,我火速辦了婚禮蝗羊,結果婚禮上悍缠,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好澎怒,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稀轨,像睡著了一般填物。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上幌缝,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天灸促,我揣著相機與錄音,去河邊找鬼涵卵。 笑死浴栽,一個胖子當著我的面吹牛,可吹牛的內容都是我干的轿偎。 我是一名探鬼主播典鸡,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼坏晦!你這毒婦竟也來了萝玷?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昆婿,失蹤者是張志新(化名)和其女友劉穎球碉,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體仓蛆,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡睁冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了看疙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豆拨。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖能庆,靈堂內的尸體忽然破棺而出辽装,到底是詐尸還是另有隱情,我是刑警寧澤相味,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布拾积,位于F島的核電站殉挽,受9級特大地震影響,放射性物質發(fā)生泄漏拓巧。R本人自食惡果不足惜斯碌,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肛度。 院中可真熱鬧傻唾,春花似錦、人聲如沸承耿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽加袋。三九已至凛辣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間职烧,已是汗流浹背扁誓。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚀之,地道東北人蝗敢。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像足删,于是被迫代替她去往敵國和親寿谴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容