6 Star 81 Fork 18

李翔 / 开箱即用的java新型雪花算法(SnowFlake)

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

开箱即用的java雪花算法(yitter-idgenerator-spring-boot-starter)

1、介绍

雪花算法是一个分布式主键id生成的解决方案,他解决了分布式id生成的痛点问题,本算法基于推特雪花算法,进行深度优化。

算法基于SnowFlake IdGenerator核心代码开发,引入springboot自动配置,从而做到开箱即用的效果。该算法在缩短ID长度的同时,具备极高瞬时并发处理能力(50W/0.1s),且支持时间回拨。

如果你觉得好用,请点一个star,这对我来说非常重要,谢谢!!!

2、技术支持

本算法基于多语言新雪花算法(SnowFlake IdGenerator)核心代码实现,关于该算法更多细节请参阅项目地址。

3、算法特点

✔ 整形数字,随时间单调递增(不一定连续),长度更短,用50年都不会超过 js Number类型最大值。(默认配置)

✔ 速度更快,是传统雪花算法的2-5倍,0.1秒可生成50万个(基于8代低压i7)。

✔ 支持时间回拨处理。比如服务器时间回拨1秒,本算法能自动适应生成临界时间的唯一ID。

✔ 支持手工插入新ID。当业务需要在历史时间生成新ID时,用本算法的预留位能生成5000个每秒。

✔ 不依赖任何外部缓存和数据库。(k8s环境下自动注册 WorkerId 的动态库依赖 redis)

✔ 基础功能,开箱即用,无需配置文件、数据库连接等。

✔支持微服务动态配置,会动态加载单例bean,通过动态加载配置文件,而实现自动注册机器码。

✔支持数据中心id分配,更贴近实际生产环境。

4、如何使用

当然,我也为你准备了一个demo:新型雪花算法微服务demo

4.1、通用步骤

4.1.1、引入相关maven

maven仓库地址

1、spring-boot引入

Maven Central

<dependency>
  <groupId>io.github.lmlx66</groupId>
  <artifactId>yitter-idgenerator-spring-boot-starter</artifactId>
  <version>${yitter-idGenerator.version}</version>
</dependency>
2、spring-cloud引入

springCloud包主要用于支持动态配置id生成器,包括刷新静态类。没有相关需求建议使用spring-boot依赖

Maven Central

<dependency>
  <groupId>io.github.lmlx66</groupId>
  <artifactId>yitter-idgenerator-spring-cloud-starter</artifactId>
  <version>${yitter-idGenerator.version}</version>
</dependency>

注意:在spring-cloud中动态配置我们使用了spring-cloud-context包。spring-boot与spring-cloud版本是强对应关系,可能需要你引入与你的spring-boot包相对应的版本,以解决冲突

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
    <version>${spring-cloud-context.version}</version>
</dependency>
4.1.2、如何使用

1、直接调用静态方法IdGenerator.next()

V1.1.3新特性

@RestController
public class IdController {
    
    @GetMapping("getId")
    public long getId(){
        return IdGenerator.next();
    } 
}

2、注入生成器WFGIdGenerator并调用next方法

@RestController
public class IdController {
   
    @Autowired
    private WFGIdGenerator wFGIdGenerator;
    
    @GetMapping("getId")
    public long getId(){
        return wFGIdGenerator.next();
    } 
}
4.1.3、yaml配置

当然我们也可以对雪花算法简单的配置一下,yaml文件示例如下:

wfg:
# 请一定注意! WorkerIdBitLength + SeqBitLength + DataCenterIdBitLength <= 22
  # 1表示雪花漂移算法,2表示传统雪花算法
  Method: 1
  # 基础时间,为2022-01-01 00:00:00
  baseTime: 1640966400000
  # 数据中心id
  DataCenterId: 0
  # 数据中心id位长,默认为0表示不开启数据中心id功能
  DataCenterIdBitLength: 0
  # 机器码(当前系统的机器码)
  WorkerId: 0
  # 机器码位长(能表示机器码的最大值)
  WorkerIdBitLength: 1
  # 序列数位长(能表示机器码的最大序列数)
  SeqBitLength: 6
  # 最大序列数(含)
  MinSeqNumber: 5
  # 最小序列数(含)
  MaxSeqNumber: 0
  # 最大漂移次数,与计算能力有关
  TopOverCostCount: 2000

4.2、整合mybatis-plus

使用mybatis-plus官方提供的自定义ID生成器接口,我们能实现自定义主键插入。拦截器实习如下:

/**
 * @author: 王富贵
 * @description: mp拦截器用于注入id请求
 * @createTime: 2023/4/23 9:40
 */
@Component
public class CustomIdGenerator implements IdentifierGenerator {

    @Resource
    private WFGIdGenerator wFGIdGenerator;

    @Override
    public Long nextId(Object entity) {
       return wFGIdGenerator.next();
    }
}

在实体类上使用注解@TableId(value = "对应表字段名", type = IdType.ASSIGN_ID),则插入该字段为null会使用我们的雪花算法生成一个id。

其中重要的是type必须为IdType.ASSIGN_ID,如下所示:

public class YourEntity {
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;
}

5、配置详解

5.1、配置文件配置

我们支持在yaml或者properties等配置文件中配置,注意前缀为wfg

参数名 默认值 作用
Method(short) 1 1表示雪花漂移算法,2表示传统雪花算法
BaseTime(long) 1640966400000 基础时间,为2022-01-01 00:00:00
DataCenterId 0 数据中心id
DataCenterIdBitLength 0 数据中心id位长,默认为0表示不开启数据中心id功能
WorkerIdBitLength 1 机器码位长(能表示机器码的最大值)
WorkerId 0 机器码(当前系统的机器码)
SeqBitLength 6 序列数位长(能表示机器码的最大序列数)
MaxSeqNumber 0(不限制) 最大序列数(含)
MinSeqNumber 5(不限制) 最小序列数(含)
TopOverCostCount 2000 最大漂移次数,与计算能力有关

5.2、配置类配置

当然,我们也支持配置类配置,返回类型为WFGIdGenerator,其构造需要一个IdGeneratorProperties类型。

IdGeneratorProperties是基础配置类实体映射类,其内部属性即我们可配置的属性。

但请注意,如果配置类和配置文件(yaml或properties)同时使用,优先采用配置类配置。

@Configuration
public class IdGeneratorConfig {
    @Bean
    public WFGIdGenerator wFGIdGenerator() {
         //准备基础配置类,在此可以配置基础信息
        IdGeneratorOptions idGeneratorOptions = new IdGeneratorOptions();
        idGeneratorOptions.setWorkerId((short) 6); //设置机器码为6
        idGeneratorOptions.setWorkerIdBitLength((byte) 3); //设置机器码位长为3
        //装载id生成器的配置文件
        return new wFGIdGenerator(idGeneratorOptions);
    }
}
5.2.1、配置优先级

请注意我们的优先级,本地配置文件配置(本地yaml文件或properties文件) < 配置类配置(自己创建bean) < 配置中心配置(如nacos-config配置)

原因如下:配置中心配置修改时会重新加载bean

5.3、参数详解

Method,表示使用什么算法,默认值为1,表示使用雪花漂移算法,2表示使用传统雪花算法。但仍建议你使用雪花漂移算法(Method=1,默认的),毕竟它具有更好的伸缩力和更高的性能。

BaseTime,基础时间(也称:基点时间、原点时间、纪元时间),默认值为:2022-01-01 00:00:00,是毫秒时间戳(是整数,.NET是DatetTime类型),作用是:用生成ID时的系统时间与基础时间的差值(毫秒数)作为生成ID的时间戳。基础时间一般无需设置,如果觉得默认值太老,你可以重新设置,不过要注意,这个值以后最好不变。

DataCenterId, 数据中心id,默认值0,必须 全局唯一(或相同 DataCenterId 内唯一),必须 程序设定,缺省条件(DataCenterIdBitLength取最大值6)时最大值63,表示支持63个数据中心。

DataCenterIdBitLength,数据中心位长,决定 DataCenterId 的最大值,默认值为0,表示不开启区分数据中心功能,取值范围[0,6]。

WorkerId,机器码,最重要参数默认值0,必须 全局唯一(或相同 DataCenterId 内唯一),必须 程序设定,缺省条件(WorkerIdBitLength取默认值)时最大值63,理论最大值 2^WorkerIdBitLength-1(不同实现语言可能会限定在 65535 或 32767,原理同 WorkerIdBitLength 规则)。不同机器或不同应用实例 不能相同,你可通过应用程序配置该值,也可通过调用外部服务获取值。针对自动注册WorkerId需求,本算法提供默认实现:通过 redis 自动注册 WorkerId 的动态库,详见“Tools\AutoRegisterWorkerId”。

WorkerIdBitLength,机器码位长,决定 WorkerId 的最大值,默认值1,取值范围 [1, 19],实际上有些语言采用 无符号 ushort (uint16) 类型接收该参数,所以最大值是16,如果是采用 有符号 short (int16),则最大值为15。

特别提示:如果一台服务器部署多个独立服务,需要为每个服务指定不同的 WorkerId。

SeqBitLength,序列数位长,默认值6,取值范围 [3, 21](建议不小于4),决定每毫秒基础生成的ID个数。规则要求:WorkerIdBitLength + SeqBitLength + DataCenterIdBitLength <= 22。

MinSeqNumber,最小序列数,默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号0-4是保留位,其中0是手工插入新值预留位,1-4是时间回拨相应预留位。

MaxSeqNumber,最大序列数,设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,真实最大序列数取最大值(2^SeqBitLength-1),不为0时,取其为真实最大序列数,一般无需设置,除非多机共享WorkerId分段生成ID(此时还要正确设置最小序列数)。

tips:

关于规则:DataCenterIdBitLength + WorkerIdBitLength + SeqBitLength <= 22,我们id采用long基本类型,,占8byte,64bit。之所以定义是因为我们需要预留一些bit给毫秒数占位,因此,如果你需要线上使用,请计算清楚在你的需求下,该实例能跑多久。如果确实需求超出long类型存储大小,请使用String类型或者BigInteger类型重写。

5.4、关于动态配置的问题

5.4.1、动态加载配置

当你使用动态加载配置时,如果你在配置中心配置文件中注释掉了某个配置,他是不会恢复默认配置的,这和动态配置底层有关,这里就不过多描述。

因此,在你使用动态配置的时候,我们强烈建议将所有的配置都配置上去,并按需修改成你预期的配置,而不是注释掉,因为就算注释掉也不会再去使用默认配置了。

默认配置如下:

wfg:
# 请一定注意! WorkerIdBitLength + SeqBitLength + DataCenterIdBitLength <= 22
  # 1表示雪花漂移算法,2表示传统雪花算法
  Method: 1
  # 基础时间,为2022-01-01 00:00:00
  baseTime: 1640966400000
  # 数据中心id
  DataCenterId: 0
  # 数据中心id位长,默认为0表示不开启数据中心id功能
  DataCenterIdBitLength: 0
  # 机器码(当前系统的机器码)
  WorkerId: 0
  # 机器码位长(能表示机器码的最大值)
  WorkerIdBitLength: 1
  # 序列数位长(能表示机器码的最大序列数)
  SeqBitLength: 6
  # 最大序列数(含)
  MinSeqNumber: 5
  # 最小序列数(含)
  MaxSeqNumber: 0
  # 最大漂移次数,与计算能力有关
  TopOverCostCount: 2000
5.4.2、引入动态配置包报错

引入普通的依赖包不报错,但是很有可能引入动态配置包之后就报错了?

很大可能是 spring-boot 与 spring-cloud 版本不对应。

动态配置是依赖 spring-cloud 核心注解@RefreshScope实现的,因此我们引入了 spring-cloud-context-3.1.2 版本,但做过微服务的朋友都知道,spring-boot与spring-cloud版本需要高度对应,因此可能 spring-cloud-context-3.1.2 可能与你项目的 spring-boot 版本不对应,需移除旧版本,或引入与你 spring-boot 版本对应的 spring-cloud-context 版本

其二,我们由于自动配置在spring-2.7.0做出较大改变,因此暂时没有适配。如果您的spring版本为2.7.0,请降低spring版本使用。

6、其他细节

6.1、id组成

  • 本算法生成的ID由3部分组成(沿用雪花算法定义):
  • +----------------------------------------------------------------------------------------+
  • | 1.相对基础时间的时间差 | 2.DataCenterId数据中心id |3.WorkerId机器码 | 4.序列数 |
  • +----------------------------------------------------------------------------------------+

当然下图并没有DataCenterId数据中心id,但其实类似于WorkerId的。

输入图片说明

6.2、异常处理

IdGeneratorException为此id生成器唯一且默认的异常类,他继承了RuntimeException,假如你需要捕获异常,请捕获它。

6.3、集成算法

1️⃣ 用单例模式调用。外部集成方使用更多的实例并行调用本算法,不会增加ID产出效能,因为本算法采用单线程生成ID。

2️⃣ 指定唯一的 WorkerId。必须由外部系统确保 WorkerId 的全局唯一性,并赋值给本算法入口方法。

3️⃣ 单机多实例部署时使用不同 WorkerId。并非所有实现都支持跨进程的并发唯一,保险起见,在同一主机上部署多应用实例时,请确保各 WorkerId 唯一。

4️⃣ 异常处理。算法会抛出所有 Exception,外部系统应 catch 异常并做好应对处理,以免引发更大的系统崩溃。

5️⃣ 认真理解 IdGeneratorOptions 的定义,这对集成和使用本算法有帮助。

6️⃣ 使用雪花漂移算法。虽然代码里包含了传统雪花算法的定义,并且你可以在入口处指定(Method=2)来启用传统算法,但仍建议你使用雪花漂移算法(Method=1,默认的),毕竟它具有更好的伸缩力和更高的性能。

7️⃣ 不要修改核心算法。本算法内部参数较多,逻辑较为复杂,在你尚未掌握核心逻辑时,请勿修改核心代码且用于生产环境,除非通过大量细致、科学的测试验证。

8️⃣ 应用域内配置策略相同。当系统运行一段时间后,项目需要从程序指定 WorkerId 转到自动注册 WorkerId 时,请确保同一应用域内所有在用实例采用一致的配置策略,这不仅仅针对 WorkerId,也包含其他所有配置参数。

9️⃣ 管理好服务器时间。雪花算法依赖系统时间,不要手工大幅度回调操作系统时间。如果一定要调整,切记:确保服务再次启动时的系统时间大于最后一次关闭时的时间。(注:世界级或网络级的时间同步或回拨,引起的系统时间小幅度变化,对本算法没影响)

6.4、配置变更

配置变更指是系统运行一段时间后,再调整运行参数(IdGeneratorOptions 选项值),请注意:

🔴 1.最重要的一条原则是:BaseTime 只能往前(比老值更小、距离现在更远)赋值,原因是往后赋值极大可能产生相同的时间戳。[不推荐在系统运行之后调整 BaseTime]

🔴 2.任何时候增加 WorkerIdBitLength 或 SeqBitLength,都是可以的,但是慎用 “减小”的操作,因为这可能导致在未来某天生成的 ID 与过去老配置时相同。[允许在系统运行之后增加任何一个 BitLength 值]

🔴 3.如果必须减小 WorkerIdBitLength 或 SeqBitLength 其中的一项,一定要满足一个条件:新的两个 BitLength 之和要大于 老的值之和。[不推荐在运行之后缩小任何一个 BitLength 值]

🔴 4.上述3条规则,并未在本算法内做逻辑控制,集成方应根据上述规则做好影响评估,确认无误后,再实施配置变更。

6.5、最佳实践

  1. 机器码请务必唯一,我们可以使用redis自增结合nacos-config或其他方法进行自动配置。也可以使用该算法本身等特性进行k8s集成。
  2. 序数位直接决定了本算法一毫秒内能够生成多少id,如果超过此数量,则会阻塞住。请务必测试好你的并发。
  3. 本包支持动态配置加载bean,但我们强烈建议动态配置中心将所有配置都配置上,而不是不配置而使用默认。
  4. 雪花算法用于解决分布式场景id问题,但其机器码动态赋值解决方案较为复杂,下面提供了一个简单的发号器。

6.6、Redis实现机器码发号

我们能够使用redis自增来进行机器码赋值处理,此外,我们还需要限制自增数量。故需要lua脚本保证原子性,示例脚本如下所示:

注意:该解决方案同一个雪花id维度必须批次上线,否则会造成机器码重复。

原因:redis无法感知机器下线也无法回收下线机器id,只能在给定机器码范围内轮询发号。假若你需要一个发号器,参考:idgenerator的动态库发号器

-- 定义一个 Redis key,用于存储当前分发的机器码
local machineCodeKey = "wfg:SnowFlake:workId"

-- 定义最大的机器码数(假设为 1024)
local maxMachineCode = 1024

-- 尝试获取当前分发的机器码
local machineCode = tonumber(redis.call('GET', machineCodeKey))

-- 如果当前机器码不存在或者超过最大值,回到0重新分发
if not machineCode or machineCode >=  maxMachineCode
    then machineCode = 0
end

-- 增加当前机器码,用于下次分发
redis.call("SET", machineCodeKey, machineCode + 1)

-- 返回当前分发的机器码
return machineCode

redis执行rua脚本需要将动态字符串拼接,并且假如在集群模式下拼接多个keys,可能会因为插槽而报错。具体执行如下:

-- 尝试获取当前分发的机器码
local machineCode = tonumber(redis.call('GET', KEYS[1]))

-- 如果当前机器码不存在或者超过最大值,回到0重新分发
if not machineCode or machineCode >=  1024
    then machineCode = 0
end

-- 增加当前机器码,用于下次分发
redis.call("SET", KEYS[1], machineCode + 1)

-- 返回当前分发的机器码
return machineCode

redis语法如下所示:

EVAL "local machineCode = tonumber(redis.call('GET', KEYS[1])); if not machineCode or machineCode >= tonumber(ARGV[1]) then machineCode = 0 end; redis.call('SET', KEYS[1], machineCode + 1); return machineCode;" 1 wfg:SnowFlake:workId

java中发号服务如下所示:

/**
 * @author 王富贵
 * @since 2023/08/08 21:15
 * 雪花算法机器id颁发器
 * 尝试获取key,如果不存在则创建一个,如果存在则自增并返回
 */
@Component
public class SnowflakeWorkIdGen {
    @Resource
    private IdGeneratorOptions idGeneratorOptions;
    @Resource
    RedisTemplate<String, Object> redisTemplate;

    private static final String WorkIdKey = "wfg:SnowFlake:workId";

    public Short getWorkId() {
        // 最大机器码
        int maxMachineCode = 1 << idGeneratorOptions.getWorkerIdBitLength();
        String luaScript =
                        " local machineCode = tonumber(redis.call('GET', KEYS[1])) " +
                        " if not machineCode or machineCode >= "+ maxMachineCode +
                        " then machineCode = 0 " +
                        " end;  " +
                        " redis.call('SET', KEYS[1], machineCode + 1); " +
                        " return machineCode ";
        Long workId = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
                Collections.singletonList(WorkIdKey));
        if (null == workId) {
            throw new RuntimeException("从redis获取id失败");
        }
        return Short.valueOf(String.valueOf(workId));
    }
}

那么创建一个配置类,将发号服务嵌入即可。

@Configuration
public class SnowflakeIdGenConfig {

    @Resource
    private IdGeneratorOptions idGeneratorOptions;
    @Resource
    private SnowflakeWorkId snowflakeWorkId;

    @Bean
    @RefreshScope //配置文件更改的时候重新加载bean,需要动态配置需要加上
    public WFGIdGenerator wFGIdGenerator() {
        Short workId = snowflakeWorkId.getWorkId();
        //准备基础配置类,在此可以配置基础信息
        idGeneratorOptions.setWorkerId(workId);
        //装载id生成器的配置文件
        return new WFGIdGenerator(idGeneratorOptions);
    }
}

7、版本迭代

V1.1.3

新增静态雪花生成器:IdGenerator,兼容某些不想或不能注册的bean。

V1.1.2

V1.1.2文档

稳定版本

MIT License Copyright (c) 2022 李翔 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

雪花算法是一个分布式主键id生成的解决方案,他解决了分布式id生成的痛点问题。 本项目依赖于SnowFlake IdGenerator核心代码,加入springboot自动配置,从而达到开箱即用的效果。该算法在缩短ID长度的同时,具备极高瞬时并发处理能力(50W/0.1s)。并且支持回拨。 展开 收起
Java
MIT
取消

发行版 (7)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/lmlx66/yitter-idgenerator-spring-boot-starter.git
git@gitee.com:lmlx66/yitter-idgenerator-spring-boot-starter.git
lmlx66
yitter-idgenerator-spring-boot-starter
开箱即用的java新型雪花算法(SnowFlake)
master

搜索帮助