雪花算法是一种用于生成唯一标识符的分布式算法,由Twitter设计开发。它的核心思想是利用时间戳、机器ID和序列号来生成全局唯一的64位整数。下面是一个简化版的雪花算法的描述:
雪花算法的构成:
- 64位整数: 雪花算法生成的ID是一个64位的整数,它由以下几部分组成:
- 1位符号位(始终为0) + 41位时间戳 + 10位机器ID + 12位序列号。
具体构成:
-
时间戳(41位):
- 使用当前时间戳,精确到毫秒级别。
- 41位时间戳可以支持约 69 年的时间。
-
机器ID(10位):
- 标识不同的机器节点,确保在不同节点上生成的ID不会冲突。
- 通常由数据中心ID和机器ID组成。
-
序列号(12位):
- 为了解决同一毫秒内可能出现的多次ID生成需求而设计。
- 当在同一毫秒内生成多个ID时,使用序列号来区分,确保生成的ID唯一。
工作流程:
-
获取时间戳: 获取当前时间戳,以毫秒为单位。
-
判断时钟回拨:
- 如果检测到时钟回拨,则暂停生成,直到时钟追上之前的时间戳,避免重复和混乱。
-
生成ID:
- 将时间戳左移22位(64-41位)腾出空间给机器ID和序列号。
- 将机器ID左移12位(64-41-10位),与时间戳相加。
- 将序列号加到最低位的12位。
注意事项:
- 机器ID唯一性: 不同机器的ID必须唯一,避免冲突。
- 时钟同步: 时间戳的连续性和准确性对于ID的唯一性至关重要。
这种算法的设计目的是在分布式系统中生成唯一的ID,适用于高并发、分布式的场景,并且不依赖于中心化的ID分配服务。
实现思路
public class Snowflake {
private final long workerId;
private long sequence = 0L;
private final long twepoch = 1620079244226L; // 起始时间戳,可以设置一个合适的开始时间
private final long workerIdBits = 5L; // 机器 ID 的位数
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 最大机器 ID
private final long sequenceBits = 12L; // 序列号的位数
private final long workerIdShift = sequenceBits;
private final long timestampLeftShift = sequenceBits + workerIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public Snowflake(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID 必须在 0 到 " + maxWorkerId + " 之间");
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟向后移动,拒绝生成ID");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}