废话不多说,今天更新场景篇-分布式ID的知识点,做好了开始发车喽!
一、场景篇-分布式ID
1、Q:什么是分布式 ID?为什么在微服务/分库分表体系中必须使用它?
A:分布式 ID 是在多节点、跨机器环境下能够 全局唯一、高可用、可快速生成 的标识符。在微服务或分库分表场景中,单库自增主键会产生 单点瓶颈,且不同库之间的自增值会冲突,导致业务数据无法唯一定位。分布式 ID 通过在每个节点独立生成而不依赖中心数据库,解决了 唯一性、并发性能、水平扩展 等问题。
2、Q: 常见的分布式 ID 生成方案有哪些?请简要对比它们的优缺点?
A: 常见方案如下:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| UUID | 基于随机数或时间戳的 128 位字符串 | 生成简单、无需依赖外部服务、全局唯一 | 长度大、无序、索引性能差、存储开销高 |
| Snowflake(Twitter) | 64 位 long:时间戳 + 机器/数据中心 ID + 序列号 | 高性能(内存生成)、有序、占用 8 字节、易扩展 | 依赖系统时钟,时钟回拨会导致冲突,需要额外处理 |
| Leaf(Segment / Snowflake) | Segment:号段预分配;Snowflake:基于 Snowflake 并通过 ZK 分配 workerId | 支持高并发、号段模式降低 DB 压力、Snowflake 模式保持有序 | 需要额外的 Zookeeper / MySQL 依赖,时钟回拨仍需防护 |
| Redis INCR | Redis 原子自增键 | 极低延迟、实现简单、可做业务前缀 | 依赖 Redis 单点或集群一致性,ID 长度受限,恢复复杂 |
| 数据库自增 + 步长 | 每库自增,步长 = 节点数 | 直接使用 DB,迁移成本低 | 单库瓶颈、扩容困难、跨库冲突风险 |
3、Q: Snowflake 算法的结构是什么?请说明每一部分的位数及含义?
A: Snowflake可以在 约 69 年 内每毫秒生成 4096(2¹²)个唯一 ID,满足多数高并发业务需求。
1 位符号位(固定 0)41 位时间戳(相对自定义纪元的毫秒数)5 位数据中心 ID(区分机房)5 位机器 ID(区分同机房内的机器)12 位序列号(同毫秒内的自增计数)
4、Q: 在 Java 实现 Snowflake 时,需要注意哪些关键点?
A:注意的关键点如下:
位运算:使用左移 () 与按位或 (
<<) 将时间戳、机器 ID、序列号拼装成 64 位 long,确保不产生溢出。时钟回拨检测:在
| 中比较当前时间戳与上一次生成的时间戳,若出现回拨且偏差 ≤ 5 ms,可等待下一毫秒;若偏差更大则抛异常,防止 ID 重复。线程安全:
nextId() 必须是 synchronized(或使用原子变量)以保证并发安全。
nextId()
5、Q:请给出一个最小可运行的 Java Snowflake 核心代码实现示例?
A:核心思路即 时间戳 + 机器/数据中心 ID + 序列号 的位拼装,已在实际项目中广泛使用。
public class SnowflakeIdWorker {
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0)
throw new IllegalArgumentException("worker Id out of range");
if (datacenterId > maxDatacenterId || datacenterId < 0)
throw new IllegalArgumentException("datacenter Id out of range");
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
Thread.sleep(offset << 1);
} catch (InterruptedException ignored) {
throw new RuntimeException(ignored.getMessage());
}
timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards.");
}
} else {
throw new RuntimeException("Clock moved backwards.");
}
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
while ((timestamp = System.currentTimeMillis()) <= lastTimestamp) {}
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
}
6、Q:Leaf 框架提供了哪两种 ID 生成模式?它们各自的工作原理是什么?
A:两种生成模式如下:
Segment(号段)模式:Leaf 从数据库的 表中一次性加载一段连续的 ID(如 1000~1999),在内存中递增使用;当号段耗尽时再向 DB 申请新号段,极大降低 DB 交互频率。Snowflake 模式:Leaf 在 Snowflake 基础上实现,使用 Zookeeper 的有序节点为每个实例分配唯一的 workerId,并在本地通过时间戳+序列号生成有序 ID;同样提供时钟回拨检测机制。
leaf_alloc
7、Q:详细描述 Leaf Segment 模式的完整工作流程?
A:该模式通过 批量预分配 大幅降低数据库写入次数,适合 高并发、对有序性要求不高 的业务。
业务请求 → 调用 Leaf 客户端的 。客户端检查本地缓存的号段是否还有剩余。若缓存不足,客户端向 Leaf Server 发送 号段申请(包含业务标识)。Leaf Server 在 MySQL 表
nextId() 中执行
leaf_alloc,获取新的 max_id。Server 将 起始值 = max_id – step + 1、结束值 = max_id 返回给客户端。客户端把这段连续的 ID 缓存到本地内存,随后直接递增返回给业务调用。当本地号段耗尽,重复第 3 步。
UPDATE leaf_alloc SET max_id = max_id + step WHERE biz_tag = ?
8、Q: Leaf Snowflake 模式是如何解决 workerId 唯一性 与 时钟回拨 的?
A:答案如下:
workerId 唯一性:Leaf 通过 Zookeeper 创建 永久有序节点(),节点的顺序号即为全局唯一的 workerId,保证不同实例不会冲突。时钟回拨:在生成 ID 前比较当前时间戳与上一次记录的时间戳;若回拨且差值 ≤ 5 ms,则等待至时间恢复;若差值更大则直接抛异常,防止重复 ID。
/leaf/snowflake/workerId/
9、Q:使用 Redis INCR 生成业务 ID 的实现方式是什么?它的优势与局限有哪些?
A:答案如下:
优势:Redis 原子自增,毫秒级响应,代码极简,可直接在业务层使用。局限:依赖 Redis 集群的 一致性 与 高可用,若 Redis 故障会导致 ID 生成中断;生成的 ID 长度受限,且不具备全局有序性(跨机器时间不一致时会出现跳跃)。
public String nextId(String prefix, int length) {
String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
String key = prefix + date; // 业务前缀 + 日期
Long seq = redisTemplate.opsForValue().increment(key, 1);
redisTemplate.expire(key, 1, TimeUnit.DAYS); // 按天过期
return key + StringUtils.leftPad(seq.toString(), length, '0');
}
10、Q: 为什么直接使用 数据库自增 ID 在分库分表环境下不推荐?有哪些改进思路?
A:因为直接使用自增id会有单点瓶颈:自增依赖单库锁,无法支撑大并发写入。以及跨库冲突:不同库的自增值会重复,导致全局唯一性失效。
改进思路:
步长 + 偏移:为每个库设置不同的自增步长(如 10),并在业务层加上库编号偏移,保证全局唯一。号段预分配(类似 Leaf Segment)或 Snowflake,彻底摆脱单库依赖。
11、Q:在 高并发 场景下,如何保证 ID 的 唯一性 与 有序性?请列出常用组合方案?
A:这些方案均在 Java 生态中有成熟实现(Spring Cloud、MyBatis‑Plus、Leaf、Redis 客户端等),可根据业务对 性能、可用性、时序需求 进行选型。
| 组合方案 | 关键技术 | 适用场景 |
|---|---|---|
| Snowflake + Zookeeper | Snowflake 位拼装 + ZK 分配 workerId,解决 workerId 冲突 & 时钟回拨 | 需要全局有序、毫秒级唯一 ID(订单、日志) |
| Leaf Snowflake | 同上,Leaf 已封装 ZK 与回拨处理 | 大型互联网业务,已有 Leaf 基础设施 |
| Leaf Segment + MySQL | 号段预分配,降低 DB 交互 | 对有序性要求不高,但需要 批量高速(计数、序列) |
| Redis INCR + 前缀 | Redis 原子自增 | 业务对有序性要求不强,且已有 Redis 集群 |
| UIDGenerator | 基于 Snowflake 改进,支持自定义时间戳、机器码 | 对时钟回拨有更严格容错需求 |
12、Q:在实际项目中,选择哪种分布式 ID 方案时应考虑哪些决策因素?请给出评估维度并说明每个维度的影响?
A:决策建议:
高并发、强有序 → Snowflake(或 Leaf‑Snowflake)业务对有序性不敏感、已有 Redis → Redis INCR快速落地、无额外依赖 → UUID(仅作请求追踪)已有 MySQL、对成本敏感 → Segment 号段(Leaf)或 DB 步长方案
| 维度 | 说明 |
|---|---|
| 性能(TPS) | 生成 ID 的吞吐量需求。Snowflake、Leaf‑Snowflake、Redis INCR 均可达 百万级 TPS;而数据库自增受限。 |
| 有序性 | 是否需要 时间递增(如订单号)。Snowflake、Leaf‑Snowflake 提供严格递增;UUID、Redis INCR(跨机器)不保证全局有序。 |
| 时钟回拨容错 | 系统时间回拨时是否会产生冲突。Snowflake 需要自行处理回拨;Leaf‑Snowflake 已内置回拨检测。 |
| 部署复杂度 | 是否需要额外的 Zookeeper / MySQL / Redis。UUID 最低,Snowflake 只需配置机器 ID,Leaf‑Snowflake 需要 ZK/DB 支持。 |
| 业务可扩展性 | 随着节点增多是否易于扩容。Segment 模式通过号段批量分配,扩容时只需增加 DB/Redis 容量;Snowflake 通过增加机器 ID 位数即可。 |
| 成本 | 维护成本、运维成本。使用已有 Redis/DB 成本低;引入 Zookeeper 或专门的 Leaf Server 成本相对更高。 |
二、结语
这是一个系列专栏,场景题分布式ID就写到这里,有需要的可以关注一下,下期更新场景题-分布式定时任务篇了!
