雪花算法(snowflake)作分庫分表shard key,數據傾斜,分表不均問題


之前有一篇文章介紹分布式id生成粗略選型,提到雪花算法作為一個優秀的方案,滿足了我們在分布式下的id生成需求。但如果直接把雪花算法生成的id作為分表鍵(shard key)在低並發下是會有問題的。下面來一起看下。

現象

我們分表數量是256張表(tb_0,tb_1,tb_2...tb_255),分表規則用雪花算法生成的id對256取余(snowflakeId % 256)。跑了一段時間后,發現,數據總數落到256中的前幾張表(tb_0,tb_1等下標值小的表里),后面下標值大的表則幾乎無數據,發生了分表傾斜。

分析

回到算法本身,如前文分布式id生成粗略選型介紹所述,雪花算法是由三部分組成,高位的時間戳,中間的機器編號,加低位的自增序列。我們重點關注低位的自增序列。

 

 

 

image.png

生成最終id核心實現代碼

 

return ((currentMillis - EPOCH) << 22) | (workerId << 12) | sequence;

按照算法的實現(實現代碼可以百度,一大把),12 bit自增序列號可以表示 2^12 = 4096 個 ID,所以理論上每毫秒(注意是每毫秒ms)的自增長序列(sequence)都從0開始,到4095為止。如果到了4095,則重新從0開始循環(毫秒值也進入下一毫秒)。說到這里是不是發現什么了?再划下重點————每毫秒都是從0開始。核心實現

    //如果是同一時間生成的,則進行毫秒內序列
    if (lastTimestamp == timestamp) {
         sequence = (sequence + 1) & sequenceMask;
        //毫秒內序列溢出
        if (sequence == 0) {
            //阻塞到下一個毫秒,獲得新的時間戳
            timestamp = tilNextMillis(lastTimestamp);
        }
    }
    //時間戳改變,毫秒內序列重置
    else {
        sequence = 0L;
    }

 

 

那么我們來看下低並發下的結果表現(高低並發怎么界定?TPS低於1000的都算吧,而其實很多業務系統的單機TPS是達不到1000的)。由於系統並發比較低,每次請求毫秒數幾乎都不同,那么,sequence都是0,或者很小的數字。所以,就導致算法生成的id分表后基本集中在前幾個下標小的分表里。

解決方案

美團點評的實現里(核心類https://github.com/Meituan-Dianping/Leaf/blob/master/leaf-core/src/main/java/com/sankuai/inf/leaf/snowflake/SnowflakeIDGenImpl.java)其實有對這個問題做過優化,關鍵優化代碼

sequence = RANDOM.nextInt(100);

就是對每毫秒起始的sequence取隨值,美團的隨機范圍是0到100。最終的效果就是生成的id會均勻分布在tb_0到tb_100。而我們如果分表數是256,則需要改成

sequence = RANDOM.nextInt(256);

總結

雪花算法是一個優秀的分布式id生成算法,而且為高並發設計。如果直接將雪花算法的id用作分庫分表的shard key,需要注意業務系統在低並發下分表不均的問題,解決方案也在上面給出。控制算法低位的序列始終在一個范圍(分表數)內,隨機生成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM