分布式ID生成方案解析 问题背景 在分布式系统中,需要全局唯一的ID标识: 数据库主键 订单号、流水号 消息追踪ID 分布式锁标识 传统方案问题: UUID:无序、长度长、无业务含义 数据库自增:单机瓶颈、分库分表困难 Redis自增:依赖Redis可用性 主流方案对比 Snowflake(雪花算法) Twitter开源,基于时间戳+机器ID+序列号 结构(64位Long): 代码实现: 优点: 高性能:单机每秒可生成百万ID 有序递增:按时间趋势递增 不依赖第三方组件 缺点: 依赖系统时钟:时钟回拨会生成重复ID 机器ID分配:需要集中管理 改进: 引入Zookeeper管理WorkerID 使用百度UidGenerator(优化了位分配)
在分布式系统中,需要全局唯一的ID标识:
传统方案问题:
Twitter开源,基于时间戳+机器ID+序列号
结构(64位Long):
0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000 ↑ ↑ ↑ ↑ ↑ | | | | | 符号位 41位时间戳 5位 5位 12位 unused datacenter worker 序列号
代码实现:
public class SnowflakeIdGenerator { private final long twepoch = 1288834974657L; // 起始时间戳 private final long datacenterIdBits = 5L; private final long workerIdBits = 5L; private final long sequenceBits = 12L; private final long maxWorkerId = ~(-1L << workerIdBits); private final long maxDatacenterId = ~(-1L << datacenterIdBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨异常"); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & ~(-1L << sequenceBits); if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << (datacenterIdBits + workerIdBits + sequenceBits)) | (datacenterId << (workerIdBits + sequenceBits)) | (workerId << sequenceBits) | sequence; } private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } }
优点:
缺点:
改进:
原理:预分配ID段,降低数据库压力
表结构:
CREATE TABLE id_segment ( biz_type VARCHAR(64) PRIMARY KEY, max_id BIGINT NOT NULL, step INT NOT NULL, version INT NOT NULL );
获取ID逻辑:
public class SegmentIdGenerator { @Transactional public long nextId(String bizType) { // 1. 查询当前号段 IdSegment segment = repository.selectByBizType(bizType); // 2. CAS更新max_id int updated = repository.updateMaxId( bizType, segment.getMaxId() + segment.getStep(), segment.getVersion() ); if (updated == 0) { // 乐观锁失败,重试 return nextId(bizType); } // 3. 返回分配的ID return segment.getMaxId(); } }
双Buffer优化(美团Leaf):
当前号段: [1000, 2000] 正在使用 预分配号段: [2000, 3000] 预加载
优点:
缺点:
原理:利用Redis原子递增命令
基本实现:
# 直接递增 INCR order:id # 设置步长(分布式场景) INCRBY order:id 1000
Java代码:
public class RedisIdGenerator { private RedisTemplate<String, Long> redisTemplate; public long nextId(String key) { return redisTemplate.opsForValue().increment(key); } // 支持步长的分布式生成 public long nextId(String key, long step) { return redisTemplate.opsForValue().increment(key, step); } }
优点:
缺点:
RFC 9562标准,时间有序UUID
结构:
0111xxxx xxxxxxxx xxxxxxxx xxxxxxxx ↑ ↑ | 48位时间戳 UUID版本(v7) xxxxxxxx xxxxxxxxxxxxxxx xxxx ↑ 变体
代码:
import java.util.UUID; public class UUIDv7Generator { public static String generate() { // 使用Java 21+内置支持 return UUID.randomUUID().toString(); } }
优点:
缺点:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 高并发、订单ID | Snowflake/Leaf | 性能高、有序 |
| 简单场景 | Redis INCR | 实现简单 |
| 需要业务含义 | 号段模式 | 可自定义规则 |
| 跨系统 | UUID v7 | 标准化、无冲突 |
机器ID分配:
时钟回拨处理:
号段预热:
监控告警:
降级方案: