分布式 ID
分布式系统中,数据库自增主键仅能保证单库唯一,跨库分表场景必须使用全局唯一 ID 生成方案。核心要求:全局唯一、趋势递增(减少 B+ 树分裂)、高性能、高可用。
Snowflake 算法
Twitter 开源,64 bit 整型,结构如下:
0 | 41 bit | 10 bit | 12 bit
符号位(0) | 毫秒时间戳 | 机器 ID | 序列号
| 约 69 年 | 1024 台机器 | 每毫秒 4096 个
纯 Java 实现:
public class SnowflakeIdGenerator {
private static final long EPOCH = 1700000000000L; // 自定义纪元
private static final long WORKER_BITS = 5L;
private static final long DATACENTER_BITS= 5L;
private static final long SEQUENCE_BITS = 12L;
private static final long MAX_WORKER_ID = ~(-1L << WORKER_BITS); // 31
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_BITS); // 31
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); // 4095
private static final long WORKER_SHIFT = SEQUENCE_BITS; // 12
private static final long DATACENTER_SHIFT = SEQUENCE_BITS + WORKER_BITS; // 17
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_BITS + DATACENTER_BITS; // 22
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private long lastStamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > MAX_WORKER_ID || workerId < 0)
throw new IllegalArgumentException("workerId out of range");
if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0)
throw new IllegalArgumentException("datacenterId out of range");
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long now = System.currentTimeMillis();
if (now < lastStamp) throw new RuntimeException("时钟回拨,拒绝生成 ID");
if (now == lastStamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0) now = waitNextMillis(lastStamp); // 同毫秒序号耗尽,等待下一毫秒
} else {
sequence = 0L;
}
lastStamp = now;
return ((now - EPOCH) << TIMESTAMP_SHIFT)
| (datacenterId << DATACENTER_SHIFT)
| (workerId << WORKER_SHIFT)
| sequence;
}
private long waitNextMillis(long lastStamp) {
long ms = System.currentTimeMillis();
while (ms <= lastStamp) ms = System.currentTimeMillis();
return ms;
}
}问题:依赖本地时钟,时钟回拨会导致 ID 重复。
美团 Leaf
Leaf 提供两种模式,可同时开启,按场景选用。
Leaf-Segment(数据库号段模式)
数据库每次批量预取一个号段(如 1000 个 ID),用完再取,减少数据库压力。双 Buffer 机制:当前号段剩余 10% 时异步加载下一号段,实现无缝切换。
CREATE TABLE `leaf_alloc` (
`biz_tag` VARCHAR(128) NOT NULL,
`max_id` BIGINT NOT NULL DEFAULT 1,
`step` INT NOT NULL COMMENT '步长,每次分配的号段大小',
`description` VARCHAR(256),
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;
INSERT INTO leaf_alloc(biz_tag, max_id, step, description)
VALUES ('order', 1, 2000, '订单 ID');leaf:
name: order-service
segment:
enable: true
url: jdbc:mysql://localhost:3306/leaf_db
username: root
password: root优点:ID 全局趋势递增,可读性强,与 DB 同步频率低;缺点:强依赖 DB,号段耗尽瞬间有短暂延迟。
Leaf-Snowflake(Snowflake 模式)
使用 ZooKeeper 分配 workerID(解决 Snowflake 机器 ID 手动配置问题),启动时向 ZK 注册,本地缓存 workerID。
leaf:
name: order-service
snowflake:
enable: true
zk-address: 127.0.0.1:2181
port: 8090@Autowired
private SegmentService segmentService;
@Autowired
private SnowflakeService snowflakeService;
// 号段模式
long id = segmentService.getId("order");
// Snowflake 模式
long id = snowflakeService.getId();百度 uid-generator
基于 Snowflake,Worker ID 通过 DB 分配(启动时插入一行,以行 ID 作为 workerID),支持时钟回拨(拒绝发放 + 等待修复)。提供 CachedUidGenerator 预生成 ID 填充 RingBuffer,异步取用,吞吐极高。
各方案对比
| 方案 | 依赖 | 趋势递增 | 时钟回拨 | QPS | 适用场景 |
|---|---|---|---|---|---|
| 数据库自增 | MySQL | 是 | 无影响 | 低 | 单库、低并发 |
| Snowflake | 无 | 是(毫秒级) | 有风险 | 极高 | 独立部署、无 ZK |
| Leaf-Segment | MySQL | 是(号段内单调) | 无影响 | 高 | 通用业务 ID |
| Leaf-Snowflake | ZooKeeper | 是 | 有风险 | 极高 | 已有 ZK 基础设施 |
| uid-generator | MySQL | 是 | 有处理 | 极高 | 高吞吐场景 |
| Redis INCR | Redis | 是 | 无影响 | 高 | 简单场景,需持久化 |
Redis INCR 方案
简单场景可用 Redis INCRBY 批量获取号段:
// 每次从 Redis 批量拿 1000 个 ID
long maxId = redisTemplate.opsForValue().increment("order:id:seq", 1000);
long minId = maxId - 1000 + 1;
// 本地使用 [minId, maxId] 区间的 ID注意:Redis 持久化配置不当时,重启可能回退 ID,需配合 AOF。
相关链接
- ShardingSphere — 分库分表场景下通常需要分布式 ID
- Zookeeper — Leaf-Snowflake / uid-generator 的 WorkerID 注册中心
- 分布式 ← 返回分布式目录