【智能排班系统】雪花算法生成分布式ID

news2024/11/19 12:31:43

文章目录

  • 雪花算法介绍
    • 起源与命名
    • 基本原理与结构
    • 优势与特点
    • 应用场景
  • 代码实现
    • 代码结构
    • 自定义机器标识
      • RandomWorkIdChoose
      • LocalRedisWorkIdChoose
        • lua脚本
    • 实体类
      • SnowflakeIdInfo
      • WorkCenterInfo
    • 雪花算法类
    • 配置类
    • 雪花算法工具类
  • 说明

雪花算法介绍

在复杂而庞大的分布式系统中,确保数据实体的唯一标识性是一项至关重要的任务,生成全局唯一且有序的ID生成机制成为必不可少的环节。雪花算法(Snowflake Algorithm)正是为此目的而生,以其简洁的设计、高效的表现与良好的扩展性赢得了业界的广泛认可。

起源与命名

雪花算法最早由Twitter公司于2010年左右设计并应用于其内部系统,旨在解决分布式环境中大规模、高并发的唯一ID生成问题。算法得名“雪花”,源于自然界中雪花的独特性——每片雪花的形态各异,象征着生成的ID如雪花般独一无二。这一形象化的命名,恰好体现了雪花算法所生成ID的特性:每个ID在全局范围内具有唯一性,且蕴含丰富的内部结构信息。

基本原理与结构

雪花算法的核心思想是将一个64位的长整型数字划分为多个部分,每个部分代表不同维度的信息。典型的雪花ID结构如下:

  • 符号位(1位):通常为0,表示生成的ID为正数,符合大多数编程语言的长整数表示习惯。

  • 时间戳(41位):记录了ID生成时的精确时间点,通常精确到毫秒级别。这使得ID具备了天然的时间顺序,同时也为系统提供了大致的时间范围参考。

  • 数据中心标识(5位):用于区分不同的数据中心或地域,确保在多数据中心部署下ID的唯一性。

  • 机器标识(5位):标识生成ID的工作节点,可以是服务器ID、进程ID等,确保同一数据中心内不同机器生成的ID不会冲突。

  • 序列号(12位):在同一毫秒内,同一工作节点生成多个ID时,通过递增序列号来区分。序列号部分允许的最大值为4095(即每毫秒可以生成2^12个不重复ID),足以应对大部分场景下的瞬时并发需求。

这种划分方式确保了雪花ID在空间分布上既能容纳足够多的节点和并发请求,又能在时间维度上保持严格递增,从而满足全局唯一、趋势有序的需求。当然,每个部分的位数不是固定的,如果需求更复杂,可以增加相应部分的位数。例如,并发非常高,可以增加序列号的位数

优势与特点

  • 全局唯一:由于时间戳、数据中心标识、机器标识和序列号的组合具有唯一性,雪花算法能确保在分布式环境中生成的每一个ID都是全球唯一的。

  • 趋势递增:时间戳作为ID的主要部分,使得生成的ID整体上按照时间顺序排列,有利于数据库索引优化,提升查询效率

  • 高可用:在单个节点故障时,其他节点仍能继续生成ID,不会影响整个系统的运行。同时,通过合理分配数据中心和机器标识,可以轻松应对节点扩容或迁移。

  • 高效性:算法实现简单,生成ID过程几乎无锁,性能极高。并且由于ID为纯数字型,存储和传输效率高。

  • 易于解析:由于ID结构清晰,可以根据ID直接解析出其包含的时间、数据中心、机器等信息,便于日志分析、问题定位和数据归档。

应用场景

雪花算法适用于多种需要全局唯一ID的分布式场景,包括但不限于:

  • 数据库主键:作为数据库表的主键,确保每一行记录具有唯一标识,且插入顺序与生成时间相关联。

  • 消息队列:为消息系统中的消息生成唯一ID,便于消息追踪、去重和排序。

  • 分布式事务:在分布式事务中,为事务ID或操作记录分配唯一标识。

  • 分布式缓存:为缓存中的键生成唯一ID,避免键冲突。

代码实现

代码结构

在这里插入图片描述

自定义机器标识

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} 获取当前时间戳
     */
    @Value("${sss.snowflake.is-use-system-clock:false}")
    private boolean isUseSystemClock;

    /**
     * 根据自定义策略获取 WorkId 生成器
     *
     * @return
     */
    protected abstract WorkCenterInfo chooseWorkId();

    /**
     * 选择 WorkId 并初始化雪花
     */
    public void chooseAndInit() {
        // 模板方法模式: 通过调用抽象方法获取 WorkId 来创建雪花算法,抽象方法的具体实现交给子类
        WorkCenterInfo workCenterInfo = chooseWorkId();
        long workId = workCenterInfo.getWorkId();
        long dataCenterId = workCenterInfo.getDataCenterId();
        // 生成机器标识之后,初始化工具类的雪花算法静态对象
        Snowflake snowflake = new Snowflake(workId, dataCenterId, isUseSystemClock);
        log.info("Snowflake type: {}, workId: {}, dataCenterId: {}", this.getClass().getSimpleName(), workId, dataCenterId);
        SnowflakeIdUtil.initSnowflake(snowflake);
    }
}

RandomWorkIdChoose和LocalRedisWorkIdChoose主要用来实现抽象方法chooseWorkId来生成工作中心ID和数据中心ID

  • RandomWorkIdChoose:随机生成
  • LocalRedisWorkIdChoose:使用Redis的lua脚本,保证分布式部署的时候,每台机器的数据中心ID或工作中心ID不同

RandomWorkIdChoose

通过随机生成的dataCenterIdworkId很容易发生冲突,属项目没有Redis的无奈之举。但是在日常开发中,项目基本都是需要使用Redis的,所以RandomWorkIdChoose也很少会使用。

package com.dam.core.snowflake;

import com.dam.core.snowflake.entity.WorkCenterInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;

/**
 * 使用随机数获取雪花 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来记录上一台机器所申请的dataCenterIdworkId,新机器申请标识的时候,通过对已有dataCenterIdworkId进行递增从而找到没有被使用的dataCenterIdworkId组合。但是因为位数的约束,不重复数肯定有一个上限,需要根据集群大小来调整数据中心和工作中心的位数

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("执行 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脚本旨在为不同的机器生成不同的数据中心ID或者工作中心ID,避免不同机器生成冲突的ID。但是由于数据中心部分和工作中心部分都是占5 bit,所以最多生成1024个不同的【数据中心、工作中心】组合,如果集群的机器数量大于1024,就要考虑给数据中心和工作中心分配更多的位数。

-- 定义了三个本地变量:
-- hashKey:表示在Redis中存储工作ID和数据中心ID的哈希表(Hash)的键名
-- dataCenterIdKey 和 workIdKey:分别表示哈希表中存储数据中心ID和工作ID的字段名
local hashKey = 'sss:snowflake_work_id_key'
local dataCenterIdKey = 'dataCenterId'
local workIdKey = 'workId'

-- 首先,检查哈希表hashKey是否存在。
-- 如果不存在(即首次初始化),则创建该哈希表并使用hincrby命令初始化dataCenterIdKey和workIdKey字段,初始值均为0
-- 然后返回一个数组 { 0, 0 },表示当前工作ID和数据中心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

-- 若哈希表已存在,从哈希表中获取当前的dataCenterId和workId值,并将其转换为数字类型
local dataCenterId = tonumber(redis.call('hget', hashKey, dataCenterIdKey))
local workId = tonumber(redis.call('hget', hashKey, workIdKey))

-- 定义最大值常量max为31,用于判断ID是否达到上限
local max = 31
-- 定义两个局部变量resultWorkId和resultDataCenterId,用于存储最终要返回的新工作ID和数据中心ID
local resultWorkId = 0
local resultDataCenterId = 0

-- 如果两者均达到上限(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未达上限(workId ~= max),递增工作ID(hincrby),并将新的工作ID作为结果,数据中心ID保持不变
elseif (workId ~= max) then
    resultWorkId = redis.call('hincrby', hashKey, workIdKey, 1)
    resultDataCenterId = dataCenterId

-- 若只有数据中心ID未达上限(dataCenterId ~= max),递增数据中心ID,将新的数据中心ID作为结果,同时将工作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 }

实体类

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 {

    /**
     * 时间戳
     */
    @JsonSerialize(using = ToStringSerializer.class)
    private Long timestamp;

    /**
     * 工作机器节点 ID
     */
    private Integer workerId;

    /**
     * 数据中心 ID
     */
    private Integer dataCenterId;

    /**
     * 自增序号,当高频模式下时,同一毫秒内生成 N 个 ID,则这个序号在同一毫秒下,自增以避免 ID 重复
     */
    private Integer sequence;

    /**
     * 通过基因法生成的序号,会和 {@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;

    /**
     * 数据中心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 算法
 * 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
 *
 * snowflake的结构如下(每部分用-分开):
 *
 * 符号位(1bit)- 时间戳相对值(41bit)- 数据中心标志(5bit)- 机器标志(5bit)- 递增序号(12bit)
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * 
 * 第一位为未使用(符号位表示正数),接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
 * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)<br>
 * 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
 * 
 * 并且可以通过生成的id反推出生成时间,datacenterId和workerId
 * 
 * 参考:http://www.cnblogs.com/relucent/p/4955340.html<br>
 * 关于长度是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;

    /**
     * 默认的起始时间,为Thu, 04 Nov 2010 01:42:54 GMT
     */
    private static long DEFAULT_TWEPOCH = 1288834974657L;

    /**
     * 默认回拨时间,2S
     */
    private static long DEFAULT_TIME_OFFSET = 2000L;

    private static final long WORKER_ID_BITS = 5L;

    // 最大支持机器节点数0~31,一共32个
    @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
    private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);

    private static final long DATA_CENTER_ID_BITS = 5L;

    // 最大支持数据中心节点数0~31,一共32个
    @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"})
    private static final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);

    // 序列号12位(表示只允许workId的范围为:0-4095)
    private static final long SEQUENCE_BITS = 12L;

    // 机器节点左移12位
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    // 数据中心节点左移17位
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

    // 时间毫秒数左移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);

    /**
     * 初始化时间点
     */
    private final long twepoch;

    private final long workerId;

    private final long dataCenterId;

    private final boolean useSystemClock;

    /**
     * 允许的时钟回拨毫秒数
     */
    private final long timeOffset;

    /**
     * 当在低频模式下时,序号始终为0,导致生成ID始终为偶数<br>
     * 此属性用于限定一个随机上限,在不同毫秒下生成序号时,给定一个随机数,避免偶数问题。<br>
     * 注意次数必须小于{@link #SEQUENCE_MASK},{@code 0}表示不使用随机数。<br>
     * 这个上限不包括值本身。
     */
    private final long randomSequenceLimit;

    /**
     * 自增序号,当高频模式下时,同一毫秒内生成N个ID,则这个序号在同一毫秒下,自增以避免ID重复。
     */
    private long sequence = 0L;

    private long lastTimestamp = -1L;

    /**
     * 构造,使用自动生成的工作节点ID和数据中心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 数据中心ID
     */
    public Snowflake(long workerId, long dataCenterId) {
        this(workerId, dataCenterId, false);
    }

    /**
     * @param workerId         终端ID
     * @param dataCenterId     数据中心ID
     * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
     */
    public Snowflake(long workerId, long dataCenterId, boolean isUseSystemClock) {
        this(null, workerId, dataCenterId, isUseSystemClock);
    }

    /**
     * @param epochDate        初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
     * @param workerId         工作机器节点id
     * @param dataCenterId     数据中心id
     * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
     * @since 5.1.3
     */
    public Snowflake(Date epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
        this(epochDate, workerId, dataCenterId, isUseSystemClock, DEFAULT_TIME_OFFSET);
    }

    /**
     * @param epochDate        初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
     * @param workerId         工作机器节点id
     * @param dataCenterId     数据中心id
     * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
     * @param timeOffset       允许时间回拨的毫秒数
     * @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           初始化时间起点(null表示默认起始日期),后期修改会导致id重复,如果要修改连workerId dataCenterId,慎用
     * @param workerId            工作机器节点id
     * @param dataCenterId        数据中心id
     * @param isUseSystemClock    是否使用{@link SystemClock} 获取当前时间戳
     * @param timeOffset          允许时间回拨的毫秒数
     * @param randomSequenceLimit 限定一个随机上限,在不同毫秒下生成序号时,给定一个随机数,避免偶数问题,0表示无随机,上限不包括值本身。
     * @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);
    }

    /**
     * 根据Snowflake的ID,获取机器id
     *
     * @param id snowflake算法生成的id
     * @return 所属机器的id
     */
    public long getWorkerId(long id) {
        return id >> WORKER_ID_SHIFT & ~(-1L << WORKER_ID_BITS);
    }

    /**
     * 根据Snowflake的ID,获取数据中心id
     *
     * @param id snowflake算法生成的id
     * @return 所属数据中心
     */
    public long getDataCenterId(long id) {
        return id >> DATA_CENTER_ID_SHIFT & ~(-1L << DATA_CENTER_ID_BITS);
    }

    /**
     * 根据Snowflake的ID,获取生成时间
     *
     * @param id snowflake算法生成的id
     * @return 生成的时间
     */
    public long getGenerateDateTime(long id) {
        return (id >> TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
    }

    /**
     * 下一个ID
     *
     * @return ID
     */
    @Override
    public synchronized long nextId() {
        long timestamp = genTime();
        if (timestamp < this.lastTimestamp) {
            if (this.lastTimestamp - timestamp < timeOffset) {
                // 容忍指定的回拨,避免NTP校时造成的异常
                timestamp = lastTimestamp;
            } else {
                // 如果服务器时间有问题(时钟后退) 报错。
                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;
    }

    /**
     * 下一个ID(字符串形式)
     *
     * @return ID 字符串形式
     */
    @Override
    public String nextIdStr() {
        return Long.toString(nextId());
    }

    // ------------------------------------------------------------------------------------------------------------------------------------ Private method start

    /**
     * 循环等待下一个时间
     *
     * @param lastTimestamp 上次记录的时间
     * @return 下一个时间
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = genTime();
        // 循环直到操作系统时间戳变化
        while (timestamp == lastTimestamp) {
            timestamp = genTime();
        }
        if (timestamp < lastTimestamp) {
            // 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错
            throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
        }
        return timestamp;
    }

    /**
     * 生成时间戳
     *
     * @return 时间戳
     */
    private long genTime() {
        return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
    }
    
    /**
     * 解析雪花算法生成的 ID 为对象
     *
     * @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;
    }
}

配置类

根据项目是否配置Redis进而判断选择注入LocalRedisWorkIdChoose还是RandomWorkIdChoose。若项目有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 自动装配
 *
 */
@Import(ApplicationContextHolder.class)
@Configuration
public class DistributedIdAutoConfiguration {

    /**
     * 本地 Redis 构建雪花 WorkId 选择器
     */
    @Bean
    public LocalRedisWorkIdChoose redisWorkIdChoose() {
        return new LocalRedisWorkIdChoose();
    }

    /**
     * 随机数构建雪花 WorkId 选择器。如果项目未使用 Redis,使用该选择器
     */
    @Bean
    @ConditionalOnMissingBean(LocalRedisWorkIdChoose.class)
    public RandomWorkIdChoose randomWorkIdChoose() {
        return new RandomWorkIdChoose();
    }
}

雪花算法工具类

注意,SNOWFLAKE是一个静态变量,在AbstractWorkIdChooseTemplate抽象类的chooseAndInit方法中被初始化

package com.dam.toolkit;

import com.dam.core.snowflake.Snowflake;
import com.dam.core.snowflake.entity.SnowflakeIdInfo;

/**
 * 分布式雪花 ID 生成器
 *
 */
public final class SnowflakeIdUtil {

    /**
     * 雪花算法对象
     */
    private static Snowflake SNOWFLAKE;

    /**
     * 初始化雪花算法
     */
    public static void initSnowflake(Snowflake snowflake) {
        SnowflakeIdUtil.SNOWFLAKE = snowflake;
    }

    /**
     * 获取雪花算法实例
     */
    public static Snowflake getInstance() {
        return SNOWFLAKE;
    }

    /**
     * 获取雪花算法下一个 ID
     */
    public static long nextId() {
        return SNOWFLAKE.nextId();
    }

    /**
     * 获取雪花算法下一个字符串类型 ID
     */
    public static String nextIdStr() {
        return Long.toString(nextId());
    }

    /**
     * 解析雪花算法生成的 ID 为对象
     */
    public static SnowflakeIdInfo parseSnowflakeId(String snowflakeId) {
        return SNOWFLAKE.parseSnowflakeId(Long.parseLong(snowflakeId));
    }

    /**
     * 解析雪花算法生成的 ID 为对象
     */
    public static SnowflakeIdInfo parseSnowflakeId(long snowflakeId) {
        return SNOWFLAKE.parseSnowflakeId(snowflakeId);
    }

}

说明

本文代码来源于马哥 12306 的代码,本人只是根据自己的理解进行少量修改并应用到智能排班系统中。代码仓库为12306,该项目含金量较高,有兴趣的朋友们建议去学习一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力控机器人原理及力控制实现

力控机器人原理及力控制实现 力控机器人是一种能够感知力量并具有实时控制能力的机器人系统。它们可以在与人类进行精准协作和合作时&#xff0c;将力传感技术&#xff08;Force Sensing Technology&#xff09;和控制算法&#xff08;Control Algorithm&#xff09;结合起来&a…

场景文本检测识别学习 day01(传统OCR的流程、常见的损失函数)

传统OCR的流程 传统OCR&#xff1a;传统光学字符识别常见的的模型主要包括以下几个步骤来识别文本 预处理&#xff1a;预处理是指对输入的图像进行处理&#xff0c;以提高文字识别的准确率。这可能包括调整图像大小、转换为灰度图像、二值化&#xff08;将图像转换为黑白两色&…

4.1.k8s的pod-创建,数据持久化,网络暴露,env环境变量

目录 一、Pod介绍 二、指令创建和管理Pod 三、资源清单创建pod 1.挂载hostPath存储卷 2.NFS存储卷 所有节点安装nfs k8s3编辑NFS配置文件 k8s1&#xff0c;k8s2节点开机挂载 编辑pod资源清单&#xff0c;挂载nfs 四、pod网络暴露 1.hostNetwork使用宿主机的网络 2.…

Struts2的入门:新建项目——》导入jar包——》jsp,action,struts.xml,web.xml——》在项目运行

文章目录 配置环境tomcat 新建项目导入jar包新建jsp界面新建action类新建struts.xml,用来配置action文件配置Struts2的核心过滤器&#xff1a;web.xml 启动测试给一个返回界面在struts.xml中配置以实现页面的跳转&#xff1a;result再写个success.jsp最后在项目运行 配置环境 …

实现点击用户头像或者id与其用户进行聊天(vue+springboot+WebSocket)

用户点击id直接与另一位用户聊天 前端如此&#xff1a; <template><!-- 消息盒子 --><div class"content-box" :style"contentWidth"><!-- 头像&#xff0c;用户名 --><div class"content-box-top box--flex">&l…

sqlmap(四)案例

一、注入DB2 http://124.70.71.251:49431/new_list.php?id1 这是墨者学院里的靶机&#xff0c;地址&#xff1a;https://www.mozhe.cn/ 1.1 测试数据库类型 python sqlmap.py -u "http://124.70.71.251:49431/new_list.php?id1" 1.2 测试用户权限类型 查询选…

【linux】ubuntu ib网卡驱动如何适配

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

晚枫|2024年3月总结,人生中第一次「车祸」

这个3月&#xff0c;真正做到了&#xff1a;过得紧张又刺激&#xff01; 1、第一次「车祸」 整个3月&#xff0c;凌晨1点之前没睡过觉&#xff0c;大部分时间都是2点以后睡。 从去年10月和朋友一起办Python中国的活动开始&#xff0c;就逐渐进入这种状态了。 除了本职工作因…

c# wpf LiveCharts 绑定 简单试验

1.概要 c# wpf LiveCharts 绑定 简单试验 2.代码 <Window x:Class"WpfApp3.Window2"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schem…

Python3 Ubuntu

一、安装中文输入法 1.sudo apt install ibus-sunpinyin 2.点击右上角输入法&#xff0c;然后点击加号&#xff0c;输入yin添加进来&#xff0c;最后选中输入法即可 二、安装截屏软件 1.sudo apt install gnome-screenshot 三、安装opencv-python 1.pip3 install --upgrade…

CleanMyMac ----MacBook卡顿处理小妙招

MacBook 是苹果公司的一款非常流行的笔记本电脑&#xff0c;但使用时 间久了之后很容易出现卡顿的问题。那么出现卡顿的原因有哪些呢&#xff1f; MacBook卡顿怎么处理?先找出卡顿的一般原因&#xff1a; 我们需要排除 MacBook 的硬件问题。可以通过访问 Mac 系统自带的硬件检…

【C++】红黑树讲解及实现

前言&#xff1a; AVL树与红黑树相似&#xff0c;都是一种平衡二叉搜索树&#xff0c;但是AVL树的平衡要求太严格&#xff0c;如果要对AVL树做一些结构修改的操作性能会非常低下&#xff0c;比如&#xff1a;插入时要维护其绝对平衡&#xff0c;旋转的次数比较多&#xff0c;更…

pyside6怎么使用Qt Designer设计自定义组件

第一步&#xff0c;新建一个自定义组件的python文件 from PySide6.QtWidgets import QPlainTextEdit from PySide6.QtCore import Signal,Qtclass CustomPlainTextEdit(QPlainTextEdit):enterPressed Signal(str)def __init__(self, parentNone):super().__init__(parent)def…

EChart简单入门

echart的安装就细不讲了&#xff0c;直接去官网下&#xff0c;实在不会的直接用cdn,省的一番口舌。 cdn.staticfile.net/echarts/4.3.0/echarts.min.js 正入话题哈 什么是EChart&#xff1f; EChart 是一个使用 JavaScript 实现的开源可视化库&#xff0c;Echart支持多种常…

【环境变量】常见的环境变量 | 相关指令 | 环境变量系统程序的结合理解

目录 常见的环境变量 HOME PWD SHELL HISTSIZE 环境变量相关的指令 echo&env export unset 本地变量 环境变量整体理解 程序现象_代码查看环境变量 整体理解 环境变量表 环境变量表的传递 环境变量表的查看 测试验证 少说废话&#x1f197; 每个用户…

前端canvas项目实战——在线图文编辑器(八):复制、删除、锁定、层叠顺序

目录 前言一、效果展示二、实现步骤1. 复制2. 删除3. 锁定4. 层叠顺序 三、实现过程中发现的bug1. clone方法不复制自定义属性2. 复制「锁定」状态的对象&#xff0c;得到的新对象也是「锁定」状态 四、Show u the code后记 前言 上一篇博文中&#xff0c;我们细致的讲解了实现…

高精度端到端在线校准环视相机和LIDAR(精度0.2度内!无需训练数据)

高精度端到端在线校准环视相机和LIDAR&#xff08;精度0.2度内&#xff01;无需训练数据&#xff09; 附赠自动驾驶学习资料和量产经验&#xff1a;链接 写在前面 在自动驾驶车辆的使用寿命内&#xff0c;传感器外参校准会因振动、温度和碰撞等环境因素而发生变化。即使是看似…

智过网:非安全专业能否报考注安?哪些专业可以报考?

近年来&#xff0c;随着社会对安全生产管理的日益重视&#xff0c;注册安全工程师&#xff08;简称注安&#xff09;这一职业逐渐受到广大从业人员的青睐。然而&#xff0c;对于许多非安全专业的朋友来说&#xff0c;他们可能会困惑&#xff1a;非安全专业是否可以报考注安&…

微软推出GPT-4 Turbo优先使用权:Copilot for Microsoft 365商业用户享受无限制对话及增强图像生成能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

thinkphp6入门(22)-- 如何下载文件

假设在public/uploads文件夹下有一个文件test.xlsx 在前端页面添加下载链接&#xff0c;用户点击该链接即可下载对应的文件。 <a href"xxxxxxx/downloadFile">下载文件</a> 2. 在后端控制器方法中&#xff0c;我们需要获取要下载的文件路径&#xff0…