Snowflake 雪花算法优缺点(Java老司机实战总结)

  • Home
  • 军火库
  • Snowflake 雪花算法优缺点(Java老司机实战总结)

Snowflake 雪花算法优缺点(Java老司机实战总结)

✍ 作者:天天摸鱼的java工程师

💼 从业8年,主攻Java后端 + 分布式架构

📌 今天我们来聊聊:Snowflake 雪花算法

💡 背景:为什么需要雪花算法?

我们在做订单系统、用户系统、支付系统时,经常需要全局唯一ID(非数据库主键) :

分布式环境下不能用数据库自增ID;

UUID 虽然唯一,但不有序 、不易读、占用空间大;

雪花算法就是为了解决这些问题而生的。

🌨 什么是 Snowflake?

Snowflake(雪花算法) 最早由 Twitter 提出,用于在分布式系统中生成全局唯一的、有序的64位整数ID。

🧬 雪花ID结构(64位)

位数

含义

示例解释

1位

符号位

永远为0

41位

时间戳

当前时间戳 - 起始时间戳(单位毫秒)

10位

机器ID

包括数据中心ID + 机器ID,最多支持1024台

12位

序列号

每毫秒内支持4096个ID

🧮 雪花ID样例

ini

复制代码

1010100110010110000000000000000000000110000010010000000000010010

=>

178972842389893122

✅ Snowflake 优点(为啥大家都爱它)

1️⃣ 高性能

纯内存计算,生成一个ID只需微秒级时间;

每毫秒能生成4096个ID,适合高并发场景。

2️⃣ 全局唯一 & 有序

基于时间戳生成,天然有序;

多节点部署也不会重复,保证唯一性。

3️⃣ 不依赖数据库

不用数据库锁,不用中心协调;

分布式部署非常友好,支持横向扩展。

4️⃣ 数字ID,可读性好

相比UUID,更短更可读;

适合用于订单号、用户ID、日志追踪等业务。

❌ Snowflake 缺点(老司机踩过的坑)

1️⃣ 时间依赖强 → 时钟回拨问题

如果服务器时间回拨(如NTP校时),可能生成重复ID;

解决方案:

检测回拨并抛异常;

等待回拨时间过去;

使用全局时钟服务(如 TSO)。

2️⃣ 单点故障风险(如果只部署一个节点)

单节点部署虽然简单,但失效就挂了;

建议部署多台机器,配合机器ID 或 数据中心ID 使用。

3️⃣ ID 不可读,不支持趋势分析

虽然是数字,但不直观;

不能看出 ID 属于哪个业务、用户等 → 可用业务前缀解决

4️⃣ 序列号耗尽(每毫秒最多4096个)

极端高并发场景下,可能溢出;

解决方式:

等待下一毫秒;

增加机器节点分流请求。

🔧 Java 实现 Snowflake 算法(实战代码)

ini

复制代码

public class SnowflakeIdGenerator {

// 起始时间戳(2020-01-01)

private final long epoch = 1577808000000L;

private final long datacenterIdBits = 5L;

private final long workerIdBits = 5L;

private final long sequenceBits = 12L;

private final long maxWorkerId = ~(-1L << workerIdBits); // 31

private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 31

private final long workerIdShift = sequenceBits; // 12

private final long datacenterIdShift = sequenceBits + workerIdBits; // 17

private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 22

private final long sequenceMask = ~(-1L << sequenceBits); // 4095

private long lastTimestamp = -1L;

private long sequence = 0L;

private final long workerId;

private final long datacenterId;

public SnowflakeIdGenerator(long workerId, long datacenterId) {

if (workerId > maxWorkerId || datacenterId > maxDatacenterId) {

throw new IllegalArgumentException("workerId or datacenterId out of range");

}

this.workerId = workerId;

this.datacenterId = datacenterId;

}

public synchronized long nextId() {

long timestamp = currentTime();

if (timestamp < lastTimestamp) {

throw new RuntimeException("Clock moved backwards. Refusing to generate id");

}

if (timestamp == lastTimestamp) {

sequence = (sequence + 1) & sequenceMask;

if (sequence == 0) {

// 当前毫秒内序列号用完,等待下一个毫秒

timestamp = waitNextMillis(lastTimestamp);

}

} else {

sequence = 0L;

}

lastTimestamp = timestamp;

return ((timestamp - epoch) << timestampLeftShift)

| (datacenterId << datacenterIdShift)

| (workerId << workerIdShift)

| sequence;

}

private long waitNextMillis(long lastTimestamp) {

long timestamp = currentTime();

while (timestamp <= lastTimestamp) {

timestamp = currentTime();

}

return timestamp;

}

private long currentTime() {

return System.currentTimeMillis();

}

}

🧪 测试效果

arduino

复制代码

public class Main {

public static void main(String[] args) {

SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1);

for (int i = 0; i < 5; i++) {

System.out.println(generator.nextId());

}

}

}

erlang

复制代码

142345234354123456

142345234354123457

...

🚀 适用场景

业务场景

是否推荐

原因说明

分布式订单系统

✅ 推荐

唯一 + 有序 + 无需中心协调

用户ID生成

✅ 推荐

可读性好,唯一性强

微服务请求追踪ID

✅ 推荐

全局唯一,便于日志追踪

高并发日志系统

✅ 推荐

性能高且有序

数据库主键

✅ 推荐

替代自增ID,支持水平分表

🧩 延伸阅读

↑ 你可以结合 Redis、ZooKeeper、Etcd 等方式来分配机器ID;

↑ 也可以替换时间戳逻辑为 逻辑时钟 / TSO(如TiDB的实现);

✅ 总结

优点

缺点

高性能

依赖时间,易受回拨影响

全局唯一 & 有序

不支持趋势分析

分布式友好

单机部署易成单点

数字ID 可读性强

每毫秒最多生成 4096 个ID

🗣️ 最后

如果你觉得这篇文章对你有帮助,欢迎:

👉 点赞 👍 + 收藏 ⭐ + 关注 🧡

👉 在评论区聊聊你们的分布式ID生成方案