發號器實踐,企業發號器實例--snowflake系列

美團發號器Leaf-snowflake方案node

Leaf-snowflake方案徹底沿用snowflake方案的bit位設計,便是「1+41+10+12」的方式組裝ID號。對於workerID的分配,當服務集羣數量較小的狀況下,徹底能夠手動配置。Leaf服務規模較大,動手配置成本過高。因此使用Zookeeper持久順序節點的特性自動對snowflake節點配置wokerID。Leaf-snowflake是按照下面幾個步驟啓動的:git

  1. 啓動Leaf-snowflake服務,鏈接Zookeeper,在leaf_forever父節點下檢查本身是否已經註冊過(是否有該順序子節點)。
  2. 若是有註冊過直接取回本身的workerID(zk順序節點生成的int類型ID號),啓動服務。
  3. 若是沒有註冊過,就在該父節點下面建立一個持久順序節點,建立成功後取回順序號當作本身的workerID號,啓動服務。

image

弱依賴ZooKeeper

除了每次會去ZK拿數據之外,也會在本機文件系統上緩存一個workerID文件。當ZooKeeper出現問題,剛好機器出現問題須要重啓時,能保證服務可以正常啓動。這樣作到了對三方組件的弱依賴。必定程度上提升了SLAgithub

解決時鐘問題

由於這種方案依賴時間,若是機器的時鐘發生了回撥,那麼就會有可能生成重複的ID號,須要解決時鐘回退的問題。算法

image

參見上圖整個啓動流程圖,服務啓動時首先檢查本身是否寫過ZooKeeper leaf_forever節點:docker

  1. 若寫過,則用自身系統時間與leaf_forever/${self}節點記錄時間作比較,若小於leaf\_forever/${self}時間則認爲機器時間發生了大步長回撥,服務啓動失敗並報警。
  2. 若未寫過,證實是新服務節點,直接建立持久節點leaf_forever/${self}並寫入自身系統時間,接下來綜合對比其他Leaf節點的系統時間來判斷自身系統時間是否準確,具體作法是取leaf_temporary下的全部臨時節點(全部運行中的Leaf-snowflake節點)的服務IP:Port,而後經過RPC請求獲得全部節點的系統時間,計算sum(time)/nodeSize。
  3. 若abs( 系統時間-sum(time)/nodeSize ) < 閾值,認爲當前系統時間準確,正常啓動服務,同時寫臨時節點leaf_temporary/${self} 維持租約。
  4. 不然認爲本機系統時間發生大步長偏移,啓動失敗並報警。
  5. 每隔一段時間(3s)上報自身系統時間寫入leaf_forever/${self}。

因爲強依賴時鐘,對時間的要求比較敏感,在機器工做時NTP同步也會形成秒級別的回退,建議能夠直接關閉NTP同步。要麼在時鐘回撥的時候直接不提供服務直接返回ERROR_CODE,等時鐘追上便可。或者作一層重試,而後上報報警系統,更或者是發現有時鐘回撥以後自動摘除自己節點並報警數組

京東到家發號器方案緩存

百度發號器方案
UidGenerator是Java實現的, 基於Snowflake算法的惟一ID生成器。UidGenerator以組件形式工做在應用項目中, 支持自定義workerId位數和初始化策略, 從而適用於docker等虛擬化環境下實例自動重啓、漂移等場景。 在實現上, UidGenerator經過借用將來時間來解決sequence自然存在的併發限制; 採用RingBuffer來緩存已生成的UID, 並行化UID的生產和消費, 同時對CacheLine補齊,避免了由RingBuffer帶來的硬件級「僞共享」問題. 最終單機QPS可達600萬。
ringbuffer.png
RingBuffer環形數組,數組每一個元素成爲一個slot。RingBuffer容量,默認爲Snowflake算法中sequence最大值,且爲2^N。可經過boostPower配置進行擴容,以提升RingBuffer 讀寫吞吐量。併發

Tail指針、Cursor指針用於環形數組上讀寫slot:性能

  • Tail指針
    表示Producer生產的最大序號(此序號從0開始,持續遞增)。Tail不能超過Cursor,即生產者不能覆蓋未消費的slot。當Tail已遇上curosr,此時可經過rejectedPutBufferHandler指定PutRejectPolicy
  • Cursor指針
    表示Consumer消費到的最小序號(序號序列與Producer序列相同)。Cursor不能超過Tail,即不能消費未生產的slot。當Cursor已遇上tail,此時可經過rejectedTakeBufferHandler指定TakeRejectPolicy

CachedUidGenerator採用了雙RingBuffer,Uid-RingBuffer用於存儲Uid、Flag-RingBuffer用於存儲Uid狀態(是否可填充、是否可消費)ui

因爲數組元素在內存中是連續分配的,可最大程度利用CPU cache以提高性能。但同時會帶來「僞共享」FalseSharing問題,爲此在Tail、Cursor指針、Flag-RingBuffer中採用了CacheLine 補齊方式。
cacheline_padding.png

RingBuffer填充時機
  • 初始化預填充
    RingBuffer初始化時,預先填充滿整個RingBuffer.
  • 即時填充
    Take消費時,即時檢查剩餘可用slot量(tail-cursor),如小於設定閾值,則補全空閒slots。閾值可經過paddingFactor來進行配置,請參考Quick Start中CachedUidGenerator配置
  • 週期填充
    經過Schedule線程,定時補全空閒slots。可經過scheduleInterval配置,以應用定時填充功能,並指定Schedule時間間隔
相關文章
相關標籤/搜索