UidGenerator
UidGenerator是什么
UidGenerator是百度開源的一款分布式高性能的唯一ID生成器,更詳細的情況可以查看github:uid-generator,里面有更詳細的介紹文檔說明,其也是基于snowflake模型的一種ID生成器,我們再來回顧以下雪花模型。
Snowflake算法描述:指定機器 & 同一時刻 & 某一并發序列,是唯一的。據此可生成一個64 bits的唯一ID(long)。默認采用上圖字節分配方式:
sign(1bit)
固定1bit符號標識,即生成的UID為正數。
delta seconds (28 bits)
當前時間,相對于時間基點"2016-05-20"的增量值,單位:秒,最多可支持約8.7年
worker id (22 bits)
機器id,最多可支持約420w次機器啟動。內置實現為在啟動時由數據庫分配,默認分配策略為用后即棄,后續可提供復用策略。
sequence (13 bits)
每秒下的并發序列,13 bits可支持每秒8192個并發。
這些字段的長度可以根據具體的應用需要進行動態的調整,滿足總長度為64位即可
百度的worker id的生成策略和美團的生成策略不太一樣,美團的snowflake主要利用本地配置的port和IP來唯一確定一個workid,美團的這種生成方式可能由于手工配置錯誤造成port重復,最終產生重復ID的風險,而百度的這種生成方式每次都是新增的,可能會一段時間后有worker id用完的情況,但人工配置錯誤的可能性很小。
DefaultUidGenerator
nextId方法主要負責ID的生成,這種實現方式很簡單,如果毫秒數未發生變化,在序列號加一即可,毫秒數發生變化,重置Sequence為0(Leaf文章中講過,重置為0會造成如果利用這個ID分表,并發量不大的時候,sequence字段會一直為0等,會出現數據傾斜)
protected synchronized long nextId() {long currentSecond = getCurrentSecond();// Clock moved backwards, refuse to generate uidif (currentSecond < lastSecond) {long refusedSeconds = lastSecond - currentSecond;throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);}// At the same second, increase sequenceif (currentSecond == lastSecond) {sequence = (sequence + 1) & bitsAllocator.getMaxSequence();// Exceed the max sequence, we wait the next second to generate uidif (sequence == 0) {currentSecond = getNextSecond(lastSecond);}// At the different second, sequence restart from zero} else {sequence = 0L;}lastSecond = currentSecond;// Allocate bits for UIDreturn bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);}CachedUidGenerator
正如名字體現的那樣,這是一種緩存型的ID生成方式,當剩余ID不足的時候,會異步的方式重新生成一批ID緩存起來,后續請求的時候直接返回現成的ID即可。
我們來看一下這種方式下幾個重要的數據結構,采用了RingBuffer的方式來緩存相關UID信息。
Tail: 指向當前最后一個可用的UID位置
Cursor: 指向下一個獲取UID的位置,其一定是小于Tail
Tail - Cursor表示的是現在可用的UID數量,當可用UID數量小于一定閾值的時候會重新添加一批新的UID到RingBuffer中。
代碼層面的優化
代碼中通過字節的填充,來避免偽共享的產生。
多核處理器處理相互獨立的變量時,一旦這些變量處于同一個緩存行,不同變量的操作均會造成這一個緩存行失效,影響緩存的實際效果,造成很大的緩存失效的性能問題。下面圖中線程處理不同的兩個變量,但這兩個變量的修改都會造成整個緩存行的失效,導致無效的加載、失效,出現了偽共享的問題
RingBuffer中通過定義一個PaddedAtomicLong來獨占一個緩存行,代碼中的實現填充可能需要根據具體的執行系統做一些調整,保證其獨占一個緩存行即可。
下面我們來看下如何獲取相關的UID
UID 的生成
public synchronized boolean put(long uid) {long currentTail = tail.get();long currentCursor = cursor.get();// tail catches the cursor, means that you can't put any cause of RingBuffer is fulllong distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor);if (distance == bufferSize - 1) {rejectedPutHandler.rejectPutBuffer(this, uid);return false;}// 1. pre-check whether the flag is CAN_PUT_FLAGint nextTailIndex = calSlotIndex(currentTail + 1);if (flags[nextTailIndex].get() != CAN_PUT_FLAG) {rejectedPutHandler.rejectPutBuffer(this, uid);return false;}// 2. put UID in the next slot// 3. update next slot' flag to CAN_TAKE_FLAG// 4. publish tail with sequence increase by oneslots[nextTailIndex] = uid;flags[nextTailIndex].set(CAN_TAKE_FLAG);tail.incrementAndGet();// The atomicity of operations above, guarantees by 'synchronized'. In another word,// the take operation can't consume the UID we just put, until the tail is published(tail.incrementAndGet())return true;}CachedUidGenerator通過緩存的方式預先生成一批UID列表,可以解決UID獲取時候的耗時,但這種方式也有不好點,一方面需要耗費內存來緩存這部分數據,另外如果訪問量不大的情況下,提前生成的UID中的時間戳可能是很早之前的,DefaultUidGenerator應該在大部分的場景中就可以滿足相關的需求了。
作者:liujianhuiouc
鏈接:https://www.jianshu.com/p/761688b4d6dd
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的UidGenerator的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入理解 SpringBoot 启动机制
- 下一篇: 百度开源分布式id生成器uid-gene