分布式環(huán)境不同于單機系統(tǒng),對ID生成有著更為嚴(yán)苛的需求,具體如下:
- 全局唯一,這是基本要求,不能出現(xiàn)重復(fù)。
- 單調(diào)遞增,連續(xù)的,下一個ID要大于上一個ID,這主要是從MySQL InnoDB存儲引擎的性能來考慮的。
- 長度,長度越短需要的存儲空間越小,同時還能夠提高查詢效率,使用MySQL時尤為突出。
- 高可用,無單點隱患
- 高性能,生成速度快,延時低,扛住高并發(fā)
常用的分布式ID生成方案
UUID
優(yōu)點: 開發(fā)語言本身提供實現(xiàn),足夠簡單;全球唯一;無性能問題
缺點:長度過大,不利于存儲和檢索;非單調(diào)遞增,對MySQL索引不利(作為數(shù)據(jù)庫主鍵,在InnoDB引擎下,UUID的無序性可能會引起數(shù)據(jù)位置頻繁變動,嚴(yán)重影響性能)
數(shù)據(jù)庫自增主鍵
基于數(shù)據(jù)庫的自增主角,單獨使用一個數(shù)據(jù)庫實例作全局ID生成器。
優(yōu)點:實現(xiàn)簡單;單調(diào)遞增;數(shù)值類型,長度合適,查詢速度快
缺點:強依賴數(shù)據(jù),存在單點隱患;存在性能問題,無法抗住高并發(fā)
數(shù)據(jù)庫多實例自增主鍵
每個數(shù)據(jù)庫設(shè)置固定的step增長步長,使得每個數(shù)據(jù)庫生成的主鍵單調(diào)遞增且不重復(fù),如:DB1生成1、4、7、10;DB2生成2、5、8、11;DB3生成3、6、9、12
優(yōu)點:無單點隱患;平衡負載
缺點:需固定步長,擴容困難;單庫壓力依然大;應(yīng)用較為復(fù)雜
類Snowflake算法
使用twitter開源的Snowflake算法,其構(gòu)造如下:

優(yōu)點:高性能(每秒生成百萬ID);單調(diào)遞增
缺點:強依賴機器時鐘,存在時鐘回撥問題(會導(dǎo)致重復(fù)的ID生成)
uid-generator
uid-generator是由百度開源的基于Snowflake算法的唯一ID生成器,使用java語言實現(xiàn)。uid-generator以組件形式工作在應(yīng)用項目中, 支持自定義workerId位數(shù)和初始化策略, 從而適用于docker等虛擬化環(huán)境下實例自動重啟、漂移等場景。 在實現(xiàn)上, uid-generator通過借用未來時間來解決sequence天然存在的并發(fā)限制; 采用RingBuffer來緩存已生成的UID, 并行化UID的生產(chǎn)和消費, 同時對CacheLine補齊,避免了由RingBuffer帶來的硬件級「偽共享」問題. 最終單機QPS可達600萬。
uid-generator項目詳情: 請點擊
uid-generator對Snowflake算法生成的ID構(gòu)造做了調(diào)整,如下:

worker node id 為每個工作節(jié)點的ID(機器、應(yīng)用實例),uid-generator提供接口可由用戶自行實現(xiàn)其生成方式,默認(rèn)是基于數(shù)據(jù)庫生成。
uid-generator解決時間回撥問題、提升性能主要是通過如下技術(shù)手段實現(xiàn):
1、動態(tài)遞增worker node id : 每次啟動都會往數(shù)據(jù)庫WORKER_NODE表中插入一條記錄,插入成功后返回的該數(shù)據(jù)對應(yīng)的自增唯一主鍵,此主鍵就作為該應(yīng)用實例的worker node id 。保證每個應(yīng)用實例、每次啟動所獲取的worker node id 都不同,因此不會出現(xiàn)生成重復(fù)的ID。即使時鐘回撥,因為workerId不同,也不會出現(xiàn)ID沖突
2、RingBuffer: RingBuffer本質(zhì)是一個數(shù)組,uid-generator利用RingBuffer數(shù)據(jù)結(jié)構(gòu)預(yù)先生成若干個ID并緩存,當(dāng)需要獲取ID時候,如果數(shù)組中有則優(yōu)先使用緩存的ID,這樣可極大提高效率與吞吐量
3、未來時間:大部分snowflake算法的實現(xiàn)都會使用System.currentTimeMillis()來獲取時間戳,這樣嚴(yán)重依賴服務(wù)器的時間。uid-generator使用填充完RingBuffer時的時間戳作為lastSecond(AtomicLong類型),下次填充時使用lastSecond.incrementAndGet()來獲取新的時間戳,非使用System.currentTimeMillis(),規(guī)避了時鐘回撥問題。
uid-generator-starter
從官網(wǎng)說明或者其他網(wǎng)上的使用教程可見,將uid-generator集成到springboot項目中,還是有點小麻煩的。uid-generator-starter對uid-generator進行了Springboot Starter風(fēng)格的封裝,只要一行注解便可將其集成到項目中,同時還增加一些實用的特性:
spring-boot-starter風(fēng)格的開箱即用。
可為uid-generator獨立設(shè)置數(shù)據(jù)源,和業(yè)務(wù)系統(tǒng)的主數(shù)據(jù)源分開。
支持使用ZooKeeper進行WORKER ID分配,藉由ZK的Paxos強一致性算法獲取更高的可用性。
開源地址
github:uid-generator-starter
如果此工具對你有幫助,請在github中Star支持下
快速開始
1、引入uid-generator-starter
<dependency>
<groupId>com.github</groupId>
<artifactId>uid-generator-starter</artifactId>
<version>最新的版本號</version>
</dependency>
2、在數(shù)據(jù)庫(mysql)中創(chuàng)建WORKER_NODE表
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;
3、注解啟用uid-generator
@Transactional
@EnableUidGenerator //啟用uid-generator
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4、使用UidGenerator
@Resource
private UidGenerator uidGenerator;
@Test
public void contextLoads() {
for(int i=0;i<100;i++) {
System.out.println("uid:"+uidGenerator.getUID());
}
}
使用獨立的數(shù)據(jù)源
在數(shù)據(jù)庫uid-db中創(chuàng)建WORKER_NODE表,使用其作為uid-generator的專用數(shù)據(jù)庫
每個業(yè)務(wù)系統(tǒng)只需將uid-generator的數(shù)據(jù)庫設(shè)置為uid-db即可
#---------------------- 業(yè)務(wù)配置 -----------------------
spring:
datasource: #業(yè)務(wù)數(shù)據(jù)源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/yewu1
password: admin
username: 123456
#---------------------- uid-generator -----------------------
uid-generator:
#time-bits: 28 #可選配置, 如未指定將采用默認(rèn)值
#worker-bits: 22 #可選配置, 如未指定將采用默認(rèn)值
#seq-bits: 13 #可選配置, 如未指定將采用默認(rèn)值
#epoch-str: 2020-10-21 #可選配置, 如未指定將采用默認(rèn)值(2020-10-21)
#boost-power: 3 #可選配置, 如未指定將采用默認(rèn)值
#padding-factor: 50 #可選配置, 如未指定將采用默認(rèn)值
#schedule-interval: #可選配置, 如未指定則不啟用此功能
datasource: #使用獨立的數(shù)據(jù)源,如未指定將采用應(yīng)用系統(tǒng)的數(shù)據(jù)源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.666:3306/uid-db
password: admin
username: 123456
使用zookeeper
作為一個專門為分布式應(yīng)用提供一致性服務(wù)的軟件,使用zookeeper作為workerId的配置維護工具再合適不過了,如果你的系統(tǒng)追求高度可用性,強烈推薦使用zookeeper集群。
#---------------------- 業(yè)務(wù)配置 -----------------------
spring:
datasource: #業(yè)務(wù)數(shù)據(jù)源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/yewu?
password: admin
username: 123456
#---------------------- uid-generator -----------------------
uid-generator:
#time-bits: 28 #可選配置, 如未指定將采用默認(rèn)值
#worker-bits: 22 #可選配置, 如未指定將采用默認(rèn)值
#seq-bits: 13 #可選配置, 如未指定將采用默認(rèn)值
#epoch-str: 2016-05-20 #可選配置, 如未指定將采用默認(rèn)值
#boost-power: 3 #可選配置, 如未指定將采用默認(rèn)值
#padding-factor: 50 #可選配置, 如未指定將采用默認(rèn)值
#schedule-interval: #可選配置, 如未指定則不啟用此功能
#datasource: #使用獨立的數(shù)據(jù)源,如未指定將采用應(yīng)用系統(tǒng)的數(shù)據(jù)源
#driver-class-name: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://192.168.1.666:3306/uid-db
#password: root
#username: root
zookeeper:
#zk連接地址,集群模式則用逗號分開,如: 192.168.1.333:2181,192.168.1.555:2182,192.168.1.66:2183
addrs: 192.168.1.333:2181
#authentication: admin:123456 #digest類型的訪問秘鑰,如:user:password,默認(rèn)為不使用秘鑰