分布式ID生成雪花算法
分布式ID生成雪花算法
为什么需要分布式ID
在分布式系统中,传统的数据库自增ID方案存在以下问题:
- 单点故障:数据库宕机导致ID生成失败
- 性能瓶颈:高并发下数据库成为瓶颈
- 数据安全:自增ID暴露业务信息
- 扩展困难:分库分表后ID可能重复
常见分布式ID方案
UUID
// UUID生成
public class UUIDGenerator {
public static String generateUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
public static Long generateUUIDAsLong() {
UUID uuid = UUID.randomUUID();
return uuid.getMostSignificantBits() & Long.MAX_VALUE;
}
}
// 使用示例
String id = UUIDGenerator.generateUUID(); // "550e8400e29b41d4a716446655440000"
Long longId = UUIDGenerator.generateUUIDAsLong(); // 正数Long值
优点:
- 简单易用
- 无需协调
- 性能高
缺点:
- 无序,不适合做主键
- 字符串存储,空间占用大
- 不可读,调试困难
数据库自增
-- 创建序列表
CREATE TABLE id_generator (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
biz_type VARCHAR(64) NOT NULL,
max_id BIGINT NOT NULL,
step INT NOT NULL,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 初始化业务类型
INSERT INTO id_generator (biz_type, max_id, step) VALUES ('order', 0, 1000);
-- 获取批量ID
SELECT max_id, step FROM id_generator WHERE biz_type = 'order' FOR UPDATE;
UPDATE id_generator SET max_id = max_id + step WHERE biz_type = 'order';
// 数据库自增ID生成器
@Component
public class DatabaseIdGenerator {
private final JdbcTemplate jdbcTemplate;
private final Map<String, IdRange> idRanges = new ConcurrentHashMap<>();
public Long generateId(String bizType) {
IdRange range = idRanges.get(bizType);
if (range == null || range.isExhausted()) {
range = fetchNewRange(bizType);
idRanges.put(bizType, range);
}
return range.next();
}
private IdRange fetchNewRange(String bizType) {
// 获取新的ID范围
IdRange range = jdbcTemplate.queryForObject(
"SELECT max_id, step FROM id_generator WHERE biz_type = ? FOR UPDATE",
new Object[]{bizType},
(rs, rowNum) -> new IdRange(
rs.getLong("max_id"),
rs.getLong("step")
)
);
// 更新数据库
jdbcTemplate.update(
"UPDATE id_generator SET max_id = max_id + step WHERE biz_type = ?",
bizType
);
return range;
}
private static class IdRange {
private long current;
private final long max;
public IdRange(long start, long step) {
this.current = start;
this.max = start + step;
}
public synchronized long next() {
if (current >= max) {
throw new IdExhaustedException("ID range exhausted");
}
return current++;
}
public boolean isExhausted() {
return current >= max;
}
}
}
Redis自增
// Redis自增ID生成器
@Component
public class RedisIdGenerator {
private final StringRedisTemplate redisTemplate;
public Long generateId(String bizType) {
String key = "id:" + bizType;
return redisTemplate.opsForValue().increment(key);
}
public Long generateId(String bizType, long step) {
String key = "id:" + bizType;
Long id = redisTemplate.opsForValue().increment(key, step);
return id - step + 1; // 返回范围的起始值
}
}
雪花算法
雪花算法(Snowflake)是Twitter开源的分布式ID生成算法,它生成一个64位的Long型ID。
ID结构
0 | 41位时间戳 | 5位数据中心ID | 5位工作机器ID | 12位序列号
- 符号位:1位,始终为0
- 时间戳:41位,毫秒级时间戳,可用约69年
- 数据中心ID:5位,最多支持32个数据中心
- 工作机器ID:5位,每个数据中心最多支持32个工作机器
- 序列号:12位,同一毫秒内最多支持4096个ID
// 雪花算法实现
public class SnowflakeIdGenerator {
// 起始时间戳 (2020-01-01 00:00:00)
private final long twepoch = 1577836800000L;
// 各部分的位数
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long sequenceBits = 12L;
// 最大值
private final long maxWorkerId = ~(-1L << workerIdBits); // 31
private final long maxDatacenterId = ~(-1L << datacenterIdBits); // 31
private final long sequenceMask = ~(-1L << sequenceBits); // 4095
// 位移
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 组件
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("Worker ID must be between 0 and " + maxWorkerId);
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException("Datacenter ID must be between 0 and " + maxDatacenterId);
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
// 时钟回拨检查
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id for " +
(lastTimestamp - timestamp) + " milliseconds");
}
// 同一毫秒内序列号递增
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 序列号用完,等待下一毫秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 组装ID
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
// 从ID中提取信息
public long extractTimestamp(long id) {
return (id >> timestampLeftShift) + twepoch;
}
public long extractDatacenterId(long id) {
return (id >> datacenterIdShift) & maxDatacenterId;
}
public long extractWorkerId(long id) {
return (id >> workerIdShift) & maxWorkerId;
}
public long extractSequence(long id) {
return id & sequenceMask;
}
}
使用示例
// 创建雪花算法生成器
@Component
public class SnowflakeIdService {
private final SnowflakeIdGenerator generator;
public SnowflakeIdService(@Value("${snowflake.worker-id:1}") long workerId,
@Value("${snowflake.datacenter-id:1}") long datacenterId) {
this.generator = new SnowflakeIdGenerator(workerId, datacenterId);
}
public Long generateId() {
return generator.nextId();
}
public void analyzeId(long id) {
System.out.println("Timestamp: " + new Date(generator.extractTimestamp(id)));
System.out.println("Datacenter ID: " + generator.extractDatacenterId(id));
System.out.println("Worker ID: " + generator.extractWorkerId(id));
System.out.println("Sequence: " + generator.extractSequence(id));
}
}
// 使用
@Service
public class OrderService {
@Autowired
private SnowflakeIdService idService;
public Order createOrder(CreateOrderRequest request) {
Long orderId = idService.generateId();
// 使用orderId创建订单
return new Order(orderId, request);
}
}
时钟回拨问题
时钟回拨可能导致ID重复,需要处理这个问题。
// 处理时钟回拨
public class SnowflakeIdGeneratorWithClockBackward {
private long lastTimestamp = -1L;
private long clockBackwardCount = 0;
private final long maxClockBackward = 5; // 最大允许回拨5毫秒
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
// 时钟回拨处理
if (timestamp < lastTimestamp) {
long backward = lastTimestamp - timestamp;
if (backward <= maxClockBackward) {
// 在允许范围内,等待时间追上
clockBackwardCount++;
timestamp = waitUntilClockCatchUp(lastTimestamp);
} else {
// 回拨过大,抛出异常
throw new RuntimeException("Clock moved backwards. Refusing to generate id for " +
backward + " milliseconds");
}
}
// 其他逻辑...
return generateId(timestamp);
}
private long waitUntilClockCatchUp(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
雪花算法变种
美团Leaf
// Leaf Segment模式
public class LeafSegment {
private long maxNum;
private long step;
private long currentNum;
private long nextStep;
public LeafSegment(long maxNum, long step) {
this.maxNum = maxNum;
this.step = step;
this.currentNum = 0;
this.nextStep = 0;
}
public synchronized long nextId() {
if (currentNum >= maxNum) {
// 当前号段用完,切换到下一个号段
currentNum = nextNum();
nextStep = step;
}
return currentNum++;
}
private long nextNum() {
// 获取新的号段
return fetchNextSegment();
}
}
// Leaf Snowflake模式
public class LeafSnowflake {
private final SnowflakeIdGenerator snowflake;
private final ZkClient zkClient;
public LeafSnowflake(long workerId, long datacenterId) {
this.snowflake = new SnowflakeIdGenerator(workerId, datacenterId);
this.zkClient = new ZkClient("zookeeper:2181");
// 注册workerId
registerWorkerId();
}
private void registerWorkerId() {
// 从ZooKeeper获取workerId
String path = "/snowflake/" + datacenterId;
List<String> children = zkClient.getChildren(path);
// 找到可用的workerId
for (int i = 0; i < 32; i++) {
String workerPath = path + "/" + i;
if (!zkClient.exists(workerPath)) {
// 创建临时节点
zkClient.createEphemeral(workerPath, InetAddress.getLocalHost().getHostName());
this.workerId = i;
break;
}
}
}
public long nextId() {
return snowflake.nextId();
}
}
方案对比
| 方案 | 性能 | 可用性 | 有序性 | 长度 | 适用场景 |
|---|---|---|---|---|---|
| UUID | 高 | 高 | 无序 | 128位 | 分布式环境,无需有序 |
| 数据库自增 | 中等 | 低 | 有序 | 64位 | 单机或小规模系统 |
| Redis自增 | 高 | 中等 | 有序 | 64位 | 需要简单有序ID |
| 雪花算法 | 高 | 高 | 有序 | 64位 | 分布式系统,高并发 |
最佳实践
- 选择合适的方案:根据业务需求选择合适的ID生成方案
- 处理异常:处理时钟回拨、ID耗尽等异常情况
- 监控告警:监控ID生成器的状态和性能
- 数据迁移:考虑数据迁移时的ID兼容性
- 安全考虑:避免ID暴露敏感业务信息