UidGenerator是百度開源的Java語言實現,基於Snowflake算法的惟一ID生成器。並且,它很是適合虛擬環境,好比:Docker。另外,它經過消費將來時間克服了雪花算法的併發限制。UidGenerator提早生成ID並緩存在RingBuffer中。 壓測結果顯示,單個實例的QPS能超過6000,000。依賴環境:node
雪花算法幾個核心部分: 算法
百度分佈式id生成器作了修改: 數據庫
時間部分是28位,意味着默認只能承受8.5年(2^28-1/86400/365)。根據不一樣業務需求,能夠適當調整delta seconds,worker node id和sequence佔用位數。 UidGenerator提供兩種方式:DefaultUidGenerator 和 CachedUidGenerator 。數組
delta seconds緩存
指的是當前時間和epoch時間的時間差,單位位秒。epoch時間指的是UidGenerator生成分佈式ID服務第一次上線的時間,可配置,也必定要根據你的上線時間進行配置,由於默認的epoch時間但是2016-09-20,不配置的話,會浪費好幾年的可用時間。安全
worker id服務器
看下worker id是如何賦值的,先建立一個表:數據結構
DROP TABLE IF EXISTS WORKER_NODE; CREATE TABLE WORKER_NODE( ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY , HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name', PORT VARCHAR(64) NOT NULL COMMENT 'port', TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER', LAUNCH_DATE DATE NOT NULL COMMENT 'launch date', MODIFIED DATETIME NOT NULL COMMENT 'modified time', CREATED DATEIMTE NOT NULL COMMENT 'created time' ) COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;
分佈式ID的實例啓動的時候,往這個表中插入一行數據,獲得的id值就是準備賦給workerId的值。因爲workerId默認22位,那麼,集成UidGenerator生成分佈式ID的全部實例重啓次數是不容許超過4194303次(即2^22-1),不然會拋出異常。 固然也能夠自定義生成workerid的方式。併發
**sequence **異步
關鍵點實現:
protected synchronized long nextId() { long currentSecond = getCurrentSecond(); if (currentSecond < lastSecond) { long refusedSeconds = lastSecond - currentSecond; throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds); } if (currentSecond == lastSecond) { sequence = (sequence + 1) & bitsAllocator.getMaxSequence(); if (sequence == 0) { currentSecond = getNextSecond(lastSecond); } } else { sequence = 0L; } lastSecond = currentSecond; return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence); }
總結
時鐘回撥的處理比較簡單粗暴。另外若是使用UidGenerator的DefaultUidGenerator方式生成分佈式ID,必定要根據你的業務的狀況和特色,調整各個字段佔用的位數:
<property name="timeBits" value="28"/> <property name="workerBits" value="22"/> <property name="seqBits" value="13"/> <property name="epochStr" value="2016-09-20"/>
CachedUidGenerator是UidGenerator的重要改進實現。它的核心利用了RingBuffer,以下圖所示,它本質上是一個數組,數組中每一個項被稱爲slot。UidGenerator設計了兩個RingBuffer,一個保存惟一ID,一個保存flag。RingBuffer的尺寸是2^n,n必須是正整數:
RingBuffer Of Flag
保存flag這個RingBuffer的每一個slot的值都是0或者1,0是CAN_PUT_FLAG的標誌位,1是CAN_TAKE_FLAG的標識位。每一個slot的狀態要麼是CAN_PUT,要麼是CAN_TAKE。以某個slot的值爲例,初始值爲0,即CAN_PUT。接下來會初始化填滿這個RingBuffer,這時候這個slot的值就是1,即CAN_TAKE。等獲取分佈式ID時取到這個slot的值後,這個slot的值又變爲0,以此類推。
RingBuffer Of UID
保存惟一ID的RingBuffer有兩個指針,Tail指針和Cursor指針。
Tail指針表示最後一個生成的惟一ID。若是這個指針追上了Cursor指針,意味着RingBuffer已經滿了。這時候,不容許再繼續生成ID了。用戶能夠經過屬性rejectedPutBufferHandler指定處理這種狀況的策略。
Cursor指針表示最後一個已經給消費的惟一ID。若是Cursor指針追上了Tail指針,意味着RingBuffer已經空了。這時候,不容許再繼續獲取ID了。用戶能夠經過屬性rejectedTakeBufferHandler指定處理這種異常狀況的策略。
另外,若是你想加強RingBuffer提高它的吞吐能力,那麼須要配置一個更大的boostPower值:
<!-- RingBuffer size擴容參數, 可提升UID生成能力.即每秒產生ID數上限能力 --> <!-- 默認:3,原bufferSize=2^13, 擴容後bufferSize = 2^13 << 3 = 65536 --> <property name="boostPower" value="3"/>
CachedUidGenerator的理論講完後,接下來就是它具體是如何實現的了,咱們首先看它的申明,它是實現了DefaultUidGenerator,因此,它事實上就是對DefaultUidGenerator的加強:
public class CachedUidGenerator extends DefaultUidGenerator implements DisposableBean { ... ... }
worker id
CachedUidGenerator的workerId實現繼承自它的父類DefaultUidGenerator,即實例啓動時往表WORKER_NODE插入數據後獲得的自增ID值。
接下來深刻解讀CachedUidGenerator的核心操做,即對RingBuffer的操做,包括初始化、取分佈式惟一ID、填充分佈式惟一ID等。
初始化
CachedUidGenerator在初始化時除了給workerId賦值,還會初始化RingBuffer。這個過程主要工做有:
說明:第二步的異步線程實現很是重要,也是UidGenerator解決時鐘回撥的關鍵:在知足填充新的惟一ID條件時,經過時間值遞增獲得新的時間值(lastSecond.incrementAndGet()),而不是System.currentTimeMillis()這種方式,而lastSecond是AtomicLong類型,因此能保證線程安全問題。
取值
RingBuffer初始化有值後,接下來的取值就簡單了。不過,因爲分佈式ID都保存在RingBuffer中,取值過程當中就會有一些邏輯判斷:
經過上面對UidGenerator的分析可知,CachedUidGenerator方式主要經過採起以下一些措施和方案規避了時鐘回撥問題和加強惟一性: