当前位置: 首页 > news >正文

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

文章目录

  • 雪花算法介绍
    • 起源与命名
    • 基本原理与结构
    • 优势与特点
    • 应用场景
  • 代码实现
    • 代码结构
    • 自定义机器标识
      • 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 {@Overrideprotected WorkCenterInfo chooseWorkId() {int start = 0, end = 31;return new WorkCenterInfo(getRandom(start, end), getRandom(start, end));}@Overridepublic 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;}@Overridepublic 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();}@Overridepublic 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) thenredis.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) thenredis.call('hset', hashKey, dataCenterIdKey, '0')redis.call('hset', hashKey, workIdKey, '0')-- 若只有工作ID未达上限(workId ~= max),递增工作ID(hincrby),并将新的工作ID作为结果,数据中心ID保持不变
elseif (workId ~= max) thenresultWorkId = redis.call('hincrby', hashKey, workIdKey, 1)resultDataCenterId = dataCenterId-- 若只有数据中心ID未达上限(dataCenterId ~= max),递增数据中心ID,将新的数据中心ID作为结果,同时将工作ID重置为0
elseif (dataCenterId ~= max) thenresultWorkId = 0resultDataCenterId = redis.call('hincrby', hashKey, dataCenterIdKey, 1)redis.call('hset', hashKey, workIdKey, '0')
endreturn { 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;// 序列掩码,用于限定序列最大值不能超过4095private 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*/@Overridepublic 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#I51EJYif (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 字符串形式*/@Overridepublic 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 选择器*/@Beanpublic 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,该项目含金量较高,有兴趣的朋友们建议去学习一下。

相关文章:

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

文章目录 雪花算法介绍起源与命名基本原理与结构优势与特点应用场景 代码实现代码结构自定义机器标识RandomWorkIdChooseLocalRedisWorkIdChooselua脚本 实体类SnowflakeIdInfoWorkCenterInfo 雪花算法类配置类雪花算法工具类 说明 雪花算法介绍 在复杂而庞大的分布式系统中&a…...

sass中的导入与部分导入

文章目录 sass中的导入与部分导入1. import&#xff1a;传统的导入方式2. use&#xff1a;现代化的模块化导入 sass中的导入与部分导入 在大型前端项目中&#xff0c;CSS代码量往往十分庞大&#xff0c;为了保持其可读性、可维护性以及便于团队协作&#xff0c;模块化开发成为…...

工业组态 物联网组态 组态编辑器 web组态 组态插件 编辑器

体验地址&#xff1a;by组态[web组态插件] BY组态是一款非常优秀的纯前端的【web组态插件工具】&#xff0c;可无缝嵌入到vue项目&#xff0c;react项目等&#xff0c;由于是原生js开发&#xff0c;对于前端的集成没有框架的限制。同时由于BY组态只是一个插件&#xff0c;不能独…...

git可视化工具

Gitkraken GitKraken 是一款专门用于管理和协作Git仓库的图形化界面工具。它拥有友好直观的界面&#xff0c;使得Git的操作变得更加简单易用&#xff0c;尤其适合那些不熟悉Git命令行的开发者。GitKraken提供了丰富的功能&#xff0c;如代码审查、分支管理、仓库克隆、提交、推…...

基于单片机电子密码锁系统设计

**单片机设计介绍&#xff0c;基于单片机电子密码锁系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机电子密码锁系统设计概要主要包括以下几个方面&#xff1a; 一、系统概述 基于单片机电子密码锁系统是一个…...

点云从入门到精通技术详解100篇-基于点云与图像纹理的 道路识别(续)

目录 3.1.2 图像滤波去噪 3.2 道路纹理特征提取 3.3 基于超像素分割的图像特征表达...

《机器学习在量化投资中的应用研究》目录

机器学习在量化投资中的应用研究 获取链接&#xff1a;机器学习在量化投资中的应用研究_汤凌冰著_北京&#xff1a;电子工业出版社 更多技术书籍&#xff1a;技术书籍分享&#xff0c;前端、后端、大数据、AI、人工智能... 内容简介 《机器学习在量化投资中的应用研究…...

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭&#xff0c;从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口&#xff0c;这个接口只有两个方法start和stop两个方法&#xff0c;但是这个接口并不是直接提供给开发者做拓展点&#xff0c;而…...

深入理解Java匿名内部类(day21)

在Java编程中&#xff0c;匿名内部类是一种非常有用的特性&#xff0c;它允许我们定义和实例化一个类的子类或实现一个接口&#xff0c;而无需给出子类的名称。这种特性使得代码更加简洁、紧凑&#xff0c;尤其适用于一些只使用一次的临时对象。本文将深入探讨Java匿名内部类的…...

《状态模式(极简c++)》

本文章属于专栏- 概述 - 《设计模式&#xff08;极简c版&#xff09;》-CSDN博客 模式说明&#xff1a; 方案&#xff1a;状态模式是一种行为设计模式&#xff0c;用于在对象的内部状态发生改变时改变其行为。它包括三个关键角色&#xff1a;上下文&#xff08;Context&#x…...

Day4-Hive直播行业基础笔试题

Hive笔试题实战 短视频 题目一&#xff1a;计算各个视频的平均完播率 有用户-视频互动表tb_user_video_log&#xff1a; id uid video_id start_time end_time if_follow if_like if_retweet comment_id 1 101 2001 2021-10-01 10:00:00 2021-10-01 10:00:30 …...

mybatis批量新增数据

数据量大的时候如果在循环中执行单条新增操作&#xff0c;是非常慢的。那么如何在mybatis中实现批量新增数据呢&#xff1f; 方法 insert 标签的 foreach 属性可以用于批量插入数据。您可以使用 foreach 属性遍历一个集合&#xff0c;并为集合中的每个元素生成一条插入语句。…...

webrtcP2P通话流程

文章目录 webrtcP2P通话流程webrtc多对多 mesh方案webrtc多对多 mcu方案webrtc多对多 sfu方案webrtc案例测试getUserMediagetUserMedia基础示例-打开摄像头getUserMedia canvas - 截图 打开共享屏幕 webrtcP2P通话流程 在这里&#xff0c;stun服务器包括stun服务和turn转发服…...

游戏引擎中的物理系统

一、物理对象与形状 1.1 对象 Actor 一般来说&#xff0c;游戏中的对象&#xff08;Actor&#xff09;分为以下四类&#xff1a; 静态对象 Static Actor动态对象 Dynamic Actor ---- 可能受到力/扭矩/冲量的影响检测器 TriggerKinematic Actor 运动学对象 ---- 忽略物理法则…...

【C++ STL有序关联容器】map 映射

文章目录 【 1. 基本原理 】【 2. map 的创建 】2.1 调用默认构造函数&#xff0c;创建一个空的 map2.2 map 被构造的同时初始化2.3 通过一个 queue 初始化另一个 queue2.4 取已建 map 中指定区域内的键值对&#xff0c;初始化新的 map2.5 指定排序规则 【 2. map 元素的操作 】…...

【ZZULIOJ】1041: 数列求和2(Java)

目录 题目描述 输入 输出 样例输入 Copy 样例输出 Copy code 题目描述 输入一个整数n&#xff0c;输出数列1-1/31/5-……前n项的和。 输入 输入只有一个整数n。 输出 结果保留2为小数,单独占一行。 样例输入 Copy 3 样例输出 Copy 0.87 code import java.util…...

C++【适配器模式】

简单介绍 适配器模式是一种结构型设计模式 | 它能使接口不兼容的对象能够相互合作。&#xff08;是适配各种不同接口的一个中间件&#xff09; 基础理解 举个例子&#xff1a;当你引用了一个第三方数据分析库&#xff0c;但这个库的接口只能兼容JSON 格式的数据。但你需要它…...

go | 上传文件分析 | http协议分析 | 使用openssl 实现 https 协议 server.key、server.pem

是这样的&#xff0c;现在分析抓包数据 test.go package mainimport ("fmt""log""github.com/gin-gonic/gin" )func main() {r : gin.Default()// Upload single filer.MaxMultipartMemory 8 << 20r.POST("/upload", func(c *g…...

Chatgpt掘金之旅—有爱AI商业实战篇|专业博客|(六)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、AI技术创业博客领域有哪些机会&#xff1f; 人工智能&#xff08;AI&#xff09;技术作为当今科技创新的前沿领域&#xff0c;为创业者提供了广阔的机会和挑战。随着AI技…...

单例模式 JAVA

单例模式 什么是单例模式&#xff1f; 1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供这一实例。 应用&#xff1a;数据库的连接类&#xff0c;这样就可以确保只创建一次。节省资源。 单例模式代码&#xff1a;涉及懒加载…...

C++从入门到精通——初步认识面向对象及类的引入

初步认识面向对象及类的引入 前言一、面向过程和面向对象初步认识C语言C 二、类的引入C的类名代表什么示例 C与C语言的struct的比较成员函数访问权限继承默认构造函数默认成员初始化结构体大小 总结 前言 面向过程注重任务的流程和控制&#xff0c;适合简单任务和流程固定的场…...

GitHub入门与实践

ISBN: 978-7-115-39409-5 作者&#xff1a;【日】大塚弘记 译者&#xff1a;支鹏浩、刘斌 页数&#xff1a;255页 阅读时间&#xff1a;2023-08-05 推荐指数&#xff1a;★★★★★ 好久之前读完的了&#xff0c;一直没有写笔记。 这本入门Git的书籍还是非常推荐的&#xff0c;…...

centos 安装 stable-diffusion 详细流程

一、安装git 新版 先安装git 工具来更新git源码 &#xff0c; 载下源码后卸载git 版本(centos 默认1.8版本&#xff0c;说是安装会引起失败) 安装git 命令&#xff0c;可使用 git --version查看版本 sudo yum install git -y 卸载git命令 sudo yum remove git 正式源码安装…...

CSS编写登录框样式

/* 重置浏览器默认样式 */ * { margin: 0; padding: 0; box-sizing: border-box; } /* 设置登录框的基本样式 */ .login-box { width: 100%; max-width: 400px; margin: 50px auto; background-color: #f4f4f4; padding: 20px; box-shad…...

Python|OpenCV-获取鼠标点击位置的坐标,并绘制图像(13)

前言 本文是该专栏的第14篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 本文主要来详细说明,基于OpenCV来获取鼠标点击位置的坐标,并按坐标的位置进行自动绘制图像。具体怎么实现,笔者在正文中将结合实际代码案例进行详细说明。 具体细节部分以及完整代码的实…...

设计模式(14):命令模式

介绍 将一个请求封装为一个对象&#xff0c;从而使我们可用不同的请求对象客户进行参数化&#xff1b;对请求排队或者记录请求日志&#xff0c;以及支持可撤销的操作。也称之为&#xff1a;动作Action模式&#xff0c;事务transaction模式。 命令模式角色 抽象命令类(Comman…...

关于阿里云云数据库自动扩缩容和自动SQL优化的20道面试题

1. 请解释阿里云云数据库自动扩缩容的概念及其工作原理。 阿里云云数据库自动扩缩容是一种基于数据库实例的实时性能数据&#xff0c;能够发现流量异常并提供合理的数据库规格建议和磁盘容量建议的功能。其工作原理如下&#xff1a; 性能监控&#xff1a;系统会实时监控数据库…...

mkcert生成ssl证书+nginx部署局域网内的https服务访问问题

文章目录 mkcert生成ssl证书nginx部署局域网内的https服务访问问题1、下载mkcert查看自己的电脑是arm还是amd架构 2、安装mkcert3、测试mkcert是否安装成功4、查看CA证书存放位置5、打开windows的证书控制台6、生成自签证书,可供局域网内使用其他主机访问以下是nginx部署https服…...

PTA C 1050 螺旋矩阵(思路与优化)

本题要求将给定的 N 个正整数按非递增的顺序&#xff0c;填入“螺旋矩阵”。所谓“螺旋矩阵”&#xff0c;是指从左上角第 1 个格子开始&#xff0c;按顺时针螺旋方向填充。要求矩阵的规模为 m 行 n 列&#xff0c;满足条件&#xff1a;mn 等于 N&#xff1b;m≥n&#xff1b;且…...

神经网络分类和回归任务实战

学习方法&#xff1a;torch 边用边学&#xff0c;边查边学 真正用查的过程才是学习的过程 直接上案例&#xff0c;先来跑&#xff0c;遇到什么解决什么 数据集Minist 数据集 做简单的任务 Minist 分类任务 总体代码&#xff08;可以跑通&#xff09; from pathlib import …...

大气绿色网站模板/绍兴seo推广公司

问题描述&#xff1a; 之前使用电脑管家满30min后&#xff0c;qq会自动加速0.2天&#xff0c;现在不加速了如下图&#xff1a; 解决办法&#xff1a;由于版本问题现在需要手动加速。 1、进入到个人中心&#xff1a;登陆电脑管家后&#xff0c;点击左上角自己的头像进入即可&am…...

网站建设流程案例/拓客团队怎么联系

我在github上写的文章&#xff0c;复制过来格式太乱&#xff0c;还是直接用链接吧。 http://sunmh207.github.io/2016/02/01/recommendcourses/...

wordpress木马乐主题/广州seo实战培训

一、一般思路&#xff1a; 1、原问题分解为子问题 2、确定状态 3、确定一些初始状态&#xff08;边界&#xff09;的值 4、确定状态转移方程。 二、问题特点&#xff1a; 1、问题有最优子结构 2、无后效性 三、求解形式&#xff1a; 1、记忆递归型 2、我为人人递推型…...

南京做网站建设的公司哪家好/全网整合营销推广系统

导读Speedtest是用来测试网络性能的开源软件&#xff0c;在Linux下面安装Speedtest可以用来测试网络出口的上传和下载速度&#xff0c;帮助排查网络方面导致的故障。Speedtest介绍由于公司几个项目用户访问的时候响应较慢&#xff0c;项目本身没问题&#xff0c;服务及调用的接…...

最新新闻热点事件2024年/优化百度seo

二叉树 满足树形结构每个节点最多有两个子节点&#xff0c;即左子树和右子树树是有序的&#xff0c;指的是&#xff0c;单左子树和单右子树是不一样的满二叉树 是二叉树每个非叶子结点都有两个子节点对于深度为k的二叉树&#xff0c;节点总数为2^k - 1完全二叉树 是二叉树如…...

江苏省建设部网站/百度人工申诉客服电话

在基于服务的分布式事务上篇中&#xff0c; 我们举了了一个业务场景&#xff0c;就是一个初始服务创建了一个分布式事务&#xff0c;在这个分布式事务包含了两个参与服务的本地事务&#xff0c;这两个本地事务由初始服务通过调用两个参与事务的服务方式组合在一起。根据分布式事…...