雪花算法工具
什么是雪花算法
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。
雪花算法的原理就是生成一个的 64 位比特位(即64位二进制)的 long 类型的唯一 id。
- 最高 1 位固定值 0,因为生成的 id 是正整数,如果是 1 就是负数了。(因为long是64位整形)
- 接下来 41 位存储毫秒级时间戳,2^41/(1000606024365)=69,大概可以使用 69 年。
- 再接下 10 位存储机器码,包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 台机器。
- 最后 12 位存储序列号。同一毫秒时间戳时,通过这个递增的序列号来区分。即对于同一台机器而言,同一毫秒时间戳下,可以生成 2^12=4096 个不重复 id。
算法优缺点
优点:
- 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。
- 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增。
- 不依赖第三方库或者中间件。
- 算法简单,在内存中进行,效率高。
缺点:
- 依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。
- 其实雪花算法每一部分占用的比特位数量并不是固定死的。例如你的业务可能达不到 69 年之久,那么可用减少时间戳占用的位数,雪花算法服务需要部署的节点超过1024 台,那么可将减少的位数补充给机器码用。
- 注意,雪花算法中 41 位比特位不是直接用来存储当前服务器毫秒时间戳的,而是需要当前服务器时间戳减去某一个初始时间戳值,一般可以使用服务上线时间作为初始时间戳值。
代码
1、工具类使用方式
package cn.jy.common;
/**
* @author jy
* @date 2023/03/13 17:53
**/
public class Snowflake {
/** 初始时间,目前为2023年1月1日0时0分0秒 */
private final long twepoch = 1672502400000l;
/** 服务id位数 */
private int workerIdBits = 5;
/** 机器id位数 */
private int datacenterIdBits = 5;
/** 最大服务位数 */
private int maxWorkerId = -1 ^ (-1 << workerIdBits);
/** 最大机器id位数 */
private int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
/** 序号位数 */
private int sequenceBits = 12;
/** 服务id需要左移位数 */
private int workerIdShift = sequenceBits;
/** 机器id需要左移位数 */
private int datacenterIdShift = sequenceBits + workerIdBits;
/** 时间戳需要左移的位数 */
private int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private int sequenceMask = -1 ^ (-1 << sequenceBits);
/** 服务id */
private int workerId;
/** 机器id */
private int datacenterId;
private long lastTimestamp = -1;
/** 每秒生成id序号 */
private int sequence = 0;
public Snowflake(){
}
/**
* 获取自定义雪花算法类
* @param workerIdBits 服务id比特位数
* @param datacenterIdBits 机器id比特位数
* @param sequenceBits 序号比特位数
* @param workerId 服务id
* @param datacenterId 机器id
*/
public Snowflake(int workerIdBits, int datacenterIdBits, int sequenceBits, int workerId, int datacenterId) {
if(workerIdBits + datacenterIdBits + sequenceBits > 22){
throw new IllegalArgumentException(String.format("workerIdBits, datacenterIdBits, sequenceBits 之和不能超过22"));
}
int maxWorkerId = -1 ^ (-1 << workerIdBits);
int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
if (workerId < 0 || workerId > maxWorkerId) {
throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
}
if (datacenterId < 0 || datacenterId > maxDatacenterId) {
throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
}
this.workerIdBits = workerIdBits;
this.datacenterIdBits = datacenterIdBits;
this.sequenceBits = sequenceBits;
this.maxWorkerId = maxWorkerId;
this.maxDatacenterId = maxDatacenterId;
this.workerId = workerId;
this.datacenterId = datacenterId;
this.workerIdShift = sequenceBits;
this.datacenterIdShift = sequenceBits + workerIdBits;
this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
this.sequenceMask = -1 ^ (-1 << sequenceBits);
}
/**
* 获取默认雪花算法类
* @param workerId 服务id,最大值为31
* @param datacenterId 机器id,最大值为31
*/
public Snowflake(int workerId, int datacenterId) {
if (workerId < 0 || workerId > maxWorkerId) {
throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
}
if (datacenterId < 0 || datacenterId > maxDatacenterId) {
throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("时间回调了,无法在 %d 毫秒内生成id", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
2、springboot中依赖注入方式
package com.jy.commom.util;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* @author jy
* @date 2023/03/13 17:53
**/
@Configuration
public class Snowflake{
/** 初始时间,目前为2023年1月1日0时0分0秒 */
@Value("${snowflake.twepoch:1672502400000}")
private long twepoch;
/** 服务id位数 */
@Value("${snowflake.workerIdBits:5}")
private int workerIdBits;
/** 机器id位数 */
@Value("${snowflake.datacenterIdBits:5}")
private int datacenterIdBits;
/** 最大服务位数 */
private int maxWorkerId;
/** 最大机器id位数 */
private int maxDatacenterId;
/** 序号位数 */
@Value("${snowflake.sequenceBits:5}")
private int sequenceBits;
/** 服务id需要左移位数 */
private int workerIdShift;
/** 机器id需要左移位数 */
private int datacenterIdShift;
/** 时间戳需要左移的位数 */
private int timestampLeftShift;
private int sequenceMask;
/** 服务id */
@Value("${snowflake.workerId}")
private int workerId;
/** 机器id */
@Value("${snowflake.datacenterId}")
private int datacenterId;
private long lastTimestamp = -1;
/** 每秒生成id序号 */
private int sequence = 0;
/**
* 使用 @PostConstruct 注解,在类对象初始化之后立即执行,初始化Snowflake
* 不使用普通依赖注入的原因是,即使在无参构造中计算各属性的值,但是由于@Value注解还未取值,导致各字段数据计算结果都为0,初始化失败
* @author jy
*/
@PostConstruct
private void init(){
if(workerIdBits + datacenterIdBits + sequenceBits > 22){
throw new IllegalArgumentException(String.format("workerIdBits, datacenterIdBits, sequenceBits 之和不能超过22"));
}
int maxWorkerId = -1 ^ (-1 << workerIdBits);
int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
if (workerId < 0 || workerId > maxWorkerId) {
throw new IllegalArgumentException(String.format("workerId 必须在 0 和 %d 之间", maxWorkerId));
}
if (datacenterId < 0 || datacenterId > maxDatacenterId) {
throw new IllegalArgumentException(String.format("datacenterId 必须在 0 和 %d 之间", maxDatacenterId));
}
this.workerIdShift = sequenceBits;
this.datacenterIdShift = sequenceBits + workerIdBits;
this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
this.sequenceMask = -1 ^ (-1 << sequenceBits);
this.maxWorkerId = -1 ^ (-1 << workerIdBits);
this.maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
}
public Snowflake(){}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("时间回调了,无法在 %d 毫秒内生成id", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
使用第二种方法时,需要在配置文件中配置一下参数:
snowflake.workerIdBits=6
snowflake.datacenterIdBits=7
snowflake.sequenceBits=9
snowflake.twepoch=1672502400000
snowflake.workerId=12
snowflake.datacenterId=15
或
snowflake:
workerIdBits: 6
datacenterIdBits: 7
sequenceBits: 9
twepoch: 1672502400000
# 以下两项为必填项
workerId: 12
datacenterId: 15
- workerIdBits:该参数配置为服务id的比特位数,默认配置为5
- datacenterIdBits:该参数配置为机器id的比特位数,默认配置为5
- sequenceBits:该参数配置为机器id的比特位数,默认配置为12
- workerId:该参数配置为服务id,根据实际情况填写,最大值小于1 << workerIdBits,如果workerIdBits为默认配置,此处服务id最大为31
- sdatacenterId:该参数配置为服务id,根据实际情况填写,最大值小于 1 << sequenceBits,如果datacenterIdBits为默认配置,此处服务id最大为31
- twepoch:该参数为时间戳的初始时间,默认配置为1672502400000,即以2023/1/1 0:00:00开始计时
- 注意:workerIdBits + datacenterIdBits + sequenceBits 之和不能超过22
- 注意:由于该工具类使用@Configuration和@PostConstruct注解,所以需要项目为springboot项目才可以用第二种方式依赖
摘抄自网络,便于检索查找。