分库分表与微服务拆分后,单机自增ID彻底失效:多库主键冲突、分片键无法平衡路由、业务含义暴露数据规模。分布式ID的选型标准很明确:全局唯一、趋势递增(利于B+树索引写入性能)、高可用、高并发下低延迟。主流方案集中在雪花算法(Snowflake)与号段模式(Leaf)两条路线上,数据库自增步长方案在中小并发场景仍有生存空间。
方案对比:雪花算法、号段模式与数据库步长自增
三种方案的适用边界:
- 雪花算法:64bit本地生成,无网络开销,单机理论400万QPS。缺陷是时钟回拨会生成重复ID,workerId分配需要额外协调。
- 号段模式(Leaf-segment):从数据库批量取号段缓存在本地,双buffer预加载保证平滑。依赖数据库但不强依赖,取号段间隔容忍DB短暂抖动。
- 数据库自增步长:N个实例设置不同起始值与相同步长(start=1,2,3…N,step=N)。配置最简单,扩容困难,适合并发1万以下且实例数稳定的场景。
趋势递增的意义常被低估:InnoDB主键是聚簇索引,乱序UUID作为主键会导致频繁页分裂,写入性能比趋势递增ID低数倍,且空间碎片率高。除主键场景外,订单号等对外ID还需要额外脱敏处理,避免暴露单量规模。
雪花算法结构设计与时钟回拨防护
64bit的标准分配:1bit符号位 + 41bit时间戳(毫秒级,可用约69年)+ 10bit机器ID(1024节点)+ 12bit序列号(每毫秒4096个)。时钟回拨是雪花算法的硬伤:NTP校时或运维误操作导致系统时间回退,同毫秒序列会生成重复ID。工程上的防护分级处理:
public class SnowflakeIdGen {
private final long epoch = 1735689600000L; // 2025-01-01基准
private final long workerIdBits = 10L;
private final long sequenceBits = 12L;
private final long maxWorkerId = ~(-1L << workerIdBits);
private final long sequenceMask = ~(-1L << sequenceBits);
private long workerId;
private long lastTimestamp = -1L;
private long sequence = 0L;
private long maxBackwardMs = 5L; // 最大容忍回拨毫秒数
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= maxBackwardMs) {
// 小幅回拨:自旋等待到上次时间戳
try { wait(offset << 1); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new IllegalStateException("clock moved backwards");
}
} else {
// 大幅回拨:直接拒绝服务,触发告警人工介入
throw new IllegalStateException(
"clock moved backwards " + offset + "ms, refuse id gen");
}
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒序列耗尽,等待下一毫秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - epoch) << (workerIdBits + sequenceBits))
| (workerId << sequenceBits)
| sequence;
}
}
小回拨(毫秒级)自旋等待,大回拨直接拒绝并告警,是保守但可靠的策略。彻底消除时钟依赖的替代方案:把41bit时间戳换成”逻辑时钟”或引入美团的Leaf-snowflake改进版——它用Zookeeper顺序节点分配workerId,并定期上报时钟水位,发现节点时钟偏差即告警下线,避免重复ID流入业务。
Leaf-segment号段模式:双buffer预加载实现
号段模式从数据库一次取一批号缓存到本地服务内存,用完再取。核心表只有一条记录:
CREATE TABLE leaf_alloc (
biz_tag VARCHAR(64) NOT NULL,
max_id BIGINT NOT NULL,
step INT NOT NULL DEFAULT 1000,
PRIMARY KEY (biz_tag)
);
-- 取号段用乐观锁更新
UPDATE leaf_alloc SET max_id = max_id + step
WHERE biz_tag = 'order';
SELECT max_id FROM leaf_alloc WHERE biz_tag = 'order';
单buffer的问题在号段切换瞬间:DB抖动时取号请求阻塞。双buffer方案在当前号段消耗到10%时,异步加载下一个号段,切换完全在本地内存完成:
public class SegmentBuffer {
private volatile Segment currentSeg; // 当前消耗的号段
private volatile Segment nextSeg; // 预加载的下一号段
private volatile boolean nextReady = false;
private final int threshold = 10; // 剩余10%触发预加载
public long getNext() {
if (currentSeg.remainPercent() <= threshold && !nextReady) {
// 异步线程加载nextSeg,不阻塞取号路径
loadNextAsync();
}
long id = currentSeg.next();
if (currentSeg.exhausted()) {
if (nextReady) {
currentSeg = nextSeg;
nextReady = false;
loadNextAsync(); // 继续预加载下一棒
} else {
// 罕见场景:预加载未完成,同步取号
currentSeg = fetchSegmentSync();
}
}
return id;
}
}
号段模式的动态step同样重要:高峰期step=10000,低峰step=200,避免高峰号段频繁耗尽访问DB、低峰大量号段浪费(重启后buffer内ID作废)。美团Leaf开源实现(github.com/Meituan-Dianping/Leaf)提供了生产可用的完整版本,包含segment与snowflake双模式,可直接引入依赖。
workerId分配与多机房部署方案
雪花算法的10bit机器ID在容器化环境下的分配难题:Pod频繁重建,静态配置无法管理。三种方案的取舍:
- Zookeeper顺序节点:节点启动时创建顺序临时节点,序号即workerId,断连自动释放。经典方案,依赖多一套ZK集群。
- 数据库表分配:worker_id表记录占用状态,启动时事务抢占一个空闲ID,心跳续约。实现简单,DB压力大时需要优化。
- 复用主键:MySQL实例自身主键作为workerId来源,适合有DB配额的业务。
多机房场景把10bit拆成5bit机房号+5bit机器号,同城双机房各32个节点位,足够绝大多数业务规模。异地多活且规模超出1024节点时,在ID高位增加机房bit或改用128bit方案。
有序性与性能验证:压测与索引写入收益测试
验证趋势递增的收益可以用sysbench对比插入性能:
# 趋势递增ID主键 vs 随机UUID主键,1000万行插入
sysbench oltp_insert --mysql-host=127.0.0.1 \
--tables=4 --table-size=10000000 prepare
# 观察:
# 1. 插入TPS差距与Buffer Pool命中率的差异
# 2. SHOW TABLE STATUS 中 Data_free 碎片率对比
对ID生成服务本身的压测要点:单机雪花算法在synchronized下约200-400万QPS,号段模式受号段加载影响,压测时把step调到业务峰值1分钟消耗量以上。监控层面给ID服务配置两个告警:号段加载耗时P99超过1秒、时钟偏差超过阈值,这两个信号分别对应DB隐患与时钟回拨风险。
选型结论收敛成一句话:单体或小规模微服务直接数据库步长;常规微服务优先Leaf-segment,实现简单且趋势递增;超高并发与时钟不可控环境用改进型雪花算法,把workerId协调与时钟监控做扎实。任何方案上线前,把”重复ID”的兜底校验(业务表唯一索引)保留好,这是最后一道防线。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-id-sheng-cheng-shi-zhan-leaf-xue-hua-suan-fa-yu/