分布式ID生成方案解析


文档摘要

分布式ID生成方案解析 问题背景 在分布式系统中,需要全局唯一的ID标识: 数据库主键 订单号、流水号 消息追踪ID 分布式锁标识 传统方案问题: UUID:无序、长度长、无业务含义 数据库自增:单机瓶颈、分库分表困难 Redis自增:依赖Redis可用性 主流方案对比 Snowflake(雪花算法) Twitter开源,基于时间戳+机器ID+序列号 结构(64位Long): 代码实现: 优点: 高性能:单机每秒可生成百万ID 有序递增:按时间趋势递增 不依赖第三方组件 缺点: 依赖系统时钟:时钟回拨会生成重复ID 机器ID分配:需要集中管理 改进: 引入Zookeeper管理WorkerID 使用百度UidGenerator(优化了位分配)

分布式ID生成方案解析

问题背景

在分布式系统中,需要全局唯一的ID标识:

  • 数据库主键
  • 订单号、流水号
  • 消息追踪ID
  • 分布式锁标识

传统方案问题

  • UUID:无序、长度长、无业务含义
  • 数据库自增:单机瓶颈、分库分表困难
  • Redis自增:依赖Redis可用性

主流方案对比

1. Snowflake(雪花算法)

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
  • 有序递增:按时间趋势递增
  • 不依赖第三方组件

缺点

  • 依赖系统时钟:时钟回拨会生成重复ID
  • 机器ID分配:需要集中管理

改进

  • 引入Zookeeper管理WorkerID
  • 使用百度UidGenerator(优化了位分配)
  • 美团Leaf(基于Snowflake,解决时钟回拨)

2. 数据库号段模式

原理:预分配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] 预加载

优点

  • 实现简单
  • 性能可控(号段大小可调)

缺点

  • 号段耗尽需访问数据库
  • ID不连续(重启后跳跃)

3. Redis INCR

原理:利用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); } }

优点

  • 实现简单
  • 性能高(Redis单机10W+ QPS)

缺点

  • 依赖Redis可用性
  • 集群模式下需考虑槽位分配

4. UUID v7

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(); } }

优点

  • 标准:RFC规范
  • 有序:按时间排序
  • 无需协调:分布式友好

缺点

  • 长度较长(36字符)
  • Java 21以下需第三方库

方案选型

场景 推荐方案 理由
高并发、订单ID Snowflake/Leaf 性能高、有序
简单场景 Redis INCR 实现简单
需要业务含义 号段模式 可自定义规则
跨系统 UUID v7 标准化、无冲突

最佳实践

  1. 机器ID分配

    • 小规模:配置文件管理
    • 大规模:Zookeeper/数据库注册
  2. 时钟回拨处理

    • 拒绝生成并告警
    • 等待时钟追上
    • 使用备用WorkerID
  3. 号段预热

    • 双Buffer预加载
    • 动态调整步长
  4. 监控告警

    • ID生成速率
    • 时钟偏移
    • 号段剩余量
  5. 降级方案

    • Snowflake降级为号段模式
    • Redis不可用降级为数据库

发布者: 作者: 转发
评论区 (0)
U