前言
雪花算法是推特开源的分布式ID生成算法,用于在不同的机器上生成唯一的ID的算法,该算法生成一个64bit的数字作为分布式ID,保证这个ID自增并且全局唯一
第1位:不使用,一直是0;
第2~42位:表示时间戳,共41位,单位是毫秒,可以支撑大约69年;
第43~47位:表示机房ID,共5位,最多可容纳1024个机房;
第48~52位:表示机器ID,共5位,最多可容纳1024台机器;
第53~64位:表示序列号,共12位,表示同一毫秒内产生的ID,单台机器每毫秒最多可生成4096个订单ID;
一、实现
import java.util.Date;
/**
* 雪花算法:基于时间戳生成自增ID
*/
public class Snowflake {
// 2023-2-21 11:03:27作为系统上线时间
private static final long START_TIMESTAMP = 1676948607869L;
// 时间戳占用位数
private static final long TIMESTAMP_BIT = 41L; // 41 bit 69年
private static final long MAX_TIMESTAMP = ~(-1L << TIMESTAMP_BIT);
// 机房占用位数
private static final long MACHINE_ROOM_BIT = 5L; // 5 bit
private static final long MAX_MACHINE_ROOM = ~(-1L << MACHINE_ROOM_BIT); // 11111
// 机器占用位数
private static final long MACHINE_BIT = 5L; // 5 bit
private static final long MAX_MACHINE = ~(-1L << MACHINE_BIT); // 11111
// 序列号占用位数
private static final long SERIAL_BIT = 12L; // 12 bit
private static final long MAX_SERIAL = ~(-1L << SERIAL_BIT); // 1111 1111 1111
private static long curSerial = -1L;
private static long lastTimestamp = 0L;
/**
* 生成id
* @param machineRoomId 机房id
* @param machineId 机器id
* @return id
* @throws Exception 空
*/
public static synchronized long generateId(long machineRoomId, long machineId) throws Exception{
if (machineRoomId > MAX_MACHINE_ROOM || machineId > MAX_MACHINE) {
throw new Exception("机房号或机器号已经满额分配");
}
long curTimestamp = System.currentTimeMillis();
if (curTimestamp < lastTimestamp) {
throw new Exception("发生时间回拨: 回拨" + (curTimestamp - lastTimestamp) + "s");
}
// 超过时间戳使用位数
if (curTimestamp - lastTimestamp > MAX_TIMESTAMP){
throw new Exception("时间戳满额分配,已到达使用年限(69年)");
}
// 如果同时请求生成id
if (curTimestamp == lastTimestamp) {
curSerial++;
if (curSerial > MAX_SERIAL) {
throw new Exception("序列号已经满额分配");
}
// 如果是全新时间请求生成id
} else {
curSerial = 0L;
}
// 记录时间戳
lastTimestamp = curTimestamp;
return ((curTimestamp - START_TIMESTAMP) << (MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT)) |
(machineRoomId << (MACHINE_BIT + SERIAL_BIT)) |
(machineId << SERIAL_BIT) |
curSerial;
}
/**
* 获取生成时间
* @param snowflakeId 该雪花算法生成的id
* @return 生成时间
*/
public static Date toDate(long snowflakeId) {
long timestampStep = MAX_TIMESTAMP << (MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT) & snowflakeId;
timestampStep >>>= MACHINE_ROOM_BIT + MACHINE_BIT + SERIAL_BIT;
return new Date(START_TIMESTAMP + timestampStep);
}
/**
* 获取机房id
* @param snowflakeId 该雪花算法生成的id
* @return 机房id
*/
public static long toMachineRoomId(long snowflakeId) {
long machineRoomId = MAX_MACHINE_ROOM << (MACHINE_BIT + SERIAL_BIT) & snowflakeId;
return machineRoomId >>> (MACHINE_BIT + SERIAL_BIT);
}
/**
* 获取机器id
* @param snowflakeId 该雪花算法生成的id
* @return 机器id
*/
public static long toMachineId(long snowflakeId) {
long machineId = MAX_MACHINE_ROOM << SERIAL_BIT & snowflakeId;
return machineId >>> SERIAL_BIT;
}
}
二、相关问题及解决办法
- 参数满额:分配达到最大值,还要继续分配?
- 记录前一个参数的最大值,当前参数满额时,使前一个参数值自增,当前参数则重新开始自增
- 在算法书写时规划好各个参数的范围,随场景需要增大满额参数的占用位,缩小其他参数的占用位
- 时钟回拨问题:当前时间小于历史最大生成时间,可能产生重复ID?
- 生成时记录时间和序列号等信息,对当前时间下的序列号增大生成避免,但是其自增性会被破坏
- 短周期生成场景(本文代码设置的是69年的时间周期),可考虑多时钟,将时间戳截断成两个时间进行生成,但是其自增性会被破坏
- 记录历史最大生成时间,对历史最大生成时间自增进行生成
- 延迟等待自增
- 自定义时间戳,但时间戳的意义可能丢失
- 最后,不管啥问题,抛错处理(谁爱管谁管(¬︿̫̿¬☆))