Java使用雪花id生成算法詳解
什么是雪花算法
雪花算法的本質(zhì)為生成一個(gè)64位長(zhǎng)度的具有自增性的分布式全局唯一id。在64bits中,會(huì)對(duì)不同段的位進(jìn)行劃分??煞譃椋?/p>
- 符號(hào)段
- 時(shí)間戳段
- 機(jī)器碼段(data center + worker)
- 自增序列號(hào)段
位段詳解
- 第一位 : 符號(hào)位,正數(shù)為0。
- [2, 42] : 41位時(shí)間戳位,表明id的生成時(shí)間點(diǎn)(完整時(shí)間戳: 起始時(shí)間戳 + 41位時(shí)間戳)。41位最多能表示的時(shí)間為: (2^41-1) / (1000 * 60 * 60 * 24 * 365) 約等為69.73年。
- [43, 47] : 5位data center id。data center id + worker id 共10位,最多能表示1024個(gè)機(jī)器。不同機(jī)器保證機(jī)器碼段的位值不同即可。
- [48, 52] : 5位worker id。data center id + worker id 共10位,最多能表示1024個(gè)機(jī)器。不同機(jī)器保證機(jī)器碼段的位值不同即可。
- [53, 64] : 12位自增序列號(hào),用于區(qū)分同一毫秒內(nèi)生成的id。序列號(hào)范圍: [0, 2^12-1],最多有2^12個(gè),即4096個(gè)。
優(yōu)點(diǎn)
- 算法簡(jiǎn)單,基于內(nèi)存,生成效率高
- 支持分布式環(huán)境下的多節(jié)點(diǎn)服務(wù)(機(jī)器碼段),秒內(nèi)可生成百萬(wàn)個(gè)唯一id
- 基于時(shí)間戳 與 同時(shí)間戳下自增序列號(hào),生成的id具有自增性
- 具有業(yè)務(wù)定制性,根據(jù)業(yè)務(wù)的不同可以對(duì)不同段的位數(shù)進(jìn)行變更。比如業(yè)務(wù)持續(xù)時(shí)長(zhǎng)不會(huì)那么久,就可以將時(shí)間戳段減少位數(shù),補(bǔ)充給自增序列段,使每一毫秒能生成更多的id。
問(wèn)題
依賴服務(wù)器時(shí)間。若服務(wù)器時(shí)鐘回?fù)埽赡軙?huì)導(dǎo)致生成的id重復(fù)。可在代碼中新增lastTimeMillis字段,在獲取nextId時(shí)根據(jù)系統(tǒng)當(dāng)前時(shí)間進(jìn)行判斷解決。
但若不進(jìn)行持久化處理,服務(wù)重啟后發(fā)生時(shí)鐘回?fù)芤琅f會(huì)出現(xiàn)重復(fù)問(wèn)題。
實(shí)際應(yīng)用
- mybatis plus:使用雪花算法生成id:@TableId(value = “id”, type = IdType.ID_WORKER)。id字段若不指定類型,默認(rèn)使用雪花算法生成id
- Hutool工具包:IdUtil.createSnowflake(workerId, datacenterId);
具體實(shí)現(xiàn)
/**
* Created by QQ.Cong on 2022-07-22 / 9:48
*
* @author: CongQingquan
* @Description: Snowflake util
*/
public class SnowflakeUtils {
// ============================== Basic field ==============================//
// Datacenter id
private long datacenterId;
// Worker id
private long workerId;
// Increment sequence
private long sequence;
// ============================== Bits ==============================//
// Bits of datacenter id
private long datacenterIdBits;
// Bits of worker id
private long workerIdBits;
// Bits of sequence
private long sequenceBits;
// ============================== Largest ==============================//
// Largest datacenter id
private long largestDatacenterId;
// Largest worker id
private long largestWorkerId;
// Largest sequence
private long largestSequence;
// ============================== Shift ==============================//
// Left shift num of worker id
private long workerIdShift;
// Left shift num of datacenter id
private long datacenterIdShift;
// Left shift num of timestamp
private long timestampShift;
// ============================== Other ==============================//
// Epoch
private long epoch;
// The timestamp that last get snowflake id
private long lastTimestamp;
// ============================== End ==============================//
public SnowflakeUtils(long dataCenterId, long workerId) {
// Default epoch: 2022-07-22 00:00:00
this(1658419200000L, -1L, dataCenterId, workerId, 5L, 5L, 5L);
}
public SnowflakeUtils(long epoch, long lastTimestamp, long datacenterId, long workerId,
long datacenterIdBits, long workerIdBits, long sequenceBits) {
this.epoch = epoch;
this.lastTimestamp = lastTimestamp;
this.datacenterId = datacenterId;
this.workerId = workerId;
this.sequence = 0L;
this.datacenterIdBits = datacenterIdBits;
this.workerIdBits = workerIdBits;
this.sequenceBits = sequenceBits;
this.largestDatacenterId = ~(-1L << datacenterIdBits);
this.largestWorkerId = ~(-1L << workerIdBits);
this.largestSequence = ~(-1L << sequenceBits);
if (datacenterId > largestDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(
String.format("The datacenter id param can't be greater than %s or less than 0",
largestDatacenterId));
}
if (workerId > largestWorkerId || workerId < 0) {
throw new IllegalArgumentException(
String.format("The worker id param can't be greater than %s or less than 0",
largestWorkerId));
}
this.workerIdShift = sequenceBits;
this.datacenterIdShift = workerIdShift + workerIdBits;
this.timestampShift = datacenterIdShift + datacenterIdBits;
}
/**
* Get snowflake id
* @return
*/
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
// 若時(shí)鐘回退
if (timestamp < lastTimestamp) {
throw new RuntimeException(
"System clock moved backward, cannot to generate snowflake id");
}
// 若當(dāng)前毫秒內(nèi)多次生成雪花id
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & largestSequence;
// 序列溢出
if (sequence == 0) {
timestamp = waitUntilNextMilli(timestamp);
}
}
// 若當(dāng)前毫秒內(nèi)首次生成雪花id
else {
sequence = 0L;
}
// 更新獲取雪花id的時(shí)間戳
lastTimestamp = timestamp;
// 生成雪花id (通過(guò)位或運(yùn)算符進(jìn)行拼接)
return ((timestamp - epoch) << timestampShift) // 時(shí)間戳段
| (datacenterId << datacenterIdShift) // 機(jī)器碼段
| (workerId << workerIdShift) // 機(jī)器碼段
| sequence; // 自增序列段
}
/**
* Wait until next millisecond
* @param lastTimestamp
* @return
*/
private long waitUntilNextMilli(long lastTimestamp) {
long currentTimeMillis;
do {
currentTimeMillis = System.currentTimeMillis();
}
while (currentTimeMillis <= lastTimestamp);
return currentTimeMillis;
}
/**
* Get util instance
* @param dataCenterId
* @param workerId
* @return
*/
public static SnowflakeUtils getInstance(long dataCenterId, long workerId) {
return new SnowflakeUtils(dataCenterId, workerId);
}
}到此這篇關(guān)于Java使用雪花id生成算法詳解的文章就介紹到這了,更多相關(guān)Java雪花id生成算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+MyBatis-Plus+Velocity實(shí)現(xiàn)代碼自動(dòng)生成
本文主要介紹了使用SpringBoot、MyBatis-Plus和Velocity模板引擎實(shí)現(xiàn)代碼自動(dòng)生成器,該生成器能夠根據(jù)數(shù)據(jù)庫(kù)表結(jié)構(gòu)自動(dòng)生成增刪改查操作的代碼,感興趣的可以了解一下2025-03-03
java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例
本文主要介紹了java通過(guò)信號(hào)量實(shí)現(xiàn)限流的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Java設(shè)計(jì)模式之java責(zé)任鏈模式詳解
這篇文章主要介紹了JAVA 責(zé)任鏈模式的的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2021-09-09
Java中字符序列的替換與分解的幾種實(shí)現(xiàn)方法
本文主要介紹了Java中字符序列的替換與分解的幾種實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
簡(jiǎn)析Java中的util.concurrent.Future接口
這篇文章主要介紹了簡(jiǎn)析Java中的util.concurrent.Future接口,作者把future歸結(jié)為在未來(lái)得到目標(biāo)對(duì)象的占位符,需要的朋友可以參考下2015-07-07
Java技巧函數(shù)方法實(shí)現(xiàn)二維數(shù)組遍歷
這篇文章主要介紹了Java技巧函數(shù)方法實(shí)現(xiàn)二維數(shù)組遍歷,二維數(shù)組遍歷,每個(gè)元素判斷下是否為偶數(shù),相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-08-08
Java中的信號(hào)量Semaphore詳細(xì)解讀
這篇文章主要介紹了Java中的信號(hào)量Semaphore詳細(xì)解讀,Java信號(hào)量機(jī)制可以用來(lái)保證線程互斥,創(chuàng)建Semaphore對(duì)象傳入一個(gè)整形參數(shù),類似于公共資源,需要的朋友可以參考下2023-11-11
MyBatis驗(yàn)證多級(jí)緩存及 Cache Aside 模式的應(yīng)用小結(jié)
本文介紹了MyBatis的多級(jí)緩存機(jī)制,包括本地緩存和全局緩存,并通過(guò)Spock測(cè)試框架驗(yàn)證了多級(jí)緩存的實(shí)現(xiàn),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-12-12

