雪花算法介紹
在復(fù)雜而龐大的分布式系統(tǒng)中,確保數(shù)據(jù)實(shí)體的唯一標(biāo)識(shí)性是一項(xiàng)至關(guān)重要的任務(wù),生成全局唯一且有序的ID生成機(jī)制成為必不可少的環(huán)節(jié)。雪花算法(Snowflake Algorithm)正是為此目的而生,以其簡(jiǎn)潔的設(shè)計(jì)、高效的表現(xiàn)與良好的擴(kuò)展性贏得了業(yè)界的廣泛認(rèn)可。
起源與命名
雪花算法最早由Twitter公司于2010年左右設(shè)計(jì)并應(yīng)用于其內(nèi)部系統(tǒng),旨在解決分布式環(huán)境中大規(guī)模、高并發(fā)的唯一ID生成問題。算法得名“雪花”,源于自然界中雪花的獨(dú)特性——每片雪花的形態(tài)各異,象征著生成的ID如雪花般獨(dú)一無二。這一形象化的命名,恰好體現(xiàn)了雪花算法所生成ID的特性:每個(gè)ID在全局范圍內(nèi)具有唯一性,且蘊(yùn)含豐富的內(nèi)部結(jié)構(gòu)信息。
基本原理與結(jié)構(gòu)
雪花算法的核心思想是將一個(gè)64位的長(zhǎng)整型數(shù)字劃分為多個(gè)部分,每個(gè)部分代表不同維度的信息。典型的雪花ID結(jié)構(gòu)如下:
-
符號(hào)位(1位):通常為0,表示生成的ID為正數(shù),符合大多數(shù)編程語言的長(zhǎng)整數(shù)表示習(xí)慣。
-
時(shí)間戳(41位):記錄了ID生成時(shí)的精確時(shí)間點(diǎn),通常精確到毫秒級(jí)別。這使得ID具備了天然的時(shí)間順序,同時(shí)也為系統(tǒng)提供了大致的時(shí)間范圍參考。
-
數(shù)據(jù)中心標(biāo)識(shí)(5位):用于區(qū)分不同的數(shù)據(jù)中心或地域,確保在多數(shù)據(jù)中心部署下ID的唯一性。
-
機(jī)器標(biāo)識(shí)(5位):標(biāo)識(shí)生成ID的工作節(jié)點(diǎn),可以是服務(wù)器ID、進(jìn)程ID等,確保同一數(shù)據(jù)中心內(nèi)不同機(jī)器生成的ID不會(huì)沖突。
-
序列號(hào)(12位):在同一毫秒內(nèi),同一工作節(jié)點(diǎn)生成多個(gè)ID時(shí),通過遞增序列號(hào)來區(qū)分。序列號(hào)部分允許的最大值為4095(即每毫秒可以生成2^12個(gè)不重復(fù)ID),足以應(yīng)對(duì)大部分場(chǎng)景下的瞬時(shí)并發(fā)需求。
這種劃分方式確保了雪花ID在空間分布上既能容納足夠多的節(jié)點(diǎn)和并發(fā)請(qǐng)求,又能在時(shí)間維度上保持嚴(yán)格遞增,從而滿足全局唯一、趨勢(shì)有序的需求。當(dāng)然,每個(gè)部分的位數(shù)不是固定的,如果需求更復(fù)雜,可以增加相應(yīng)部分的位數(shù)。例如,并發(fā)非常高,可以增加序列號(hào)的位數(shù)。
優(yōu)勢(shì)與特點(diǎn)
-
全局唯一
:由于時(shí)間戳、數(shù)據(jù)中心標(biāo)識(shí)、機(jī)器標(biāo)識(shí)和序列號(hào)的組合具有唯一性,雪花算法能確保在分布式環(huán)境中生成的每一個(gè)ID都是全球唯一的。 -
趨勢(shì)遞增
:時(shí)間戳作為ID的主要部分,使得生成的ID整體上按照時(shí)間順序排列,有利于數(shù)據(jù)庫索引優(yōu)化,提升查詢效率。 -
高可用
:在單個(gè)節(jié)點(diǎn)故障時(shí),其他節(jié)點(diǎn)仍能繼續(xù)生成ID,不會(huì)影響整個(gè)系統(tǒng)的運(yùn)行。同時(shí),通過合理分配數(shù)據(jù)中心和機(jī)器標(biāo)識(shí),可以輕松應(yīng)對(duì)節(jié)點(diǎn)擴(kuò)容或遷移。 -
高效性
:算法實(shí)現(xiàn)簡(jiǎn)單,生成ID過程幾乎無鎖,性能極高。并且由于ID為純數(shù)字型,存儲(chǔ)和傳輸效率高。 -
易于解析
:由于ID結(jié)構(gòu)清晰,可以根據(jù)ID直接解析出其包含的時(shí)間、數(shù)據(jù)中心、機(jī)器等信息,便于日志分析、問題定位和數(shù)據(jù)歸檔。
應(yīng)用場(chǎng)景
雪花算法適用于多種需要全局唯一ID的分布式場(chǎng)景,包括但不限于:
-
數(shù)據(jù)庫主鍵:作為數(shù)據(jù)庫表的主鍵,確保每一行記錄具有唯一標(biāo)識(shí),且插入順序與生成時(shí)間相關(guān)聯(lián)。
-
消息隊(duì)列:為消息系統(tǒng)中的消息生成唯一ID,便于消息追蹤、去重和排序。
-
分布式事務(wù):在分布式事務(wù)中,為事務(wù)ID或操作記錄分配唯一標(biāo)識(shí)。
-
分布式緩存:為緩存中的鍵生成唯一ID,避免鍵沖突。
代碼實(shí)現(xiàn)
代碼結(jié)構(gòu)
自定義機(jī)器標(biāo)識(shí)
package com.dam.core.snowflake;
import cn.hutool.core.date.SystemClock;
import com.dam.core.snowflake.entity.WorkCenterInfo;
import lombok.extern.slf4j.Slf4j;
import com.dam.toolkit.SnowflakeIdUtil;
import org.springframework.beans.factory.annotation.Value;
/**
* 雪花算法模板生成
*
*/
@Slf4j
public abstract class AbstractWorkIdChooseTemplate {
/**
* 是否使用 {@link SystemClock} 獲取當(dāng)前時(shí)間戳
*/
@Value("${sss.snowflake.is-use-system-clock:false}")
private boolean isUseSystemClock;
/**
* 根據(jù)自定義策略獲取 WorkId 生成器
*
* @return
*/
protected abstract WorkCenterInfo chooseWorkId();
/**
* 選擇 WorkId 并初始化雪花
*/
public void chooseAndInit() {
// 模板方法模式: 通過調(diào)用抽象方法獲取 WorkId 來創(chuàng)建雪花算法,抽象方法的具體實(shí)現(xiàn)交給子類
WorkCenterInfo workCenterInfo = chooseWorkId();
long workId = workCenterInfo.getWorkId();
long dataCenterId = workCenterInfo.getDataCenterId();
// 生成機(jī)器標(biāo)識(shí)之后,初始化工具類的雪花算法靜態(tài)對(duì)象
Snowflake snowflake = new Snowflake(workId, dataCenterId, isUseSystemClock);
log.info("Snowflake type: {}, workId: {}, dataCenterId: {}", this.getClass().getSimpleName(), workId, dataCenterId);
SnowflakeIdUtil.initSnowflake(snowflake);
}
}
RandomWorkIdChoose和LocalRedisWorkIdChoose主要用來實(shí)現(xiàn)抽象方法chooseWorkId來生成工作中心ID和數(shù)據(jù)中心ID
- RandomWorkIdChoose:隨機(jī)生成
- LocalRedisWorkIdChoose:使用Redis的lua腳本,保證分布式部署的時(shí)候,每臺(tái)機(jī)器的數(shù)據(jù)中心ID或工作中心ID不同
RandomWorkIdChoose
通過隨機(jī)生成的dataCenterId
和workId
很容易發(fā)生沖突,屬項(xiàng)目沒有Redis的無奈之舉。但是在日常開發(fā)中,項(xiàng)目基本都是需要使用Redis的,所以RandomWorkIdChoose也很少會(huì)使用。
package com.dam.core.snowflake;
import com.dam.core.snowflake.entity.WorkCenterInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
/**
* 使用隨機(jī)數(shù)獲取雪花 WorkId
*/
@Slf4j
public class RandomWorkIdChoose extends AbstractWorkIdChooseTemplate implements InitializingBean {
@Override
protected WorkCenterInfo chooseWorkId() {
int start = 0, end = 31;
return new WorkCenterInfo(getRandom(start, end), getRandom(start, end));
}
@Override
public void afterPropertiesSet() throws Exception {
chooseAndInit();
}
private static long getRandom(int start, int end) {
long random = (long) (Math.random() * (end - start + 1) + start);
return random;
}
}
LocalRedisWorkIdChoose
通過使用Redis來記錄上一臺(tái)機(jī)器所申請(qǐng)的dataCenterId
和workId
,新機(jī)器申請(qǐng)標(biāo)識(shí)的時(shí)候,通過對(duì)已有dataCenterId
或workId
進(jìn)行遞增從而找到?jīng)]有被使用的dataCenterId
和workId
組合。但是因?yàn)槲粩?shù)的約束,不重復(fù)數(shù)肯定有一個(gè)上限,需要根據(jù)集群大小來調(diào)整數(shù)據(jù)中心和工作中心的位數(shù)
package com.dam.core.snowflake;
import cn.hutool.core.collection.CollUtil;
import com.dam.ApplicationContextHolder;
import com.dam.core.snowflake.entity.WorkCenterInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import java.util.ArrayList;
import java.util.List;
/**
* 使用 Redis 獲取雪花 WorkId
*/
@Slf4j
public class LocalRedisWorkIdChoose extends AbstractWorkIdChooseTemplate implements InitializingBean {
private RedisTemplate stringRedisTemplate;
public LocalRedisWorkIdChoose() {
System.out.println("執(zhí)行 LocalRedisWorkIdChoose -----------------------");
StringRedisTemplate bean = ApplicationContextHolder.getBean(StringRedisTemplate.class);
// System.out.println("bean = " + bean);
this.stringRedisTemplate = bean;
}
@Override
public WorkCenterInfo chooseWorkId() {
DefaultRedisScript redisScript = new DefaultRedisScript();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/chooseWorkIdLua.lua")));
List<Long> luaResultList = null;
try {
redisScript.setResultType(List.class);
luaResultList = (ArrayList) this.stringRedisTemplate.execute(redisScript, null);
} catch (Exception ex) {
log.error("Redis Lua 腳本獲取 WorkId 失敗", ex);
}
return CollUtil.isNotEmpty(luaResultList) ? new WorkCenterInfo(luaResultList.get(0), luaResultList.get(1)) : new RandomWorkIdChoose().chooseWorkId();
}
@Override
public void afterPropertiesSet() throws Exception {
chooseAndInit();
}
}
lua腳本
lua腳本旨在為不同的機(jī)器生成不同的數(shù)據(jù)中心ID或者工作中心ID,避免不同機(jī)器生成沖突的ID。但是由于數(shù)據(jù)中心部分和工作中心部分都是占5 bit,所以最多生成1024個(gè)不同的【數(shù)據(jù)中心、工作中心】組合,如果集群的機(jī)器數(shù)量大于1024,就要考慮給數(shù)據(jù)中心和工作中心分配更多的位數(shù)。
-- 定義了三個(gè)本地變量:
-- hashKey:表示在Redis中存儲(chǔ)工作ID和數(shù)據(jù)中心ID的哈希表(Hash)的鍵名
-- dataCenterIdKey 和 workIdKey:分別表示哈希表中存儲(chǔ)數(shù)據(jù)中心ID和工作ID的字段名
local hashKey = 'sss:snowflake_work_id_key'
local dataCenterIdKey = 'dataCenterId'
local workIdKey = 'workId'
-- 首先,檢查哈希表hashKey是否存在。
-- 如果不存在(即首次初始化),則創(chuàng)建該哈希表并使用hincrby命令初始化dataCenterIdKey和workIdKey字段,初始值均為0
-- 然后返回一個(gè)數(shù)組 { 0, 0 },表示當(dāng)前工作ID和數(shù)據(jù)中心ID均為0
if (redis.call('exists', hashKey) == 0) then
redis.call('hincrby', hashKey, dataCenterIdKey, 0)
redis.call('hincrby', hashKey, workIdKey, 0)
return { 0, 0 }
end
-- 若哈希表已存在,從哈希表中獲取當(dāng)前的dataCenterId和workId值,并將其轉(zhuǎn)換為數(shù)字類型
local dataCenterId = tonumber(redis.call('hget', hashKey, dataCenterIdKey))
local workId = tonumber(redis.call('hget', hashKey, workIdKey))
-- 定義最大值常量max為31,用于判斷ID是否達(dá)到上限
local max = 31
-- 定義兩個(gè)局部變量resultWorkId和resultDataCenterId,用于存儲(chǔ)最終要返回的新工作ID和數(shù)據(jù)中心ID
local resultWorkId = 0
local resultDataCenterId = 0
-- 如果兩者均達(dá)到上限(dataCenterId == max且workId == max),將它們重置為0
if (dataCenterId == max and workId == max) then
redis.call('hset', hashKey, dataCenterIdKey, '0')
redis.call('hset', hashKey, workIdKey, '0')
-- 若只有工作ID未達(dá)上限(workId ~= max),遞增工作ID(hincrby),并將新的工作ID作為結(jié)果,數(shù)據(jù)中心ID保持不變
elseif (workId ~= max) then
resultWorkId = redis.call('hincrby', hashKey, workIdKey, 1)
resultDataCenterId = dataCenterId
-- 若只有數(shù)據(jù)中心ID未達(dá)上限(dataCenterId ~= max),遞增數(shù)據(jù)中心ID,將新的數(shù)據(jù)中心ID作為結(jié)果,同時(shí)將工作ID重置為0
elseif (dataCenterId ~= max) then
resultWorkId = 0
resultDataCenterId = redis.call('hincrby', hashKey, dataCenterIdKey, 1)
redis.call('hset', hashKey, workIdKey, '0')
end
return { resultWorkId, resultDataCenterId }
實(shí)體類
SnowflakeIdInfo
package com.dam.core.snowflake.entity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 雪花算法組成部分,通常用來反解析使用
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SnowflakeIdInfo {
/**
* 時(shí)間戳
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long timestamp;
/**
* 工作機(jī)器節(jié)點(diǎn) ID
*/
private Integer workerId;
/**
* 數(shù)據(jù)中心 ID
*/
private Integer dataCenterId;
/**
* 自增序號(hào),當(dāng)高頻模式下時(shí),同一毫秒內(nèi)生成 N 個(gè) ID,則這個(gè)序號(hào)在同一毫秒下,自增以避免 ID 重復(fù)
*/
private Integer sequence;
/**
* 通過基因法生成的序號(hào),會(huì)和 {@link SnowflakeIdInfo#sequence} 共占 12 bit
*/
private Integer gene;
}
WorkCenterInfo
package com.dam.core.snowflake.entity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* WorkId 包裝器
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WorkCenterInfo {
/**
* 工作ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long workId;
/**
* 數(shù)據(jù)中心ID
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long dataCenterId;
}
雪花算法類
package com.dam.core.snowflake;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.dam.core.IdGenerator;
import com.dam.core.snowflake.entity.SnowflakeIdInfo;
import java.io.Serializable;
import java.util.Date;
/**
* 雪花算法真正生成ID的類
*
* Twitter的Snowflake 算法
* 分布式系統(tǒng)中,有一些需要使用全局唯一ID的場(chǎng)景,有些時(shí)候我們希望能使用一種簡(jiǎn)單一些的ID,并且希望ID能夠按照時(shí)間有序生成。
*
* snowflake的結(jié)構(gòu)如下(每部分用-分開):
*
* 符號(hào)位(1bit)- 時(shí)間戳相對(duì)值(41bit)- 數(shù)據(jù)中心標(biāo)志(5bit)- 機(jī)器標(biāo)志(5bit)- 遞增序號(hào)(12bit)
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
*
* 第一位為未使用(符號(hào)位表示正數(shù)),接下來的41位為毫秒級(jí)時(shí)間(41位的長(zhǎng)度可以使用69年)<br>
* 然后是5位datacenterId和5位workerId(10位的長(zhǎng)度最多支持部署1024個(gè)節(jié)點(diǎn))<br>
* 最后12位是毫秒內(nèi)的計(jì)數(shù)(12位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒產(chǎn)生4096個(gè)ID序號(hào))
*
* 并且可以通過生成的id反推出生成時(shí)間,datacenterId和workerId
*
* 參考:http://www.cnblogs.com/relucent/p/4955340.html<br>
* 關(guān)于長(zhǎng)度是18還是19的問題見:https://blog.csdn.net/unifirst/article/details/80408050
*
* @author Looly
* @since 3.0.1
*/
public class Snowflake implements Serializable, IdGenerator {
private static final long serialVersionUID = 1L;
/**
* 默認(rèn)的起始時(shí)間,為Thu, 04 Nov 2010 01:42:54 GMT
*/
private static long DEFAULT_TWEPOCH = 1288834974657L;
/**
* 默認(rèn)回?fù)軙r(shí)間,2S
*/
private static long DEFAULT_TIME_OFFSET = 2000L;
private static final long WORKER_ID_BITS = 5L;
// 最大支持機(jī)器節(jié)點(diǎn)數(shù)0~31,一共32個(gè)
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);
private static final long DATA_CENTER_ID_BITS = 5L;
// 最大支持?jǐn)?shù)據(jù)中心節(jié)點(diǎn)數(shù)0~31,一共32個(gè)
@SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
// 序列號(hào)12位(表示只允許workId的范圍為:0-4095)
private static final long SEQUENCE_BITS = 12L;
// 機(jī)器節(jié)點(diǎn)左移12位
private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
// 數(shù)據(jù)中心節(jié)點(diǎn)左移17位
private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
// 時(shí)間毫秒數(shù)左移22位
private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
// 序列掩碼,用于限定序列最大值不能超過4095
private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
/**
* 初始化時(shí)間點(diǎn)
*/
private final long twepoch;
private final long workerId;
private final long dataCenterId;
private final boolean useSystemClock;
/**
* 允許的時(shí)鐘回?fù)芎撩霐?shù)
*/
private final long timeOffset;
/**
* 當(dāng)在低頻模式下時(shí),序號(hào)始終為0,導(dǎo)致生成ID始終為偶數(shù)<br>
* 此屬性用于限定一個(gè)隨機(jī)上限,在不同毫秒下生成序號(hào)時(shí),給定一個(gè)隨機(jī)數(shù),避免偶數(shù)問題。<br>
* 注意次數(shù)必須小于{@link #SEQUENCE_MASK},{@code 0}表示不使用隨機(jī)數(shù)。<br>
* 這個(gè)上限不包括值本身。
*/
private final long randomSequenceLimit;
/**
* 自增序號(hào),當(dāng)高頻模式下時(shí),同一毫秒內(nèi)生成N個(gè)ID,則這個(gè)序號(hào)在同一毫秒下,自增以避免ID重復(fù)。
*/
private long sequence = 0L;
private long lastTimestamp = -1L;
/**
* 構(gòu)造,使用自動(dòng)生成的工作節(jié)點(diǎn)ID和數(shù)據(jù)中心ID
*/
public Snowflake() {
this(IdUtil.getWorkerId(IdUtil.getDataCenterId(MAX_DATA_CENTER_ID), MAX_WORKER_ID));
}
/**
* @param workerId 終端ID
*/
public Snowflake(long workerId) {
this(workerId, IdUtil.getDataCenterId(MAX_DATA_CENTER_ID));
}
/**
* @param workerId 終端ID
* @param dataCenterId 數(shù)據(jù)中心ID
*/
public Snowflake(long workerId, long dataCenterId) {
this(workerId, dataCenterId, false);
}
/**
* @param workerId 終端ID
* @param dataCenterId 數(shù)據(jù)中心ID
* @param isUseSystemClock 是否使用{@link SystemClock} 獲取當(dāng)前時(shí)間戳
*/
public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
this(null, workerId, dataCenterId, isUseSystemClock);
}
/**
* @param epochDate 初始化時(shí)間起點(diǎn)(null表示默認(rèn)起始日期),后期修改會(huì)導(dǎo)致id重復(fù),如果要修改連workerId dataCenterId,慎用
* @param workerId 工作機(jī)器節(jié)點(diǎn)id
* @param dataCenterId 數(shù)據(jù)中心id
* @param isUseSystemClock 是否使用{@link SystemClock} 獲取當(dāng)前時(shí)間戳
* @since 5.1.3
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
}
/**
* @param epochDate 初始化時(shí)間起點(diǎn)(null表示默認(rèn)起始日期),后期修改會(huì)導(dǎo)致id重復(fù),如果要修改連workerId dataCenterId,慎用
* @param workerId 工作機(jī)器節(jié)點(diǎn)id
* @param dataCenterId 數(shù)據(jù)中心id
* @param isUseSystemClock 是否使用{@link SystemClock} 獲取當(dāng)前時(shí)間戳
* @param timeOffset 允許時(shí)間回?fù)艿暮撩霐?shù)
* @since 5.8.0
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset) {
this(epochDate, workerId, dataCenterId, isUseSystemClock, timeOffset, 0);
}
/**
* @param epochDate 初始化時(shí)間起點(diǎn)(null表示默認(rèn)起始日期),后期修改會(huì)導(dǎo)致id重復(fù),如果要修改連workerId dataCenterId,慎用
* @param workerId 工作機(jī)器節(jié)點(diǎn)id
* @param dataCenterId 數(shù)據(jù)中心id
* @param isUseSystemClock 是否使用{@link SystemClock} 獲取當(dāng)前時(shí)間戳
* @param timeOffset 允許時(shí)間回?fù)艿暮撩霐?shù)
* @param randomSequenceLimit 限定一個(gè)隨機(jī)上限,在不同毫秒下生成序號(hào)時(shí),給定一個(gè)隨機(jī)數(shù),避免偶數(shù)問題,0表示無隨機(jī),上限不包括值本身。
* @since 5.8.0
*/
public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock, long timeOffset, long randomSequenceLimit) {
this.twepoch = (null != epochDate) ? epochDate.getTime() : DEFAULT_TWEPOCH;
this.workerId = Assert.checkBetween(workerId, 0, MAX_WORKER_ID);
this.dataCenterId = Assert.checkBetween(dataCenterId, 0, MAX_DATA_CENTER_ID);
this.useSystemClock = isUseSystemClock;
this.timeOffset = timeOffset;
this.randomSequenceLimit = Assert.checkBetween(randomSequenceLimit, 0, SEQUENCE_MASK);
}
/**
* 根據(jù)Snowflake的ID,獲取機(jī)器id
*
* @param id snowflake算法生成的id
* @return 所屬機(jī)器的id
*/
public long getWorkerId(long id) {
return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS);
}
/**
* 根據(jù)Snowflake的ID,獲取數(shù)據(jù)中心id
*
* @param id snowflake算法生成的id
* @return 所屬數(shù)據(jù)中心
*/
public long getDataCenterId(long id) {
return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS);
}
/**
* 根據(jù)Snowflake的ID,獲取生成時(shí)間
*
* @param id snowflake算法生成的id
* @return 生成的時(shí)間
*/
public long getGenerateDateTime(long id) {
return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
}
/**
* 下一個(gè)ID
*
* @return ID
*/
@Override
public synchronized long nextId() {
long timestamp = genTime();
if (timestamp < this.lastTimestamp) {
if (this.lastTimestamp - timestamp < timeOffset) {
// 容忍指定的回?fù)?,避免NTP校時(shí)造成的異常
timestamp = lastTimestamp;
} else {
// 如果服務(wù)器時(shí)間有問題(時(shí)鐘后退) 報(bào)錯(cuò)。
throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
}
}
if (timestamp == this.lastTimestamp) {
final long sequence = (this.sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
this.sequence = sequence;
} else {
// issue#I51EJY
if (randomSequenceLimit > 1) {
sequence = RandomUtil.randomLong(randomSequenceLimit);
} else {
sequence = 0L;
}
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT) | (dataCenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
}
/**
* 下一個(gè)ID(字符串形式)
*
* @return ID 字符串形式
*/
@Override
public String nextIdStr() {
return Long.toString(nextId());
}
// ------------------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 循環(huán)等待下一個(gè)時(shí)間
*
* @param lastTimestamp 上次記錄的時(shí)間
* @return 下一個(gè)時(shí)間
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = genTime();
// 循環(huán)直到操作系統(tǒng)時(shí)間戳變化
while (timestamp == lastTimestamp) {
timestamp = genTime();
}
if (timestamp < lastTimestamp) {
// 如果發(fā)現(xiàn)新的時(shí)間戳比上次記錄的時(shí)間戳數(shù)值小,說明操作系統(tǒng)時(shí)間發(fā)生了倒退,報(bào)錯(cuò)
throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
}
return timestamp;
}
/**
* 生成時(shí)間戳
*
* @return 時(shí)間戳
*/
private long genTime() {
return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
}
/**
* 解析雪花算法生成的 ID 為對(duì)象
*
* @param snowflakeId 雪花算法 ID
* @return
*/
public SnowflakeIdInfo parseSnowflakeId(long snowflakeId) {
SnowflakeIdInfo snowflakeIdInfo = SnowflakeIdInfo.builder().sequence((int) (snowflakeId & ~(-1L << SEQUENCE_BITS))).workerId((int) ((snowflakeId >> WORKER_ID_SHIFT)
& ~(-1L << WORKER_ID_BITS))).dataCenterId((int) ((snowflakeId >> DATA_CENTER_ID_SHIFT)
& ~(-1L << DATA_CENTER_ID_BITS)))
.timestamp((snowflakeId >> TIMESTAMP_LEFT_SHIFT) + twepoch).build();
return snowflakeIdInfo;
}
}
配置類
根據(jù)項(xiàng)目是否配置Redis進(jìn)而判斷選擇注入LocalRedisWorkIdChoose
還是RandomWorkIdChoose
。若項(xiàng)目有Redis,則注入LocalRedisWorkIdChoose
,反之,注入RandomWorkIdChoose
package com.dam.config;
import com.dam.ApplicationContextHolder;
import com.dam.core.snowflake.LocalRedisWorkIdChoose;
import com.dam.core.snowflake.RandomWorkIdChoose;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 分布式 ID 自動(dòng)裝配
*
*/
@Import(ApplicationContextHolder.class)
@Configuration
public class DistributedIdAutoConfiguration {
/**
* 本地 Redis 構(gòu)建雪花 WorkId 選擇器
*/
@Bean
public LocalRedisWorkIdChoose redisWorkIdChoose() {
return new LocalRedisWorkIdChoose();
}
/**
* 隨機(jī)數(shù)構(gòu)建雪花 WorkId 選擇器。如果項(xiàng)目未使用 Redis,使用該選擇器
*/
@Bean
@ConditionalOnMissingBean(LocalRedisWorkIdChoose.class)
public RandomWorkIdChoose randomWorkIdChoose() {
return new RandomWorkIdChoose();
}
}
雪花算法工具類
注意,SNOWFLAKE是一個(gè)靜態(tài)變量,在AbstractWorkIdChooseTemplate抽象類的chooseAndInit方法中被初始化文章來源:http://www.zghlxwxcb.cn/news/detail-846516.html
package com.dam.toolkit;
import com.dam.core.snowflake.Snowflake;
import com.dam.core.snowflake.entity.SnowflakeIdInfo;
/**
* 分布式雪花 ID 生成器
*
*/
public final class SnowflakeIdUtil {
/**
* 雪花算法對(duì)象
*/
private static Snowflake SNOWFLAKE;
/**
* 初始化雪花算法
*/
public static void initSnowflake(Snowflake snowflake) {
SnowflakeIdUtil.SNOWFLAKE = snowflake;
}
/**
* 獲取雪花算法實(shí)例
*/
public static Snowflake getInstance() {
return SNOWFLAKE;
}
/**
* 獲取雪花算法下一個(gè) ID
*/
public static long nextId() {
return SNOWFLAKE.nextId();
}
/**
* 獲取雪花算法下一個(gè)字符串類型 ID
*/
public static String nextIdStr() {
return Long.toString(nextId());
}
/**
* 解析雪花算法生成的 ID 為對(duì)象
*/
public static SnowflakeIdInfo parseSnowflakeId(String snowflakeId) {
return SNOWFLAKE.parseSnowflakeId(Long.parseLong(snowflakeId));
}
/**
* 解析雪花算法生成的 ID 為對(duì)象
*/
public static SnowflakeIdInfo parseSnowflakeId(long snowflakeId) {
return SNOWFLAKE.parseSnowflakeId(snowflakeId);
}
}
說明
本文代碼來源于馬哥 12306 的代碼,本人只是根據(jù)自己的理解進(jìn)行少量修改并應(yīng)用到智能排班系統(tǒng)中。代碼倉庫為12306,該項(xiàng)目含金量較高,有興趣的朋友們建議去學(xué)習(xí)一下。文章來源地址http://www.zghlxwxcb.cn/news/detail-846516.html
到了這里,關(guān)于【智能排班系統(tǒng)】雪花算法生成分布式ID的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!