分布式 id 生成算法的有很多种,Twitter 的雪花算法(nowFlake)就是其中经典的一种,它的结构如下:
- 1bit,固定值,因为二进制中最高位是符号位,1 表示负数,0 表示正数。生成的 id 一般都用正整数,所以最高位固定为 0 。
- 41bit,是时间戳,单位是毫秒,不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 – 开始时间截)得到的值,这里的开始时间截,一般是 id 生成器开始使用的时间,由程序来指定的,表示的范围是 0 到 (2^42) – 1,也就是说可以表示 (2^42) – 1 毫秒的值,转化为年是 69 年。
- 10bit,机器标识,5 位机房 id 和 5 位机器 id ,10 位的长度最多支持部署 1024 个节点。
- 12bit,计数序列号,表示能在一毫秒内生成 4096 个不同的 id 。
public class SnowFlake {
// 开始时间截 (2023年7月22日)的某个时间点。
private final long TWEPOCH = 1690000890436L;
// 机器id所占的位数
private final long WORKER_ID_BITS = 5L;
// 数据标识(机房)id所占的位数
private final long DATACENTER_ID_BITS = 5L;
// 支持的最大机器id值。
private final long MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
// 支持的最大数据标识id值。
private final long MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1;
// 12位序列号
private final long sequenceBits = 12L;
// 生成序列的掩码, (0b111111111111=0xfff)
private final long SEQUENCE_MASK = (1 << sequenceBits) - 1;
// 机器ID(0~31)
private long workerId;
// 数据中心(机房)ID(0~31)
private long datacenterId;
// 12位序列的值,(0~4095)
private long sequence = 0L;
// 上次生成ID的时间截
private long lastTimestamp = -1L;
/**
* 构造函数
*
* @param workerId 机器id(0~31)
* @param datacenterId 机房ID (0~31)
*/
public SnowFlake(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0)
throw new IllegalArgumentException(String.format("worker Id can't be " +
"greater than %d or less than 0", MAX_WORKER_ID));
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0)
throw new IllegalArgumentException(String.format("datacenter Id can't be " +
"greater than %d or less than 0", MAX_DATACENTER_ID));
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获得下一个ID (该方法是线程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();// 生成当前时间的时间戳
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常。
if (timestamp < lastTimestamp)
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate " +
"id for %d milliseconds", lastTimestamp - timestamp));
// 如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) { //毫秒内序列数量超出12表示的范围。
// 阻塞到下一个毫秒,获得新的时间戳。
timestamp = tilNextMillis(lastTimestamp);
}
} else { //不是同一时间生成的,毫秒内序列重置。
sequence = 0L;
}
// 上次生成ID的时间截。
lastTimestamp = timestamp;
// 拼接成64为的ID。
return ((timestamp - TWEPOCH) << 22) // 41位时间戳,要减去开始时间。
| (datacenterId << 17) // 5位机房id
| (workerId << 12) // 5位机器id
| sequence;// 12位随机数
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
private long tilNextMillis(long lastTimestamp) {
long curTime = timeGen();
while (curTime <= lastTimestamp) {
curTime = timeGen();
}
return curTime;
}
// 生成当前时间的时间戳。
private long timeGen() {
return System.currentTimeMillis();
}
}