日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UidGenerator

發布時間:2025/3/21 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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

public long take() {// spin get next available cursorlong currentCursor = cursor.get();long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1);// check for safety consideration, it never occursAssert.isTrue(nextCursor >= currentCursor, "Curosr can't move back");// trigger padding in an async-mode if reach the thresholdlong currentTail = tail.get();if (currentTail - nextCursor < paddingThreshold) {LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail,nextCursor, currentTail - nextCursor);bufferPaddingExecutor.asyncPadding();}// cursor catch the tail, means that there is no more available UID to takeif (nextCursor == currentCursor) {rejectedTakeHandler.rejectTakeBuffer(this);}// 1. check next slot flag is CAN_TAKE_FLAGint nextCursorIndex = calSlotIndex(nextCursor);Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status");// 2. get UID from next slot// 3. set next slot flag as CAN_PUT_FLAG.long uid = slots[nextCursorIndex];flags[nextCursorIndex].set(CAN_PUT_FLAG);// Note that: Step 2,3 can not swap. If we set flag before get value of slot, the producer may overwrite the// slot with a new UID, and this may cause the consumer take the UID twice after walk a round the ringreturn uid;}
  • 通過AtomicLong.updateAndGet來避免對整個方法進行加鎖,獲取一個可以訪問的UID的游標值,根據這個下標獲取slots中相關的uid直接返回
  • 緩存中可用的uid(Tail - Cursor)小于一定閾值的時候,需要啟動另外一個線程來生成一批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;}
  • 獲取Tail的下標值,如果緩存區滿的話直接調用RejectedPutHandler.rejectPutBuffer方法
  • 未滿的話將UID放置在slots數組相應的位置上,同時將Flags數組相應的位置改為CAN_TAKE_FLAG
  • CachedUidGenerator通過緩存的方式預先生成一批UID列表,可以解決UID獲取時候的耗時,但這種方式也有不好點,一方面需要耗費內存來緩存這部分數據,另外如果訪問量不大的情況下,提前生成的UID中的時間戳可能是很早之前的,DefaultUidGenerator應該在大部分的場景中就可以滿足相關的需求了。



    作者:liujianhuiouc
    鏈接:https://www.jianshu.com/p/761688b4d6dd
    來源:簡書
    著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

    總結

    以上是生活随笔為你收集整理的UidGenerator的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。