国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【智能排班系統(tǒng)】雪花算法生成分布式ID

這篇具有很好參考價(jià)值的文章主要介紹了【智能排班系統(tǒng)】雪花算法生成分布式ID。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

雪花算法介紹

在復(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)

【智能排班系統(tǒng)】雪花算法生成分布式ID,# 智能排班系統(tǒng),算法,分布式,雪花算法

自定義機(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ī)生成的dataCenterIdworkId很容易發(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)的dataCenterIdworkId,新機(jī)器申請(qǐng)標(biāo)識(shí)的時(shí)候,通過對(duì)已有dataCenterIdworkId進(jìn)行遞增從而找到?jīng)]有被使用的dataCenterIdworkId組合。但是因?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方法中被初始化

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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 分布式唯一ID生成算法——雪花算法(SnowFlake)

    分布式唯一ID生成算法——雪花算法(SnowFlake)

    SnowFlake算法 據(jù)國(guó)家大氣研究中心的查爾斯·奈特稱,一般的雪花大約由10^19個(gè)水分子組成。在雪花形成過程中,會(huì)形成不同的結(jié)構(gòu)分支,所以說大自然中不存在兩片完全一樣的雪花,每一片雪花都擁有自己漂亮獨(dú)特的形狀。 雪花算法表示生成的id如雪花般獨(dú)一無二。 snowflake是

    2023年04月20日
    瀏覽(26)
  • 分布式Id生成之雪花算法(SnowFlake)

    分布式Id生成之雪花算法(SnowFlake)

    目錄 前言 回顧二進(jìn)制 二進(jìn)制概念 運(yùn)算法則 位(Bit) 字節(jié)(Byte) 字符 字符集 二進(jìn)制原碼、反碼、補(bǔ)碼 有符號(hào)數(shù)和無符號(hào)數(shù) 疑問:為什么不是-127 ~ 127 ? 為什么需要分布式全局唯一ID以及分布式ID得業(yè)務(wù)需求? ID生成規(guī)則部分硬性要求 ID生成系統(tǒng)的可用性要求 通用解決方

    2024年02月11日
    瀏覽(25)
  • 分布式ID(2):雪花算法生成ID

    分布式ID(2):雪花算法生成ID

    1 雪花算法簡(jiǎn)介 這種方案大致來說是一種以劃分命名空間(UUID也算,由于比較常見,所以單獨(dú)分析)來生成ID的一種算法,這種方案把64-bit分別劃分成多段,分開來標(biāo)示機(jī)器、時(shí)間等,比如在snowflake中的64-bit分別表示如下圖(圖片來自網(wǎng)絡(luò))所示: 41-bit的時(shí)間可以表示(1L

    2024年01月20日
    瀏覽(30)
  • 【Java筆記】分布式id生成-雪花算法

    【Java筆記】分布式id生成-雪花算法

    隨著業(yè)務(wù)的增長(zhǎng),有些表可能要占用很大的物理存儲(chǔ)空間,為了解決該問題,后期使用數(shù)據(jù)庫分片技術(shù)。將一個(gè)數(shù)據(jù)庫進(jìn)行拆分,通過數(shù)據(jù)庫中間件連接。如果數(shù)據(jù)庫中該表選用ID自增策略,則可能產(chǎn)生重復(fù)的ID,此時(shí)應(yīng)該使用分布式ID生成策略來生成ID。 snowflake是Twitter開源的

    2024年02月11日
    瀏覽(22)
  • 雪花算法,在分布式環(huán)境下實(shí)現(xiàn)高效的ID生成

    其實(shí)雪花算法比較簡(jiǎn)單,可能稱不上什么算法,就是一種構(gòu)造UID的方法。 點(diǎn)1:UID是一個(gè)long類型的41位時(shí)間戳,10位存儲(chǔ)機(jī)器碼,12位存儲(chǔ)序列號(hào)。 點(diǎn)2:時(shí)間戳的單位是毫秒,可以同時(shí)鏈接1024臺(tái)機(jī)器,每臺(tái)機(jī)器每毫秒可以使用4096個(gè)序列號(hào),我們會(huì)給生成id上一個(gè)同步鎖,阻塞

    2024年02月15日
    瀏覽(29)
  • 對(duì)于現(xiàn)有的分布式id發(fā)號(hào)器的思考 id生成器 雪花算法 uuid

    目錄 雪花id tinyid uuid 分布式id特點(diǎn) 業(yè)務(wù)編號(hào) 數(shù)據(jù)中心編號(hào) 當(dāng)前時(shí)間 ip地址 當(dāng)前序號(hào) 對(duì)于時(shí)鐘回?fù)軉栴} 發(fā)號(hào)器機(jī)器當(dāng)期時(shí)間小于redis的時(shí)間 解決步驟 發(fā)號(hào)器機(jī)器當(dāng)期時(shí)間等于redis時(shí)間 發(fā)號(hào)器機(jī)器當(dāng)期時(shí)間大于redis最大的時(shí)間(相關(guān)的key不存在) 分布式id的單次獲取和批次獲

    2024年02月13日
    瀏覽(17)
  • 分布式唯一ID 雪花算法

    分布式唯一ID 雪花算法

    ?????????個(gè)人主頁:五敷有你? ? ?? ???系列專欄:算法分析與設(shè)計(jì) ??穩(wěn)中求進(jìn),曬太陽 雪花算法是 64 位 的二進(jìn)制,一共包含了四部分: 1位是符號(hào)位,也就是最高位,始終是0,沒有任何意義,因?yàn)橐俏ㄒ挥?jì)算機(jī)二進(jìn)制補(bǔ)碼中就是負(fù)數(shù),0才是正數(shù)。 41位是時(shí)間戳

    2024年04月10日
    瀏覽(24)
  • 分布式id解決方法--雪花算法

    分布式id解決方法--雪花算法

    uuid,jdk自帶,但是數(shù)據(jù)庫性能差,32位呀。 mysql數(shù)據(jù)庫主鍵越短越好,B+tree產(chǎn)生節(jié)點(diǎn)分裂,大大降低數(shù)據(jù)庫性能,所以u(píng)uid不建議。 redis的自增,但是要配置維護(hù)redis集群,就為了一個(gè)id,還要引入一套redis。費(fèi)事,成本高。 如果有序自增別人就知道你的業(yè)務(wù)量多少了。 分布式

    2024年02月04日
    瀏覽(23)
  • C# 分布式自增ID算法snowflake(雪花算法)

    C# 分布式自增ID算法snowflake(雪花算法)

    分布式系統(tǒng)中,有一些需要使用全局唯一 ID 的場(chǎng)景,這種時(shí)候?yàn)榱朔乐?ID 沖突可以使用36位的 UUID ,但是 UUID 有一些缺點(diǎn),首先他相對(duì)比較長(zhǎng),另外 UUID 一般是無序的。有些時(shí)候我們希望能使用一種簡(jiǎn)單一些的 ID ,并且希望 ID 能夠按照時(shí)間有序生成。而 Twitter 的 snowflake 解

    2024年04月10日
    瀏覽(24)
  • JAVA實(shí)用工具: 改良版雪花算法-分布式唯一ID神器

    JAVA實(shí)用工具: 改良版雪花算法-分布式唯一ID神器

    Seata內(nèi)置了一個(gè)分布式UUID生成器,用于輔助生成全局事務(wù)ID和分支事務(wù)ID。具體如下特點(diǎn): 高性能 全局唯一 趨勢(shì)遞增 這個(gè)分布式UUID生成器是基于雪花算法進(jìn)行改良的,本文針對(duì)改良的方法、目的等進(jìn)行總結(jié) 改良版雪花算法的實(shí)現(xiàn)原理參考如下: Seata基于改良版雪花算法的分

    2024年02月14日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包