分布式 ID 如何设计:从自增 ID 到 Snowflake 与号段模式
分布式 ID 如何设计:从自增 ID 到 Snowflake 与号段模式
单体系统里,数据库自增主键通常已经够用。真正进入多实例、分库分表或跨系统写入后,问题才变成:
多个节点如何在没有单点冲突的情况下,高性能地生成全局唯一 ID?
分布式 ID 没有统一最优解。设计时需要在唯一性、性能、可用性、排序能力和运维成本之间取舍。

一、先明确设计目标
一个可用的分布式 ID 通常需要满足以下条件:
| 目标 | 说明 |
|---|---|
| 全局唯一 | 不同服务、不同节点生成的 ID 不能重复 |
| 高性能 | 生成 ID 不应成为业务链路瓶颈 |
| 高可用 | 某个节点或依赖短暂故障时仍能工作 |
| 趋势递增 | 新 ID 大体更大,减少数据库索引随机写入 |
| 长度可控 | 常用 BIGINT 比长字符串更节省索引空间 |
| 不泄露业务 | 不直接编码手机号、订单量、地区等敏感信息 |
这里要区分两个概念:
- 趋势递增:总体上越晚生成的 ID 越大。
- 全局严格递增:所有节点生成的 ID 都能按真实发生顺序排列。
后者需要全局协调,代价明显更高。大部分业务只需要唯一和趋势递增。
二、常见方案对比
| 方案 | 优点 | 主要问题 | 适用场景 |
|---|---|---|---|
| 数据库自增 | 简单、严格递增、索引友好 | 依赖单库,分库后冲突 | 单库单表 |
| UUIDv4 | 本地生成,无中心节点 | 无序、128 位、索引局部性差 | 文件名、追踪 ID |
| UUIDv7 | 时间有序,无 workerId | 仍是 128 位 | 跨系统资源 ID |
Redis INCR |
严格递增,实现简单 | 每次都要远程访问 Redis | 中小规模集中发号 |
| Snowflake | 64 位、本地生成、吞吐高 | workerId、时钟回拨 | 常规微服务 |
| 号段模式 | 不依赖机器时钟、性能稳定 | 需要号段服务和数据库 | 高并发、容器化系统 |
UUIDv7 为什么值得关注
传统 UUIDv4 的随机部分位于整个 ID 中,写入 B+ 树索引时局部性较差。
UUIDv7 将 Unix 毫秒时间戳放在高位,使 ID 可以按时间大致排序,同时保留足够的随机空间。它不需要分配 workerId,比较适合跨团队、跨系统生成资源标识。
但 UUIDv7 仍然是 128 位。如果数据库核心表更重视索引大小和写入性能,64 位 Snowflake 或号段 ID 通常更合适。
三、Snowflake:本地生成 64 位 ID
Snowflake 的经典结构如下:

0 | 41 位时间戳 | 10 位 workerId | 12 位序列号
对应能力:
- 10 位 workerId:最多 1024 个生成节点。
- 12 位序列号:单节点每毫秒最多 4096 个 ID。
- 41 位毫秒时间戳:大约可使用 69 年。
- ID 使用 64 位有符号整数,最高符号位固定为 0。
Java 示例
下面是一个简化实现,适合理解原理:
public final class SnowflakeIdGenerator {
private static final long EPOCH = 1735689600000L; // 2025-01-01 UTC
private static final long WORKER_BITS = 10L;
private static final long SEQUENCE_BITS = 12L;
private static final long MAX_WORKER_ID = (1L << WORKER_BITS) - 1;
private static final long SEQUENCE_MASK = (1L << SEQUENCE_BITS) - 1;
private static final long WORKER_SHIFT = SEQUENCE_BITS;
private static final long TIMESTAMP_SHIFT = WORKER_BITS + SEQUENCE_BITS;
private final long workerId;
private long sequence;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId < 0 || workerId > MAX_WORKER_ID) {
throw new IllegalArgumentException("invalid workerId: " + workerId);
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new IllegalStateException(
"clock moved backwards: " + (lastTimestamp - timestamp) + "ms"
);
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & SEQUENCE_MASK;
if (sequence == 0) {
timestamp = waitNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
| (workerId << WORKER_SHIFT)
| sequence;
}
private long waitNextMillis(long previousTimestamp) {
long timestamp;
do {
Thread.onSpinWait();
timestamp = System.currentTimeMillis();
} while (timestamp <= previousTimestamp);
return timestamp;
}
}
生产环境不建议直接复制这段代码上线。真正困难的不是位运算,而是 workerId 分配和时钟异常处理。
四、Snowflake 的两个核心风险
1. workerId 冲突
两台机器使用相同 workerId,并且在同一毫秒生成相同序列号,就可能产生重复 ID。
常见分配方式:
- 配置中心静态分配。
- ZooKeeper、etcd 或数据库租约分配。
- Kubernetes StatefulSet 使用稳定的 Pod 序号。
- 独立 ID 服务统一管理 workerId。
不推荐直接使用:
workerId = IP 最后一段 % 1024
原因包括:
- 不同网段可能出现相同尾号。
- 容器 IP 会变化和复用。
- 同一主机可能运行多个生成进程。
- 扩缩容时难以审计是否冲突。
2. 时钟回拨
NTP 校时、虚拟机迁移或人工修改系统时间,都可能导致当前时间小于上次生成 ID 的时间。
常见处理策略:
- 小幅回拨:短暂等待时间追平。
- 中等回拨:切换到预留 workerId。
- 大幅回拨:拒绝发号并告警。
- 使用逻辑时间:保证生成器时间不倒退。
最危险的做法是忽略回拨,继续使用更小的时间戳生成 ID。
Snowflake 通常只能保证单节点递增和全局趋势递增,不能替代业务时间字段。表中仍然应该保留:
created_at DATETIME(3) NOT NULL
五、号段模式:数据库批量分配,本地发号
号段模式不为每个 ID 访问数据库,而是一次申请一批:
服务 A:10001 ~ 11000
服务 B:11001 ~ 12000
服务在内存中使用 AtomicLong 递增。号段快用完时,再向数据库申请下一段。

号段表
CREATE TABLE id_alloc (
biz_tag VARCHAR(64) PRIMARY KEY,
max_id BIGINT NOT NULL,
step INT NOT NULL,
version BIGINT NOT NULL DEFAULT 0,
updated_at DATETIME(3) NOT NULL
);
不同业务使用不同 biz_tag:
order
user
payment
coupon
申请号段时,需要在事务中锁定记录并更新 max_id:
BEGIN;
SELECT max_id, step
FROM id_alloc
WHERE biz_tag = 'order'
FOR UPDATE;
UPDATE id_alloc
SET max_id = max_id + step,
version = version + 1,
updated_at = CURRENT_TIMESTAMP(3)
WHERE biz_tag = 'order';
COMMIT;
假设更新前:
max_id = 10000
step = 1000
当前服务获得:
10001 ~ 11000
数据库中保存的新 max_id 为 11000。
为什么要双缓冲
如果等当前号段完全耗尽后才访问数据库,请求会在切换时出现延迟尖峰。
更稳妥的方式是:
- 当前号段使用到 70%~80%。
- 后台线程提前申请下一号段。
- 当前号段耗尽后立即切换备用号段。
- 再异步补充新的备用号段。
这样数据库短暂不可用时,服务仍能继续消费已经缓存的 ID。
号段浪费是不是问题
服务重启后,未使用完的号段可能被跳过,例如:
已经使用:10001 ~ 10320
未使用:10321 ~ 11000
这些 ID 会形成空洞,但不会重复。
分布式 ID 应该追求唯一,不应该追求绝对连续。
连续 ID 会增加系统协调成本,还可能暴露业务量。
六、实际项目如何选择
单库单表
直接使用数据库自增主键。
不要因为项目使用了 Spring Cloud,就强行引入分布式 ID。
普通 Java 微服务
优先使用成熟的 Snowflake 实现:
- 本地生成,延迟低。
- 使用
BIGINT即可存储。 - 不需要每次访问远程 ID 服务。
前提是能可靠解决 workerId 和时钟回拨。
Kubernetes 动态扩缩容
更适合号段模式,尤其是 Pod 数量变化频繁时。
Snowflake 也可以使用,但需要稳定分配并回收 workerId,不能简单依赖容器 IP。
跨系统资源标识
可以考虑 UUIDv7:
- 不需要中心服务。
- 不需要 workerId。
- 时间有序性好于 UUIDv4。
- 适合公开资源 ID、同步数据和跨组织写入。
日志追踪 ID
UUID、ULID、UUIDv7 或 Snowflake 都可以。
追踪 ID 通常不作为高频业务表的聚簇主键,重点是生成方便和跨服务传递。
七、数据库与接口层注意事项
1. 前端精度
JavaScript 的 Number 最大安全整数是:
2^53 - 1
Snowflake ID 经常超过这个范围。后端返回 JSON 时应将 ID 转成字符串:
{
"id": "1991234567890123456"
}
Spring Boot 可以只对 ID 字段配置序列化:
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
不建议无差别地把所有 long 字段都转成字符串。
2. 不用 ID 代替时间
多节点 Snowflake 不能保证严格业务顺序。排序时应该使用:
ORDER BY created_at, id
3. 不编码敏感业务信息
不建议设计成:
日期 + 地区 + 用户 ID + 商品类型 + 序列号
这会导致:
- ID 结构与业务强耦合。
- 字段位数很快不够用。
- 暴露用户、地区和业务规模信息。
- 未来难以迁移和兼容。
4. 主键与外部编号可以分开
数据库主键可以使用 Snowflake 或号段 ID,展示给用户的订单编号可以单独生成:
数据库主键:1991234567890123456
订单编号:ORD20260717000182
展示编号可以调整格式,内部主键保持稳定。
八、常见错误
使用当前毫秒直接作为 ID
long id = System.currentTimeMillis();
同一毫秒内多个请求会立刻冲突。
要求 ID 连续无空洞
任何缓存、批量分配、失败重试和服务重启都可能产生空洞。要求绝对连续,实际上是在引入全局串行化。
自研算法但没有故障测试
至少需要覆盖:
- 多线程并发。
- 多节点 workerId 冲突。
- 时钟回拨。
- 服务重启。
- 数据库故障。
- 号段切换。
- 网络超时后重复申请。
- ID 服务降级与恢复。
把 ID 服务做成新的单点
集中式 ID 服务必须具备:
- 多实例部署。
- 本地缓存或号段缓存。
- 数据库高可用。
- 超时、重试和熔断。
- 唯一性监控和告警。
九、推荐落地方案
对于大多数 Java 业务系统,可以按以下顺序决策:
- 单库单表:使用数据库自增。
- 常规微服务:使用成熟 Snowflake 实现。
- 容器动态扩缩容、不能接受时钟风险:使用号段模式。
- 跨系统且不希望协调 workerId:使用 UUIDv7。
- 无论选择哪种方案,都单独保存
created_at。 - 64 位 ID 返回前端时统一按字符串处理。
- 不追求连续,不把业务隐私编码进 ID。
真正可靠的分布式 ID 设计,不是位数划分得多漂亮,而是故障发生时仍然不会重复。
参考资料
-
IETF, RFC 9562: Universally Unique IDentifiers (UUIDs)
https://www.rfc-editor.org/rfc/rfc9562.html -
Twitter Engineering, Announcing Snowflake
https://blog.x.com/engineering/en_us/a/2010/announcing-snowflake -
美团技术团队,Leaf——美团点评分布式 ID 生成系统
https://tech.meituan.com/2017/04/21/mt-leaf.html -
美团技术团队,Leaf:美团分布式 ID 生成服务开源
https://tech.meituan.com/2019/03/07/open-source-project-leaf.html -
Meituan-Dianping, Leaf GitHub Repository
https://github.com/Meituan-Dianping/Leaf
分布式 ID 如何设计:从自增 ID 到 Snowflake 与号段模式
https://lautung.com/archives/V1PDcprQ
评论