『SnowFlake』雪花算法的详解及时间回拨解决方案
📣讀完這篇文章里你能收獲到
- 圖文形式為你講解原生雪花算法的特征及原理
- 了解時間回撥的概念以及可能引起發此現象的操作
- 掌握時間回撥的解決方案—基于時鐘序列的雪花算法
- 關于雪花算法的常見問題解答
文章目錄
- 一、原生的雪花算法
- 1. 簡介
- 2. 特征
- 3. 原理
- 3.1 格式(64bit)
- 3.2 字節分配
- 二、雪花算法的時間回撥問題
- 1. 問題描述
- 2. 現象引發
- 3. 常見的處理方式
- 3.1 直接拋出異常
- 3.2 延遲等待
- 三、基于時鐘序列解決時間回撥的方案
- 1. 簡介
- 2. 設計思路
- 3. 算法支持
- 4. 關鍵實現代碼
- 四、關于雪花算法的常見問題解答
- 1. 雪花算法支持的并發數最大多少?
- 2. 雪花算法支持最多支持系統運行多少年?
- 3. 用了雪花Id,出現負閏秒為什么會導致系統大量拋異常?
一、原生的雪花算法
1. 簡介
- 分布式 ID 生成算法用于在分布式系統中生成全局唯一的 ID 標識
- Twitter 提出的雪花算法便是其中一種知名的算法,也是一種發號器方案
- 百度(uid-generator)、美團(Leaf)及滴滴(Tinyid)等開源分布式ID都是基于雪花算法實現的,如果有人問你有關唯一 ID 生成的問題,你應該立即想到雪花!
- 雪花是二進制的 64位(只有 63 位用于填充有符號整數),最終數字一般以十進制序列化
2. 特征
- 全局唯一性:雪花算法可以保證集群系統的ID全局唯一
- 趨勢遞增:由于強依賴時間戳,所以整體趨勢會隨著時間遞增
- 單調遞增(×):不滿足單調遞增,在不考慮時間回撥的情況下,雖然在單機中可以保持單調遞增,但在分布式集群中無法做到單調遞增,只能保證總體趨勢遞增
- 信息安全指的是ID生成不規則,無法猜測下一個
3. 原理
3.1 格式(64bit)
二進制64位長整型數字:1bit保留 + 41bit時間戳 + 10bit機器 + 12bit序列號
3.2 字節分配
- 1bit不用:因為二進制中最高位是符號位,1表示負數,0表示正數,生成的id一般都是用整數,所以最高位固定為0
- 41bit時間戳:這里采用的就是當前系統的具體時間,單位為毫秒
- 10bit工作機器ID(workerId):每臺機器分配一個id,這樣可以標示不同的機器,但是上限為1024,標示一個集群某個業務最多部署的機器個數上限
- 12bit序列號(自增域):表示在某一毫秒下,這個自增域最大可以分配的bit個數,在當前這種配置下,每一毫秒可以分配2^12 = 4096個數據
二、雪花算法的時間回撥問題
1. 問題描述
簡單說就是時間被調整回到了之前的時間,由于雪花算法重度依賴機器的當前時間,所以一旦發生時間回撥,將有可能導致生成的 ID 可能與此前已經生成的某個 ID 重復(前提是剛好在同一毫秒生成 ID 時序列號也剛好一致),這就是雪花算法最經常討論的問題——時間回撥
2. 現象引發
- 網絡時間校準
- 人工設置
- 出現負閏秒(關于閏秒的介紹會在后面講到)
3. 常見的處理方式
3.1 直接拋出異常
在雪花算法原本的實現中,針對這種問題,算法本身只是返回錯誤,由應用另行決定處理邏輯,如果是在一個并發不高或者請求量不大的業務系統中,錯誤等待或者重試的策略問題不大,但是如果是在一個高并發的系統中,這種策略顯得過于粗暴
3.2 延遲等待
將當前線程阻塞3ms,之后再獲取時間,看時間是否比上一次請求的時間大,如果大了,說明恢復正常了,則不用管如果還小,說明真出問題了,則拋出異常,缺點仍然如3.1所描述
當使用雪花算法出現時間回撥時,不想拋異常,又希望能繼續保持全局唯一性、趨勢遞增、信息安全,可以了解第四點,基于時間序列的方案
三、基于時鐘序列解決時間回撥的方案
1. 簡介
我這里介紹的是一種基于修改擴展位的思路,基于時鐘序列的雪花算法
二進制64位長整型數字:1bit保留 + 41bit時間戳 + 3位時鐘序列 + 7bit機器 + 12bit序列號
2. 設計思路
- 如上圖,將原本10位的機器碼拆分成3位時鐘序列及7位機器碼
- 發生時間回撥的時候,時間已經發生了變化,那么這時將時鐘序列新增1位,重新定義整個雪花Id
- 為了避免實例重啟引起時間序列丟失,因此時鐘序列最好通過DB/緩存等方式存儲起來
3. 算法支持
- 還是支持最長 69 年多的運行時間
- 分布式實例規模由210(1024)降至27(128)
- 單實例每毫秒仍然支持 4096次請求
- 每個分布式實例支持最多 2^3(8) 次時間回撥
4. 關鍵實現代碼
.Net Demo 其他語言參考流程自行改造
/// <summary> /// 獲取下一個ID /// </summary> /// <returns></returns> public long NextId() {lock (_lock){//當前系統時間戳var currentTimestamp = TimeGen();//出現時間回撥 當前系統時間小于最后更新時間if (currentTimestamp < _lastTimestamp){// _clockSequence自增,和CLOCK_SEQUENCE_MASK相與一下,去掉高位_clockSequence = (_clockSequence + 1) & CLOCK_SEQUENCE_MASK;}// 如果上次生成時間和當前時間相同,在同一毫秒內if (_lastTimestamp == currentTimestamp){// sequence自增,和SEQUENCE_MASK相與一下,去掉高位_sequence = (_sequence + 1) & SEQUENCE_MASK;//判斷是否溢出,也就是每毫秒內超過1024,當為1024時,與sequenceMask相與,sequence就等于0if (_sequence == 0){//等待到下一毫秒currentTimestamp = TilNextMillis(_lastTimestamp);}}else{//如果和上次生成時間不同,重置sequence,就是下一毫秒開始,sequence計數重新從0開始累加,_sequence = 0;}_lastTimestamp = currentTimestamp;return ((currentTimestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | _clockSequence << CLOCK_SEQUENCE_SHIFT | (WorkerId << WORKER_ID_SHIFT) | _sequence;} }‘
四、關于雪花算法的常見問題解答
1. 雪花算法支持的并發數最大多少?
- 這個是由序列號的位數決定的,原生雪花算法序列號12位,也就是1毫秒最大可生成2^12(4096),相當于1秒可生成 4096 * 1000 個ID,也就是QPS可以到 409.6 w/s
2. 雪花算法支持最多支持系統運行多少年?
-
這個是由時間戳位數決定的,原生雪花算法時間戳占41位,也就是支持最大的時間戳為2^41(2199023255552),而1年的總毫秒數為3600 * 1000 * 24 * 365 = 31,536,000,000,因此2^41 / 1年的總毫秒數≈69.7年
-
其實衍生出另一個問題,41位能表示的最大的時間戳為2^41(2199023255552)對應的時間應該是2039-09-07 23:47:35,距離現在只有不到20年的時間,為什么算出來的是69年呢?
-
其實時間戳的算法是1970年1月1日到指點時間所經過的毫秒或秒數,那咱們把開始時間從2021年開始,就可以延長41位時間戳能表達的最大時間,所以這里實際指的是相對自定義開始時間的時間戳
3. 用了雪花Id,出現負閏秒為什么會導致系統大量拋異常?
- 閏秒是偶爾運用于協調世界時(UTC)的調整,經由增加或減少一秒,以消彌精確的時間(使用原子鐘測量)和不精確的觀測太陽時(稱為UT1),之間的差異
- 這種做法已被證明具有破壞性,特別是在二十一世紀,尤其是在依賴精確時間戳或時間關鍵程序控制的服務中
- 而雪花算法嚴重依賴時間戳,當出現負閏秒也就是時間減少一秒時(時間往前回撥1秒),雪花Id就可能出現重復,而原生的雪花算法出現時間回撥的處理方式是直接拋異常
- 2022年11月,在第27屆國際計量大會上,科學家和政府代表投票決定到2035年取消閏秒
總結
以上是生活随笔為你收集整理的『SnowFlake』雪花算法的详解及时间回拨解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机动车污染排放检验信息系统信息化建设目标
- 下一篇: Openbravo ERP 3.0安装指