分布式部署ID全局配置之雪花算法
前言
为什么需要分布式全局唯一ID 以及分布式ID的业务需求?
- 在复杂分布式系统中,往往需要对大量对数据和消息进行标识
- 如在美团、支付、餐饮 中 系统的数据日渐增长,对数据分库分表需要有一个唯一来标识一条数据或消息
- 此时一个能够生成全局唯一ID的系统是非常有必要的
ID生成规则部分硬性要求
- 全局唯一 :不能出现重复的ID,要 唯一标识
- 趋势递增 :在Mysql 的InnoDB引擎使用的是聚集索引,由于多数RDBMS 使用的是Btree数据结构来存储数据,在主键的选择上面我们应该尽量使用有序的主键保证数据写入
- 单调递增 :保证下一个ID一定大于上一个ID,例如事物版本号,增量消息
- 信息安全 :如果ID是连续的,恶意用户的扒取数据就非常容易来,直接按照顺序下载指定的URL,如果是订单号就更危险来,竞争对手可以知道我们一天的单量,所以在一些应用场景下,需要ID不规则
- 含时间戳 :这样就能够在开发中快速了解这个分布式id的生成时间
ID生成系统的可用性要求
- 高可用 :发一个获取分布式ID的请求,服务器就要保证99.99%的情况下给我创建一个唯一分布式ID
- 低延迟 :发一个获取分布式ID的请求,服务器就是要快,极速
- 高QPS :假如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶的住一下子成功创建10w个分布式ID
我们平时的方案
UUID 、 数据库自增主键 、基于Redis 生成全局ID策略
弊端
UUID 不能生成顺序,递增的数据,并且长,不是很推荐
数据库自增,集群多的情况下,扩容简直就是噩梦
Redis 使用Redis INCR 和 INCRBY 实现
snowflake(雪花算法)
Twitter的分布式自增ID算法:snowflake(雪花算法)
概述
最初 Twitter把存储系统从Mysql 迁移到 Cassandra (由Facebook 开发一套开源分布式Nosql系统) 因为Cassandra没有顺序ID生成机制,所以开发成了这样一套全局唯一 ID生成服务
Twitter 的分布式雪花算法SnowFlake , 经测试 snowflake 每秒能产出26 万个自增可排序的ID
- twitter的SnowFlake生成ID能够按照时间有序生成
- SnowFlake 算法生成id 的结果是一个64 bit 大小的整数,为一个Long 型(转换成字符后长度19位)
- 分布式系统不会产生ID碰撞(由datacenter 和 workerld 区分)并且效率较高
雪花算法原理讲解
- 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
- 41bit-时间戳,用来记录时间戳,毫秒级。
- 41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oll7dpVz-1605695234754)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个数字,
- 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCzpD94C-1605695234762)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)],减1是因为可表示的数值范围是从0开始算的,而不是1。
- 也就是说41位可以表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEQQjn42-1605695234764)(https://math.jianshu.com/math?formula=2%5E%7B41%7D-1)]个毫秒的值,转化成单位年则是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUNl2pqO-1605695234768)(https://math.jianshu.com/math?formula=(2%5E%7B41%7D-1)]%20%2F%20(1000%20*%2060%20*%2060%20*%2024%20*365)%20%3D%2069)年 - 10bit-工作机器id,用来记录工作机器id。
- 可以部署在[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7uNLXEq-1605695234771)(https://math.jianshu.com/math?formula=2%5E%7B10%7D%20%3D%201024)]个节点,包括5位datacenterId和5位workerId
- 5位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-egRhWNu3-1605695234774)(https://math.jianshu.com/math?formula=2%5E%7B5%7D-1%20%3D%2031)],即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId - 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
- 12位(bit)可以表示的最大正整数是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0YyvYCu9-1605695234774)(D:%5Ccustersofeware%5Ctypora%5Cimage%5Cmath)],即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
SnowFlake可以保证:
- 所有生成的id按时间趋势递增
- 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
twitter的雪花算法:https://github.com/twitter-archive/snowflake
GitHub上java版的雪花算法:
https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java
使用建议
1、改进
其实雪花算法就是把id按位打散,然后再分成上面这几块,用位来表示状态,这其实就是一种思想。
所以咱们实际在用的时候,也不必非得按照上面这种分割,只需保证总位数在64位即可
如果你的业务不需要69年这么长,或者需要更长时间
用42位存储时间戳,(1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139年
用41位存储时间戳,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
用40位存储时间戳,(1L << 40) / (1000L * 60 * 60 * 24 * 365) = 34年
用39位存储时间戳,(1L << 39) / (1000L * 60 * 60 * 24 * 365) = 17年
用38位存储时间戳,(1L << 38) / (1000L * 60 * 60 * 24 * 365) = 8年
用37位存储时间戳,(1L << 37) / (1000L * 60 * 60 * 24 * 365) = 4年
如果你的机器没有那么1024个这么多,或者比1024还多
用7位存储机器id,(1L << 7) = 128
用8位存储机器id,(1L << 8) = 256
用9位存储机器id,(1L << 9) = 512
用10位存储机器id,(1L << 10) = 1024
用11位存储机器id,(1L << 11) = 2048
用12位存储机器id,(1L << 12) = 4096
用13位存储机器id,(1L << 13) = 8192
如果你的业务,每个机器,每毫秒最多也不会4096个id要生成,或者比这个还多
用8位存储随机序列,(1L << 8) = 256
用9位存储随机序列,(1L << 9) = 512
用10位存储随机序列,(1L << 10) = 1024
用11位存储随机序列,(1L << 11) = 2048
用12位存储随机序列,(1L << 12) = 4096
用13位存储随机序列,(1L << 13) = 8192
用14位存储随机序列,(1L << 14) = 16384
用15位存储随机序列,(1L << 15) = 32768
注意,随机序列建议不要太大,一般业务,每毫秒要是能产生这么多id,建议在机器id上增加位
如果你的业务量很小,比如一般情况下每毫秒生成不到1个id,此时可以将随机序列设置成随机开始自增
比如从0到48随机开始自增,算是一种优化建议
如果你有多个业务,也可以拿出来几位来表示业务,比如用最后4位,支持16种业务的区分
如果你的业务特别复杂,可以考虑128位存储,不过这样的话,也可以考虑使用uuid了,但uuid无序,这个有序
如果你的业务很简单,甚至可以考虑32位存储,时间戳改成秒为单位…
2、总结:
合理的根据自己的实际情况去设计各个唯一条件的组合,雪花算法只是提供了一种相对合理的方式。
雪花算法这种用位来表示状态的,我们还可以用在其他方面,比如数据库存储,可以用更小的空间去表示不同的状态位
包括各种底层的比如序列化,也是有用到拆解位,充分利用存储
算法实现
经过学习,本人写了两种方式去获取全局ID。
方式1:使用工具类进行获取ID
package com.lyj.demo.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import static java.util.concurrent.Executors.newFixedThreadPool;
/**
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 加起来刚好64位,为一个Long型。<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
/**
* @author
* @date 2020/11/18 10:14
* 雪花算法分布式唯一ID生成工具
*/
public class SnowflakeIdUtil {
private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdUtil.class);
/** 开始时间戳 */
private final long startTimeStamp = 1605665795726L;
/** 机器ID所占位数 */
private final long workIdBits = 5L;
/** 数据标志Id所占位数 */
private final long dataCenterIdBits = 5L;
/** 支持的机器最大ID,结果是31,这里受机器设置的ID所占位数大小变化 */
private final long maxSupportWorkId = -1L ^ (-1L << workIdBits);
/** 支持的最大数据标识ID,结果是31,这里受数据标识ID所占位数大小变化 */
private final long maxSupportDataCenterId = -1L ^ (-1L << dataCenterIdBits);
/** 序列号ID所占位数 */
private final long sequenceBits = 12L;
/** 工作厂房ID向左移12位 */
private final long workIdLeftShift = sequenceBits;
/** 数据标识ID向左移17位(12+5) */
private final long dataCenterIdLeftShift = sequenceBits + dataCenterIdBits;
/** 时间戳向左移22位(12+5+5) */
private final long timeStampLeftShift = sequenceBits + workIdBits + dataCenterIdBits;
/** 生成序列的掩码,这里是4095,受序列号位数影响,(0b111111111111=0xfff=4095)2^{12}-1 = 4095 即可以用0、1、2、3、4094 这 4095个数字 */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作厂房ID(0-31) */
private long workId;
/** 数据中心ID(0-31) */
private long dataCenterId;
/** 毫秒内序列ID(0-4094) */
private long sequence;
/** 上次生成ID的时间戳 */
private long lastTimeStamp = -1L;
/** 全局ID生成器 */
private static SnowflakeIdUtil snowflakeId;
public SnowflakeIdUtil() {
}
static {
// 静态加载服务所在系统的ID配置
logger.info("SnowflakeIdUtil workId : {}, dataCenterId : {}", getWorkId(), getDataCenterId());
snowflakeId = new SnowflakeIdUtil(getWorkId(), getDataCenterId());
}
/**
* 获取全局唯一ID
* @return
*/
public static Long generateId() {
return snowflakeId.nextId();
}
/**
* 初始化构造函数
*
* @param workId 指定的工作厂房Id
* @param dataCenterId 指定的数据中心ID(机器ID)
*/
public SnowflakeIdUtil(long workId, long dataCenterId) {
if (workId > maxSupportWorkId || workId < 0) {
throw new IllegalArgumentException(MessageFormat.format("machineId can't greater than {0} or less than 0", maxSupportWorkId));
}
if (dataCenterId > maxSupportDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(MessageFormat.format("dataCenterId can't greater than {0} or less than 0",maxSupportDataCenterId));
}
this.workId = workId;
this.dataCenterId = dataCenterId;
}
public synchronized long nextId () {
long timeStamp = getCurrencyTime();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timeStamp < lastTimeStamp) {
throw new RuntimeException(MessageFormat.format("Clock moved backwards. Refusing to generate id for {0} milliseconds", (lastTimeStamp - timeStamp)));
}
// 如果是同一时间生成的,则进行毫秒内序列
if (lastTimeStamp == timeStamp) {
sequence = (sequence+1) & sequenceMask;
if (sequence == 0) {
// 阻塞到下一毫秒,获取新的时间戳
timeStamp = blockNextMills(lastTimeStamp);
}
// 时间戳改变,毫秒内序列重置
} else {
sequence = 0L;
}
lastTimeStamp = timeStamp;
// 移位并通过或运算拼到一起组成64位的ID
return ((timeStamp - startTimeStamp) << timeStampLeftShift) |
(dataCenterId << dataCenterIdLeftShift) |
(workId << workIdLeftShift) |
sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimeStamp 上次生成的时间戳ID
* @return 当前时间戳
*/
private static long blockNextMills(long lastTimeStamp) {
long timeStamp = getCurrencyTime();
while (timeStamp <= lastTimeStamp) {
timeStamp = getCurrencyTime();
}
return timeStamp;
}
/**
* 获取当前系统时间
*
* @return 毫秒
*/
private static long getCurrencyTime() {
return System.currentTimeMillis();
}
/**
* IP地址取模
*
* @return IP地址取模
*/
private static long ipModule() {
InetAddress localHost = null;
try {
localHost = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
// 获取IP失败,使用随机数兜底
return RandomUtils.nextInt(0, 31);
}
// 机器位占用5位,所以取模32,最大限度避免重复
return ipv4ToLong(localHost.getHostAddress()) % 32;
}
/**
* IP地址转long类型
* @param strIP IP地址
* @return long类型
*/
private static long ipv4ToLong(String strIP) {
Validator.validateIpv4(strIP, "Invalid IPv4 address!");
long[] ip = (long[])Convert.convert(long[].class, StrUtil.split(strIP, '.'));
return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
}
/**
* 获取数据中心ID(机器ID)
*
* @return 采用本地机器名称取32的模(降低分布式部署工作ID相同)
*/
private static long getDataCenterId() {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
try {
InetAddress localHost = InetAddress.getLocalHost();
String hostName = localHost.getHostName();
List<Integer> tenNums = string2Integer(hostName);
if (CollectionUtils.isEmpty(tenNums)) {
return localRandom.nextInt(0,31);
}
int sums = 0;
for (int i = 0; i < tenNums.size(); i++) {
sums += tenNums.get(i);
}
return (long) (sums % 32);
} catch (UnknownHostException e) {
return localRandom.nextInt(0, 31);
}
}
/**
* 获取工作工厂ID
*
* @return 采用本地IP地址取32的模(降低分布式部署工作ID相同)
*/
private static long getWorkId() {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
try {
InetAddress localHost = InetAddress.getLocalHost();
String ip = localHost.getHostAddress();
List<Integer> tenNums = string2Integer(ip);
if (CollectionUtils.isEmpty(tenNums)) {
// IP为空,则采用随机数指定工作ID
return localRandom.nextInt(0,31);
}
int sums = 0;
for (int i = 0; i < tenNums.size(); i++) {
sums += tenNums.get(i);
}
return (long) (sums % 32);
} catch (UnknownHostException e) {
// ip 获取失败,则采用随机数指定工作ID
return localRandom.nextInt(0,31);
}
}
/**
* 字符串转成10进制的数字集合
*
* @param str 字符串
* @return 10进制的数字集合(这里的是将字符转成对应的ASICC码)
*/
private static List<Integer> string2Integer(String str) {
if (StringUtils.isBlank(str)) {
return Collections.emptyList();
}
List<Integer> integers = Lists.newArrayListWithCapacity(str.length());
for (int i = 0; i < str.length(); i++) {
integers.add(Integer.valueOf(Integer.toString(str.charAt(i), 10)));
}
return integers;
}
public static void main(String[] args) throws UnknownHostException {
// 并发测试
ExecutorService threadPool = newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> {
System.out.println(snowflakeId.nextId());
});
// threadPool.shutdown();
}
// 200W条数据,耗时在13ms左右,平均150条/ms
System.out.println("++++++++++++++++++++++++++=");
BufferedWriter bufferedWriter = null;
try {
//设置文件编码,解决文件乱码问题
//将字节流转换为字符流,实际上使用了一种设计模式——适配器模式(生成本地项目根目录)
bufferedWriter = new BufferedWriter(new FileWriter("./idSnow.txt"));
System.out.println(System.currentTimeMillis());
long startTime = System.nanoTime();
for (int i = 0; i < 2000000; i++) {
long id = snowflakeId.nextId();
bufferedWriter.write(String.valueOf(id));
bufferedWriter.newLine();//按行读取,写入一个分行符,否则所有内容都在一行显示
System.out.println(id);
}
System.out.println((System.nanoTime()-startTime)/1000000+"ms");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
方式二:交给spring管理,通过注入方式获取全局ID
package com.lyj.demo.config;
import com.google.common.collect.Lists;
import com.lyj.demo.utils.SnowflakeIdUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.CollectionUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* 网上的教程一般存在两个问题:
* 1. 机器ID(5位)和数据中心ID(5位)配置没有解决,分布式部署的时候会使用相同的配置,任然有ID重复的风险。
* 2. 使用的时候需要实例化对象,没有形成开箱即用的工具类。
*
* 针对上面两个问题进行解决,解决方案是,workId使用服务器hostName生成,
* dataCenterId使用IP生成,这样可以最大限度防止10位机器码重复,但是由于两个ID都不能超过32,
* 只能取余数,还是难免产生重复,但是实际使用中,hostName和IP的配置一般连续或相近,
* 只要不是刚好相隔32位,就不会有问题,况且,hostName和IP同时相隔32的情况更加是几乎不可能
* 的事,平时做的分布式部署,一般也不会超过10台容器。使用上面的方法可以零配置使用雪花算法,
* 雪花算法10位机器码的设定理论上可以有1024个节点,生产上使用docker配置一般是一次编译,
* 然后分布式部署到不同容器,不会有不同的配置,这里不知道其他公司是如何解决的,即使有方法
* 使用一套配置,然后运行时根据不同容器读取不同的配置,但是给每个容器编配ID,1024个
* (大部分情况下没有这么多),似乎也不太可能,此问题留待日后解决后再行补充。
*/
/**
* @author
* @date 2020/11/18 16:53
* 全局分布式ID配置(雪花算法)交给spring管理
*/
@Configuration
public class SnowflakeIdConfig {
private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdConfig.class);
@Value("${snowflake.id.workId:noWorkId}")
private String workId;
@Value("${snowflake.id.dataCenterId:noDataCenterId}")
private String dataCenterId;
public SnowflakeIdConfig() {
}
@Bean(name = "snowflakeIdWorker")
@Primary
public SnowflakeIdUtil snowflakeId(){
// 这里还是采用的是上面的工具类,静态加载的配置可以删掉,也可以不删除,因为这里已经重新new对象了,不是同一个对象
return new SnowflakeIdUtil(getWorkId(workId), getDataCenterId(dataCenterId));
}
/**
* workId配置
*
* @param workId workId
* @return workId
*/
private long getWorkId(String workId) {
logger.info("SnowflakeIdConfig workId : {}", workId);
if ("noWorkId".equalsIgnoreCase(workId)) {
return getWorkId();
}
return num2Model(string2Integer(workId));
}
/**
* dataCenterId配置
*
* @param dataCenterId 数据中心ID
* @return dataCenterId
*/
private long getDataCenterId(String dataCenterId) {
logger.info("SnowflakeIdConfig dataCenterId : {}", dataCenterId);
if ("noDataCenterId".equalsIgnoreCase(dataCenterId)) {
return getDataCenterId();
}
return num2Model(string2Integer(dataCenterId));
}
/**
* 获取数据中心ID(机器ID)
*
* @return 采用本地机器名称取32的模(降低分布式部署工作ID相同)
*/
private long getDataCenterId() {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
try {
InetAddress localHost = InetAddress.getLocalHost();
String hostName = localHost.getHostName();
List<Integer> tenNums = string2Integer(hostName);
if (CollectionUtils.isEmpty(tenNums)) {
return localRandom.nextInt(0,31);
}
return num2Model(tenNums);
} catch (UnknownHostException e) {
return localRandom.nextInt(0, 31);
}
}
/**
* 获取工作工厂ID
*
* @return 采用本地IP地址取32的模(降低分布式部署工作ID相同)
*/
private long getWorkId() {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
try {
InetAddress localHost = InetAddress.getLocalHost();
String ip = localHost.getHostAddress();
List<Integer> tenNums = string2Integer(ip);
if (CollectionUtils.isEmpty(tenNums)) {
// IP为空,则采用随机数指定工作ID
return localRandom.nextInt(0,31);
}
return num2Model(tenNums);
} catch (UnknownHostException e) {
// ip 获取失败,则采用随机数指定工作ID
return localRandom.nextInt(0,31);
}
}
/**
* 字符串转成10进制的数字集合
*
* @param str 字符串
* @return 10进制的数字集合(这里的是将字符转成对应的ASICC码)
*/
private List<Integer> string2Integer(String str) {
if (StringUtils.isBlank(str)) {
return Collections.emptyList();
}
List<Integer> integers = Lists.newArrayListWithCapacity(str.length());
for (int i = 0; i < str.length(); i++) {
integers.add(Integer.valueOf(Integer.toString(str.charAt(i), 10)));
}
return integers;
}
/**
* 求和取模32
*
* @param nums 数字集合
* @return long类型
*/
private long num2Model(List<Integer> nums) {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
if (CollectionUtils.isEmpty(nums)) {
return localRandom.nextInt(0,31);
}
int sums = 0;
for (int i = 0; i < nums.size(); i++) {
sums += nums.get(i);
}
return (long) (sums % 32);
}
}
测试
@Autowired
private SnowflakeIdUtil snowflakeIdWorker;
/**
* 雪花算法分布式Id唯一测试
* 1:工具类静态加载管理测试
* 2:spring管理测试
*/
@RequestMapping("/test/snowflake")
public void snowflakeTest() {
// 通过工具类静态加载获取
System.out.println(SnowflakeIdUtil.generateId());
// 通过spring管理优先注入获取
System.out.println(snowflakeIdWorker.nextId());
}
参考链接:https://developer.aliyun.com/article/772913
https://www.cnblogs.com/jpfss/p/11506973.html
https://www.cnblogs.com/austinspark-jessylu/p/11900527.html