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

Snowflake算法描述:指定機(jī)器 & 同一時(shí)刻 & 某一并發(fā)序列,是唯一的。據(jù)此可生成一個(gè)64 bits的唯一ID(long)。默認(rèn)采用上圖字節(jié)分配方式:
- sign(1bit) 固定1bit符號標(biāo)識,即生成的UID為正數(shù)。
- delta seconds (28 bits) 當(dāng)前時(shí)間,相對于時(shí)間基點(diǎn)"2016-05-20"的增量值,單位:秒,最多可支持約8.7年
- worker id (22 bits) 機(jī)器id,最多可支持約420w次機(jī)器啟動(dòng)。內(nèi)置實(shí)現(xiàn)為在啟動(dòng)時(shí)由數(shù)據(jù)庫分配,默認(rèn)分配策略為用后即棄,后續(xù)可提供復(fù)用策略。
- sequence (13 bits) 每秒下的并發(fā)序列,13 bits可支持每秒8192個(gè)并發(fā)。
以上參數(shù)均可通過Spring進(jìn)行自定義
CachedUidGenerator
RingBuffer環(huán)形數(shù)組,數(shù)組每個(gè)元素成為一個(gè)slot。RingBuffer容量,默認(rèn)為Snowflake算法中sequence最大值,且為2^N。可通過boostPower配置進(jìn)行擴(kuò)容,以提高RingBuffer 讀寫吞吐量。
Tail指針、Cursor指針用于環(huán)形數(shù)組上讀寫slot:
- Tail指針 表示Producer生產(chǎn)的最大序號(此序號從0開始,持續(xù)遞增)。Tail不能超過Cursor,即生產(chǎn)者不能覆蓋未消費(fèi)的slot。當(dāng)Tail已趕上curosr,此時(shí)可通過rejectedPutBufferHandler指定PutRejectPolicy
- Cursor指針 表示Consumer消費(fèi)到的最小序號(序號序列與Producer序列相同)。Cursor不能超過Tail,即不能消費(fèi)未生產(chǎn)的slot。當(dāng)Cursor已趕上tail,此時(shí)可通過rejectedTakeBufferHandler指定TakeRejectPolicy

CachedUidGenerator采用了雙RingBuffer,Uid-RingBuffer用于存儲Uid、Flag-RingBuffer用于存儲Uid狀態(tài)(是否可填充、是否可消費(fèi))
由于數(shù)組元素在內(nèi)存中是連續(xù)分配的,可最大程度利用CPU cache以提升性能。但同時(shí)會帶來「偽共享」FalseSharing問題,為此在Tail、Cursor指針、Flag-RingBuffer中采用了CacheLine 補(bǔ)齊方式。

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

UIDGenerator環(huán)境配置
創(chuàng)建表
- 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;
將其項(xiàng)目中的mapper.xml文件copy到自己項(xiàng)目中,對應(yīng)的WorkerNodeEntity,WorkerNodeDAO,
DisposableWorkerIdAssigner都copy到自己的項(xiàng)目中。
DisposableWorkerIdAssigner主要是修改注入的dao,因?yàn)檫@里我們需要修改默認(rèn)的dao相關(guān)的配置。
項(xiàng)目配置
依賴
- <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及對應(yīng)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到自己項(xiàng)目中的,不是百度提供的。
- @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()) ;
- }
- }


完畢!!!