java 雪花算法,同时解决超过前端 js 数字上限的问题
雪花算法最终得到一个 long 类型的 64 位数字雪花算法的组成部分:符号位(1) + 时间戳(41) + 数据编码(5)+ 机器编码(5)+ 序列号(12)理论上当 机器编码 和 数据编码 不变的情况下可以生成:0 - 2^53 个 ID, 千万亿级别。
1. 雪花算法(标准版)
雪花算法最终得到一个 long 类型的 64 位数字
雪花算法的组成部分:符号位(1) + 时间戳(41) + 数据编码(5)+ 机器编码(5)+ 序列号(12)
理论上当 机器编码 和 数据编码 不变的情况下可以生成:0 - 2^53 个 ID, 千万亿级别
代码如下:
/**
* 雪花算法的组成部分:符号位(1) + 时间戳(41) + 数据编码(5)+ 机器编码(5)+ 序列号(12)
* ID 生成范围: (0 - 9223372036854775808] 2^63 百亿亿级别
* @author Mitchell
* @since 2022-10-26
*/
public class SnowFlake {
/**
* 每一部分所占位数(数据中心编码,机器中心编码,序列号)
*/
private final long datacenterIdBits = 5L;
private final long workerIdBits = 5L;
private final long sequenceBits = 12L;
/**
* 每一部分的最大值(数据中心编码,机器中心编码,序列号)
*/
private final long maxDatacenterId = ~(-1L << datacenterIdBits);
private final long maxWorkerId = ~(-1L << workerIdBits);
private final long maxSequence = ~(-1L << sequenceBits);
/**
* 每一部分的偏移(时间戳区域,数据中心区域,机器中心区域)
*/
private final long timestampShift = sequenceBits + datacenterIdBits + workerIdBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long workerIdShift = sequenceBits;
/**
* 起始时间戳,初始化后不可修改
*/
private final long epoch = 1690373045764L;
/**
* 数据中心编码,初始化后不可修改,取值范围: [0,31]
*/
private final long datacenterId;
/**
* 机器进程编码,初始化后不可修改,取值范围: [0,31]
*/
private final long workerId;
/**
* 序列号,取值范围: [0,4095]
*/
private long sequence = 0L;
/**
* 上次执行生成 ID 方法的时间戳
*/
private long lastTimestamp = -1L;
/**
* 雪花算法构造器
* @param datacenterId 数据中心编码 [0,31]
* @param workerId 机器进程编码 [0,31]
*/
public SnowFlake(long datacenterId, long workerId) {
if (datacenterId > maxDatacenterId || datacenterId < 0) {
String errMsg = String.format("datacenterId 取值范围在 [0,%d] 之间", maxDatacenterId);
throw new IllegalArgumentException(errMsg);
}
if (workerId > maxWorkerId || workerId < 0) {
String errMsg = String.format("workerId 取值范围在 [0,%d] 之间", maxWorkerId);
throw new IllegalArgumentException(errMsg);
}
this.datacenterId = datacenterId;
this.workerId = workerId;
}
/**
* 生成下一个唯一标识,其中:符号位 + 时间戳占 42 位
* 1,雪花算法依赖服务器时间,如果时间发生回拨,抛异常
* 2,每毫秒生成 maxSequence 个 id,如果超过则阻塞到下一毫秒
*/
public synchronized long nextId() {
long currTimestamp = timestampGen();
if (currTimestamp < lastTimestamp) {
String errMsg = String.format("服务器时间发生回拨 %d milliseconds", lastTimestamp - currTimestamp);
throw new IllegalStateException(errMsg);
}
if (currTimestamp == lastTimestamp) {
sequence = (sequence + 1) & maxSequence;
if (sequence == 0) {
currTimestamp = waitNextMillis(currTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = currTimestamp;
long id = (currTimestamp - epoch) << timestampShift;
id = id | (datacenterId << datacenterIdShift);
return id | (workerId << workerIdShift) | sequence;
}
/**
* 循环阻塞直到下一(毫秒)
* @param currTimestamp 时间戳
*/
protected long waitNextMillis(long currTimestamp) {
while (currTimestamp <= lastTimestamp) {
currTimestamp = timestampGen();
}
return currTimestamp;
}
/**
* 获取当前时间戳
*/
public long timestampGen() {
return System.currentTimeMillis();
}
}
2. 雪花算法(小数字版)
标准的雪花算法生成 id ,正常都超过了 前端 javascript 的数字范围上限,往往会丢失一部分,从而导致 ID 不一致导致系统出错
-
解决方案1:把生成的 id 当成字符串储存,但是这会降低数据库的查询性能
-
解决方案2:把生成的 id 当还是当成数字储存在表里,程序执行完转 json 给前端时,转化为 字符串
以上两种方式都可以解决问题,但是心中还是不爽,明明生成的是一个数字,却要和字符串转来转去,
笔者这边想了一种方式,那就是让生成的 ID 小于前端 JS 的数字上限,这样就可以愉快的使用了
这边修改了雪花算法的组成部分:符号位(1) + 时间戳(41)+ 机器编码(5)+ 序列号(7)
理论上当 机器编码 不变的情况下可以生成:0 - 2^48 个 ID, 百万亿级别
代码如下:
package com.mitchell.admin.back.service.mybatis;
/**
* 小数字版雪花算法的组成部分:符号位(1) + 时间戳(41) + 数据编码(0)+ 机器编码(5)+ 序列号(7)
* ID 生成范围: (0 - 9007199254740992] 2^53 千万亿
* @author Mitchell
* @since 2022-10-26
*/
public class SnowFlakeSmall {
/**
* 每一部分所占位数(机器中心编码,序列号)
*/
private final long workerIdBits = 5;
private final long sequenceBits = 7;
/**
* 每一部分的最大值(机器中心编码,序列号)
*/
private final long maxWorkerId = ~(-1L << workerIdBits);
private final long maxSequence = ~(-1L << sequenceBits);
/**
* 每一部分的偏移(时间戳区域,机器中心区域)
*/
private final long timestampShift = workerIdBits + sequenceBits;
private final long workerIdShift = sequenceBits;
/**
* 起始时间戳,初始化后不可修改
*/
private final long epoch = 1690373045764L;
/**
* 机器进程编码,初始化后不可修改,取值范围: [0,31]
*/
private final long workerId;
/**
* 序列号,取值范围: [0,4095]
*/
private long sequence = 0L;
/**
* 上次执行生成 ID 方法的时间戳
*/
private long lastTimestamp = -1L;
/**
* 雪花算法构造器(缩小版)
* @param workerId 数据中心编码 [0,31]
*/
public SnowFlakeSmall(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("无效的 machineId,其范围应该在 0 - " + maxWorkerId);
}
this.workerId = workerId;
}
/**
* 生成下一个唯一标识,其中:符号位 + 时间戳占 42 位
* 1,雪花算法依赖服务器时间,如果时间发生回拨,抛异常
* 2,每毫秒生成 maxSequence 个 id,如果超过则阻塞到下一毫秒
*/
public synchronized long nextId() {
long currTimestamp = timestampGen();
if (currTimestamp < lastTimestamp) {
String errMsg = String.format("服务器时间发生回拨 %d milliseconds", lastTimestamp - currTimestamp);
throw new IllegalStateException(errMsg);
}
if (currTimestamp == lastTimestamp) {
sequence = (sequence + 1) & maxSequence;
if (sequence == 0L) {
currTimestamp = waitNextMillis(currTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = currTimestamp;
return (currTimestamp - epoch) << timestampShift | workerId << workerIdShift | sequence;
}
/**
* 循环阻塞直到下一毫秒
* @param currTimestamp 时间戳
*/
private long waitNextMillis(long currTimestamp) {
while (currTimestamp <= lastTimestamp) {
currTimestamp = timestampGen();
}
return currTimestamp;
}
/**
* 获取当前时间戳
*/
private long timestampGen() {
return System.currentTimeMillis();
}
}
3. 两相对比
标准的算法总共能生成的 ID 个数是 小数字版 的 2 ^ 5 倍,即 32 倍
标准的算法每毫秒能生成 2 ^ 12 = 4096 个 ID,小数字版本是 2 ^ 7 = 128 个
标准的算法由于同时存在 机器码 和 数据码,理论上分布式可以达到 32 * 32 = 1024 个节点,而小数字版只有 32 个
通过上方两个量级的对比,对于一般项目,小数字版已经够用,当然如果你的项目存在大量的新增,对于 ID 的需求量很大的话,还是建议使用标准版
更多推荐
所有评论(0)