雪花算法原理深度解析
雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,用于在分布式系统中生成全局唯一且趋势递增的 ID。以下是对其简介、优缺点、原理及代码实现的详细解析:
一、雪花算法简介
据国家大气研究中心的查尔斯·奈特称,一般的雪花大约由10^19个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己漂亮独特的形状。雪花算法表示生成的id如雪花般独一无二。
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
二、雪花算法的优缺点
优点
- 全局唯一性:通过数据中心 ID 和机器 ID 确保不同节点生成的 ID 不重复。
- 趋势递增:生成的 ID 按时间有序递增,有利于数据库索引优化。
- 高性能:本地生成,无需依赖数据库或分布式协调服务,QPS 理论值约为 409.6 万 / 秒。
- 灵活可配置:可根据业务需求调整各部分位数(如增加机器 ID 位数、减少序列号位数)。
缺点
- 依赖系统时钟:若服务器时钟回拨,可能导致生成重复 ID(需额外处理)。
- ID 位数限制:64 位 ID 的各部分长度固定,需提前规划各字段的分配。
- 单节点生成上限:每毫秒最多生成 4096 个 ID,高并发场景可能需多节点扩展。
三、雪花算法原理
1)1位保留 (基本不用)
1位标识:由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0,所以这第一位都是0。
2)41位时间戳
接下来 41 位存储毫秒级时间戳,41位可以表示2^41-1个毫秒的值,转化成单位年则是:(2^41−1)/(1000∗60∗60∗24∗365)=69年 。
41位时间戳 :也就是说这个时间戳可以使用69年不重复,大概可以使用 69 年。
注意:41位时间截不是存储当前时间的时间截,而是存储时间截的差值“当前时间截 – 开始时间截”得到的值。
这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的,一般设置好后就不要去改变了,切记!!!
因为,雪花算法有如下缺点:依赖服务器时间,服务器时钟回拨时可能会生成重复 id。
3)10位机器
10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId,最多可以部署 2^10=1024 台机器。这里的5位可以表示的最大正整数是2^5−1=31,即可以用0、1、2、3、….31这32个数字,来表示不同的datecenterId(数据中心ID),或workerId(工作机器ID)
4) 12bit序列号2
用来记录同毫秒内产生的不同id,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号。
QPS(每秒查询率)的计算基于 “单台机器每秒可生成的最大 ID 数”:
- 1 秒 = 1000 毫秒
- 每毫秒最多生成 4096 个 ID
- 单台机器理论 QPS = 4096×1000=4,096,000(即 409.6 万 / 秒)。
这一数值是单节点的极限性能,实际中受系统处理能力(如 CPU、内存)限制,可能无法达到,但足以支撑绝大多数高并发场景(例如秒杀、峰值订单处理等)。
四、雪花算法代码实现(java)
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
五、注意事项
- 机器 ID 分配:需确保每个节点的
datacenter_id和worker_id组合唯一,可通过配置文件或分布式协调服务(如 ZooKeeper)分配。 - 时钟同步:建议所有服务器通过 NTP 服务定期同步时间,减少时钟回拨风险。
- 性能优化:高并发场景下,可通过预分配多个 Snowflake 实例(每个线程一个)减少锁竞争。
更多推荐


所有评论(0)