Java精選面試題(微信小程序):5000+道面試題和選擇題,包含Java基礎(chǔ)、并發(fā)、JVM、線程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架構(gòu)設(shè)計(jì)、大廠真題等,在線隨時(shí)刷題!
最近我們線上系統(tǒng)發(fā)生了一起嚴(yán)重事故:訂單號(hào)/流水號(hào)出現(xiàn)了重復(fù),影響了核心業(yè)務(wù)流程。最終定位到根源:一個(gè)自研的二方包雪花算法ID生成器出現(xiàn)了問題。
下面我們來回顧一下雪花算法的標(biāo)準(zhǔn)結(jié)構(gòu),分析問題出在哪,并總結(jié)一些通用的設(shè)計(jì)建議。
一、標(biāo)準(zhǔn)雪花算法(Snowflake)
標(biāo)準(zhǔn)的Snowflake ID由一個(gè)64位long型整數(shù)構(gòu)成:
+----------------------------------------------------------------------------------------------------+
|1Bit |41Bits 時(shí)間戳 |5Bits 數(shù)據(jù)中心ID |5Bits 機(jī)器ID |12Bits 序列號(hào) |
+----------------------------------------------------------------------------------------------------+
1位符號(hào)位:始終為0,確保生成正數(shù)。
41位時(shí)間戳:記錄與固定起始時(shí)間的毫秒差,可支持約69年。
10位機(jī)器ID:用于標(biāo)識(shí)不同節(jié)點(diǎn)。
12位序列號(hào):在同一毫秒內(nèi)生成多個(gè)ID時(shí)使用,最多支持每毫秒生成4096個(gè)ID。
優(yōu)點(diǎn):
高性能生成唯一ID,按時(shí)間有序,適用于分布式環(huán)境。
二、我們的“定制版”雪花算法:問題在哪?
我們使用的二方包雪花算法結(jié)構(gòu)如下(根據(jù)排查推測):
+----------------------------------------------------------------------------------------------------+
|31Bits 時(shí)間戳Delta |13Bits 數(shù)據(jù)中心ID |4Bits 工作ID |8Bits 業(yè)務(wù)ID |8Bits 序列號(hào) |
+----------------------------------------------------------------------------------------------------+
看起來字段豐富,但存在嚴(yán)重問題:
1. 時(shí)間戳僅保留31位,最多支持24.85天!
左移33位后只用31位時(shí)間戳,
超過 2312^{31}231 毫秒后開始循環(huán),
我們自定義的起始時(shí)間是2018年,2025年時(shí)早已繞了無數(shù)圈。
2. BusinessId 用的是 IP 最后一段
使用的IPy用點(diǎn)分隔的最后一位,即192.168.0.1的1,極容易重復(fù)。
3. WorkId 和 DataCenterId 未配置,全為0
相當(dāng)于所有實(shí)例共享同一節(jié)點(diǎn)標(biāo)識(shí),唯一性形同虛設(shè)。
最終結(jié)果:時(shí)間輪回 + IP沖突 + 序列重復(fù),ID徹底撞車。
三、教訓(xùn)總結(jié)
通用組件不建議自研
雪花算法涉及時(shí)鐘回?fù)堋⑽贿\(yùn)算、分布式協(xié)調(diào)等關(guān)鍵細(xì)節(jié),成熟組件更穩(wěn)妥。
不盲信二方包
無論誰寫的代碼,都要看清實(shí)現(xiàn)邏輯,理解其唯一性保障機(jī)制。
合理設(shè)置機(jī)器ID
靠IP后綴太脆弱,建議集中規(guī)劃,統(tǒng)一分配Worker ID和DataCenter ID。
提前覆蓋邊界場景
模擬長時(shí)間運(yùn)行、序列號(hào)溢出、時(shí)間回?fù)艿葮O端情況,確保系統(tǒng)穩(wěn)健。
四、推薦做法
使用成熟的開源實(shí)現(xiàn),如 Hutool、Baomidou 等:
// Hutool 示例
Snowflake snowflake = IdUtil.getSnowflake(1,1);
longid = snowflake.nextId();
// Baomidou 示例(支持從 IP/MAC 自動(dòng)推導(dǎo),也可手動(dòng)指定)
DefaultIdentifierGenerator generator =newDefaultIdentifierGenerator(1,1);// workerId=1, dataCenterId=1
longid = generator.nextId("user");
對于中大型系統(tǒng),DataCenterId 一般用來標(biāo)識(shí)不同的機(jī)房或者 AZ (Availability Zone)。
WorkerId 的配置策略可以根據(jù)系統(tǒng)規(guī)模逐步演進(jìn):
簡單方式:通過配置文件手動(dòng)指定。這種方式配置簡便,適用于開發(fā)環(huán)境或單機(jī)部署。
標(biāo)準(zhǔn)方式:將 IP 與端口號(hào)(或進(jìn)程號(hào))拼接后進(jìn)行哈希,再對 WorkerId 總數(shù)取模。具備一定自動(dòng)化能力,不依賴外部系統(tǒng),適用于中小規(guī)模部署。
中級(jí)方案:依賴注冊中心(如 Eureka、Nacos),在服務(wù)注冊時(shí)分配編號(hào),結(jié)合服務(wù)ID保障唯一性。
高級(jí)方案:使用 Redis、Zookeeper 等集中協(xié)調(diào) WorkerId 分配與釋放,支持動(dòng)態(tài)擴(kuò)容、避免沖突。
隨著系統(tǒng)規(guī)模擴(kuò)大,推薦逐步引入更復(fù)雜但更穩(wěn)妥的機(jī)制,避免一開始就過度設(shè)計(jì)。
五、其它建議:不要將業(yè)務(wù)標(biāo)志拼入ID中
有時(shí)我們?yōu)榱舜_保唯一性,會(huì)試圖將業(yè)務(wù)信息(如類型前綴、模塊編號(hào))拼接進(jìn)ID。但這種做法會(huì)帶來一系列問題:
會(huì)導(dǎo)致 ID 非全數(shù)字,失去原本按時(shí)間遞增的排序特性,影響數(shù)據(jù)庫索引效率;
ID長度變得不規(guī)則或偏長,可能增加存儲(chǔ)成本,也會(huì)影響日志展示、用戶體驗(yàn);
若業(yè)務(wù)字段含義變動(dòng),還可能造成數(shù)據(jù)兼容性問題。
更穩(wěn)妥的做法是將業(yè)務(wù)字段單獨(dú)存儲(chǔ),ID僅用于唯一標(biāo)識(shí)和排序。
六、結(jié)語
別為造輪子而造輪子,尤其在通用組件上,不可抱僥幸心理。
如果你也有雪花算法的踩坑經(jīng)驗(yàn),歡迎交流分享!
作者:MrWho
來源:https://juejin.cn/post/7507203999102648360
公眾號(hào)“Java精選”所發(fā)表內(nèi)容注明來源的,版權(quán)歸原出處所有(無法查證版權(quán)的或者未注明出處的均來自網(wǎng)絡(luò),系轉(zhuǎn)載,轉(zhuǎn)載的目的在于傳遞更多信息,版權(quán)屬于原作者。如有侵權(quán),請聯(lián)系,筆者會(huì)第一時(shí)間刪除處理!
最近有很多人問,有沒有讀者交流群!加入方式很簡單,公眾號(hào)Java精選,回復(fù)“加群”,即可入群!
文章有幫助的話,點(diǎn)在看,轉(zhuǎn)發(fā)吧!
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.