雪花算法
雪花算法(Snowflake)是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake IDs或snowflakes。这种算法由Twitter创建,并用于推文的ID。Discord和Instagram等其他公司采用了修改后的版本。
格式
一个Snowflake ID有64位元。前41位是时间戳,表示了自选定的时期以来的毫秒数。 接下来的10位代表计算机ID,防止冲突。 其余12位代表每台机器上生成ID的序列号,这允许在同一毫秒内创建多个Snowflake ID。最后以十进制将数字序列化。
SnowflakeID基于时间生成,故可以按时间排序。此外,一个ID的生成时间可以由其自身推断出来,反之亦然。该特性可以用于按时间筛选ID,以及与之联系的对象。
范例
2022年六月由@Wikipedia所发的一条推文的雪花ID是1541815603606036480。这个数字被转换成二进制就是0b 0001 0101 0110 0101 1010 0001 0001 1111 0110 0010 00|01 0111 1010|0000 0000 0000,其中以竖线分隔成三个部分。
- 最前面41位元(加上1位元的0)转换成十进制为367597485448。将这个值加上推特纪年1288834974657(UNIX时间毫秒),这条推特的UNIX时间为1656432460.105:即世界协调时间2022年6月28日16:07:40.105。
- 中间10位元01 0111 1010是机器ID。
- 后面12位元解码出来全部为0,代表这条推文是该机器于这一微秒所发出的第一条推文。
Java实现代码
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
/**
* @Description 使用雪花算法生成随机ID
* @Author hyx
* @Date 2023-07-31
*/
public class SnowflakeIdGenerator {
/**
* 定义起始时间戳, UTC+8:00 2023-07-31 00:00:00
*/
private static final long START_TIMESTAMP = 1690732800L;
// 定义每个部分的bit数
/**
* 序列号占用的bit数,用于表示在同一毫秒内生成的序列号。
*/
private static final long SEQUENCE_BIT = 12;
/**
* 工作机器ID占用的bit数,用于表示当前工作机器的唯一ID。
*/
private static final long WORKER_BIT = 10;
/**
* 数据中心ID占用的bit数,用于表示当前数据中心的唯一ID。
*/
private static final long DATACENTER_BIT = 10;
// 定义每个部分的最大值
/**
* 定义了数据中心ID所能表示的最大值。它是一个位运算的结果,将二进制表示的最低DATACENTER_BIT位全部置为1,从而得到最大的数据中心ID。
*/
private static final long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
/**
* 定义了工作机器ID所能表示的最大值,用法类似于MAX_DATACENTER_NUM。
*/
private static final long MAX_WORKER_NUM = ~(-1L << WORKER_BIT);
/**
* 定义了序列号所能表示的最大值,用法类似于MAX_DATACENTER_NUM。
*/
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
// 定义每个部分向左的位移数
/**
* 定义了工作机器ID向左的位移数,用于将工作机器ID占用的bit位移动到正确的位置。
*/
private static final long WORKER_LEFT = SEQUENCE_BIT;
/**
* 定义了数据中心ID向左的位移数,用法类似于WORKER_LEFT。
*/
private static final long DATACENTER_LEFT = SEQUENCE_BIT + WORKER_BIT;
/**
* 定义了时间戳向左的位移数,用法类似于WORKER_LEFT。
*/
private static final long TIMESTAMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
/**
* 表示当前数据中心的唯一ID
*/
private final long DATA_CENTER_ID;
/**
* 表示当前工作机器的唯一ID
*/
private final long WORKER_ID;
/**
* 用AtomicLong来表示序列号,用于记录在同一毫秒内生成的序列号。初始值为0,每次生成ID时会自增,并通过& MAX_SEQUENCE操作保证在规定位数内循环。
*/
private final AtomicLong SEQUENCE = new AtomicLong(0L);
/**
* 用于记录上次生成ID的时间戳,初始值为-1L。在生成ID时,会检查当前时间戳与上次时间戳的大小关系,以保证生成的ID是单调递增的。
*/
private volatile long lastTimestamp = -1L;
/**
* 构造函数
*
* @param datacenterId 数据中心ID
* @param workerId 工作机器ID
*/
public SnowflakeIdGenerator(long datacenterId, long workerId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("数据中心ID不能大于 " + MAX_DATACENTER_NUM + " 或小于 0");
}
if (workerId > MAX_WORKER_NUM || workerId < 0) {
throw new IllegalArgumentException("工作机器ID不能大于 " + MAX_WORKER_NUM + " 或小于 0");
}
this.DATA_CENTER_ID = datacenterId;
this.WORKER_ID = workerId;
SEQUENCE.set(0L);
}
/**
* 生成ID
*
* @return long
*/
public synchronized long nextId() {
long currentTimestamp = getTimestamp();
if (currentTimestamp < lastTimestamp) {
throw new RuntimeException("时钟向后移动。拒绝为 " + (lastTimestamp - currentTimestamp) + " ms生成ID。");
}
if (currentTimestamp == lastTimestamp) {
long seq = SEQUENCE.getAndIncrement() & MAX_SEQUENCE;
if (seq == 0) {
// 当前毫秒的序列号已用完,等待下一毫秒
currentTimestamp = getNextTimestamp(lastTimestamp);
SEQUENCE.set(0L);
}
} else {
SEQUENCE.set(0L);
}
lastTimestamp = currentTimestamp;
// 将各个部分拼接为最终的ID
return ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT) |
(DATA_CENTER_ID << DATACENTER_LEFT) |
(WORKER_ID << WORKER_LEFT) |
SEQUENCE.get();
}
/**
* 获取当前时间戳
*
* @return long
*/
private long getTimestamp() {
// 使用System.nanoTime()获取更高精度的时间戳
long nanoTime = System.nanoTime();
// 将纳秒级的时间戳转换为毫秒级,并加上起始时间戳
long timeStamp = (nanoTime / 1000000L) + START_TIMESTAMP;
// 返回当前时间戳和上次时间戳中较大的一个,以保证生成的ID是单调递增的
return Math.max(timeStamp, lastTimestamp);
}
/**
* 等待下一毫秒
*
* @param lastTimestamp 最后一次生成ID的时间戳
* @return long
*/
private long getNextTimestamp(long lastTimestamp) {
long currentTimestamp = getTimestamp();
while (currentTimestamp <= lastTimestamp) {
// 等待一纳秒,避免循环空转,导致CPU资源浪费
LockSupport.parkNanos(1L);
currentTimestamp = getTimestamp();
}
return currentTimestamp;
}
// 示例用法
public static void main(String[] args) {
SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(0, 1);
for (int i = 0; i < 100; i++) {
long id = idGenerator.nextId();
System.out.println("Generated ID" + i + ": " + id);
}
}
}