雪花算法 Snowflake
一种由 Twitter 开发的分布式全局唯一 ID 生成算法,它生成的 ID 是一个 64 位的整数,也就是 Java 中的 long 类型。
具备以下特点:
- 整体有序性:基于时间戳生成,大致按照时间递增。
- 高性能:生成速度快,适用于高并发场景。
- 分布式:可以在多个节点生成唯一 ID。
- 高扩展性:通过调整位数分配,可以适应不同场景需求。
原理分析
标准的 64 位雪花算法 ID 分为以下几个部分:
1. 符号位(1 bit): 始终为 0,表示 ID 为正数。
2. 时间戳相对值(41 bit): 表示当前时间与基准时间的差值,可以支持 69 年的时间范围(2^41 / (1000 * 60 * 60 * 24 * 365))。
3. 数据中心 ID(5 bit): 用于标识数据中心,可以支持最多 2^5 = 32 个数据中心。
4. 机器 ID(5 bit): 用于标识每个数据中心中的机器,可以支持最多 2^5 = 32 台机器。
5. 序列号(12 bit): 表示同一毫秒内生成的序号,可以支持每毫秒生成 2^12 = 4096 个。
符号位 | 时间间戳相对值 | 数据中心 ID | 机器 ID | 序列号 |
---|---|---|---|---|
0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000 |
注意事项
- 在一个节点里,由于机器 ID 相同,如果创建多个 SnowflakeIdGenerator 实例,会生成相同 Id,所以 SnowflakeIdGenerator 应该设计成单例。
- 在多个节点里,如果使用相同的机器 ID,也可能会生成相同 Id,所以要确保每个节点分配的机器 ID 是唯一的。
- 系统时间回退,可能导致生成重复 ID,也就是当前系统时间,小于上一次生成 Id 的时间,可以拒绝生成,抛出异常,也可以使用额外的时间偏移量进行补偿。
机器 ID 分配策略
- 保证唯一性:避免分配重复的机器 ID。
- 自动化分配:通过动态机制分配机器 ID ,减少人工干预。
- 兼容性:支持不同的运行环境(容器、虚拟机、本地多实例)。
常见分配策略
- 中心化分配: 使用集中式的配置管理或注册服务分配机器 ID,如 ZooKeeper、Consul 等。
- 去中心化分配: 从环境或系统信息推导出唯一机器 ID ,如利用 IP、MAC 地址、容器 hostname、进程 ID 等计算出机器 ID。
容器部署:基于容器 ID 、hostname 生成。
虚拟机部署:基于 IP 地址、MAC 地址生成。
本地多实例:基于进程 ID、端口号生成。
ID 长度问题
已知 long 的最大值为 2^63 - 1 = 9223372036854775807,十进制长度为 19 位。
也就是说,随着时间推移,当前时间与基准时间的差值越来也大,雪花 ID 的长度也会变长。
Java 实现
本地简单代码示例,基准时间为 2024-01-01, 默认使用进程 ID 当做机器 ID,系统时间回退时,拒绝生成,并抛出异常。
import java.lang.management.ManagementFactory;
import java.util.concurrent.locks.ReentrantLock;
/**
* 雪花算法
*
* @author sheng
*/
public class Snowflake {
public static final Snowflake INSTANCE = new Snowflake(getMachineId());
// 自定义时间戳基准 2024 年 1 月 1 日)
private static final long EPOCH = 1704067200000L;
// 默认机器 ID
private static final long DEFAULT_MACHINE_ID = 1L;
// 时间戳位数
private static final long TIMESTAMP_BITS = 41L;
// 机器 ID 位数
private static final long MACHINE_ID_BITS = 10L;
// 递增序列号位数
private static final long SEQUENCE_BITS = 12L;
// 机器 ID 左移位数
private static final long MACHINE_ID_LEFT_SHIFT = SEQUENCE_BITS;
// 时间戳左移位数
private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
// 最大时间戳
private static final long MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) - 1;
// 最大机器 ID
private static final long MAX_MACHINE_ID = (1 << MACHINE_ID_BITS) - 1;
// 最大序列号
private static final long MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;
private final long machineId; // 机器 ID
private long lastTimestamp = -1L; // 上次生成 ID 的时间戳
private long sequence = 0L; // 递增序列号
private final ReentrantLock lock = new ReentrantLock();
// 私有化构造方法
private Snowflake(long machineId) {
// 校验机器 ID
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException(
String.format("MachineId 必须在 0 - %d 之间", MAX_MACHINE_ID));
}
this.machineId = machineId;
}
/**
* 生成唯一 ID
*/
public long nextId() {
lock.lock();
try {
long timestamp = currentTimeMillis();
// 检查时间是否回拨
if (timestamp < lastTimestamp) {
throw new RuntimeException("系统时钟回退,拒绝生成 ID");
}
// 检查时间戳是否溢出
long relativeTimestamp = timestamp - EPOCH;
if (relativeTimestamp > MAX_TIMESTAMP) {
throw new RuntimeException("时间戳超出可用范围");
}
// 同一毫秒内递增序列号
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
// 序列号溢出,等待下一毫秒
if (sequence == 0) {
timestamp = waitForNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 生成雪花算法 ID
return (relativeTimestamp << TIMESTAMP_LEFT_SHIFT)
| (machineId << MACHINE_ID_LEFT_SHIFT)
| sequence;
} finally {
lock.unlock();
}
}
/**
* 获取当前时间戳(毫秒)
*/
private long currentTimeMillis() {
return System.currentTimeMillis();
}
/**
* 等待下一毫秒
*/
private long waitForNextMillis(long lastTimestamp) {
long timestamp = currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = currentTimeMillis();
}
return timestamp;
}
/**
* 机器 ID
* 使用进程 ID
*/
private static long getMachineId() {
try {
// 格式:<pid>@<hostname>
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
// 保留后十位
return Long.parseLong(pid) & MAX_MACHINE_ID;
} catch (Exception e) {
throw new RuntimeException("获取机器 ID 失败", e);
}
}
}
测试
/**
* @author sheng
*/
public class SnowflakeIdTest {
public static void main(String[] args) {
Snowflake snowflake = Snowflake.INSTANCE;
for (int i = 0; i < 100; i++) {
System.out.println(snowflake.nextId());
}
}
}
结果
130472881724493824
130472881728688128
130472881728688129
130472881728688130
130472881728688131
130472881728688132
130472881728688133