Springboot整合百度開源分布式ID生成器UIDGenerator
環境:sprinboot2.3.12.RELEASE + uid-generator1.0.0
簡介
UidGenerator是Java實現的, 基于Snowflake算法的唯一ID生成器。UidGenerator以組件形式工作在應用項目中, 支持自定義workerId位數和初始化策略, 從而適用于docker等虛擬化環境下實例自動重啟、漂移等場景。 在實現上, UidGenerator通過借用未來時間來解決sequence天然存在的并發限制; 采用RingBuffer來緩存已生成的UID, 并行化UID的生產和消費, 同時對CacheLine補齊,避免了由RingBuffer帶來的硬件級「偽共享」問題. 最終單機QPS可達600萬。
依賴版本:Java8及以上版本, MySQL(內置WorkerID分配器, 啟動階段通過DB進行分配; 如自定義實現, 則DB非必選依賴)
Snowflake算法

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個并發。
以上參數均可通過Spring進行自定義
CachedUidGenerator
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狀態(是否可填充、是否可消費)
由于數組元素在內存中是連續分配的,可最大程度利用CPU cache以提升性能。但同時會帶來「偽共享」FalseSharing問題,為此在Tail、Cursor指針、Flag-RingBuffer中采用了CacheLine 補齊方式。

RingBuffer填充時機
- 初始化預填充 RingBuffer初始化時,預先填充滿整個RingBuffer.
- 即時填充 Take消費時,即時檢查剩余可用slot量(tail - cursor),如小于設定閾值,則補全空閑slots。閾值可通過paddingFactor來進行配置,請參考Quick Start中CachedUidGenerator配置
- 周期填充 通過Schedule線程,定時補全空閑slots。可通過scheduleInterval配置,以應用定時填充功能,并指定Schedule時間間隔
以上為百度的官方介紹,接下來我們將其整合到Springboot項目中
UIDGenerator下載
在下面地址下載uid-generator

UIDGenerator環境配置
創建表
- DROP TABLE IF EXISTS WORKER_NODE;
- CREATE TABLE WORKER_NODE
- (
- ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
- 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 TIMESTAMP NOT NULL COMMENT 'modified time',
- CREATED TIMESTAMP NOT NULL COMMENT 'created time',
- PRIMARY KEY(ID)
- )COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;
將其項目中的mapper.xml文件copy到自己項目中,對應的WorkerNodeEntity,WorkerNodeDAO,
DisposableWorkerIdAssigner都copy到自己的項目中。
DisposableWorkerIdAssigner主要是修改注入的dao,因為這里我們需要修改默認的dao相關的配置。
項目配置
依賴
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>2.1.4</version>
- </dependency>
- <dependency>
- <groupId>com.github.pagehelper</groupId>
- <artifactId>pagehelper-spring-boot-starter</artifactId>
- <version>1.3.0</version>
- </dependency>
mybatis配置
- pagehelper:
- helperDialect: mysql
- reasonable: true
- pageSizeZero: true
- offsetAsPageNum: true
- rowBoundsWithCount: true
- ---
- mybatis:
- type-aliases-package: com.pack.domain
- mapper-locations:
- - classpath:/mappers/*.xml
- configuration:
- lazy-loading-enabled: false
- aggressive-lazy-loading: false
mapper文件中的namespace及對應domain修改為自己路徑下的。
配置UIDGenerator
- @Configuration
- public class UIDConfig {
- @Bean
- public DefaultUidGenerator defaultUidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
- DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator() ;
- defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner) ;
- defaultUidGenerator.setTimeBits(29) ;
- defaultUidGenerator.setWorkerBits(21) ;
- defaultUidGenerator.setSeqBits(13) ;
- defaultUidGenerator.setEpochStr("2021-01-01") ;
- return defaultUidGenerator ;
- }
- }
注意這里的
DisposableWorkerIdAssigner是copy到自己項目中的,不是百度提供的。
- @Component
- public class DisposableWorkerIdAssigner implements WorkerIdAssigner {
- @Resource
- private WorkerNodeMapper workerNodeDAO;
- // other code
- }
到此所有的都配置完成了。
測試
- @Resource
- private DefaultUidGenerator uidGenerator ;
- @Test
- public void testGeneratorId() {
- for (int i = 0; i < 10; i++) {
- System.out.println(uidGenerator.getUID()) ;
- }
- }


完畢!!!