分布式 ID 生成:破解数据唯一性难题

91 阅读3分钟

在分布式系统中,数据库分库分表后,传统自增 ID(AUTO_INCREMENT)无法保证全局唯一 —— 不同分表可能生成相同 ID,导致数据冲突。分布式 ID 生成技术,就是为跨库、跨表的全局数据提供唯一标识的解决方案。

分布式 ID 的核心要求

一个可靠的分布式 ID 需满足:

  • 全局唯一:这是基本要求,绝不能重复
  • 趋势递增:便于数据库建立索引(B + 树索引偏好递增数据)
  • 高性能:生成速度快,不成为系统瓶颈
  • 高可用:生成服务不能单点故障
  • 可追溯:最好能包含时间戳等信息,便于问题排查

主流实现方案

1. 雪花算法(Snowflake):性能与灵活性兼顾

这是 Twitter 开源的分布式 ID 生成算法,生成 64 位 Long 型 ID,结构如下:

  • 1 位符号位(固定 0,确保正数)

  • 41 位时间戳(毫秒级,可使用约 69 年)

  • 10 位机器 ID(支持 1024 台机器)

  • 12 位序列号(每台机器每秒可生成 4096 个 ID)

优势

  • 纯内存生成,性能极高(单机每秒可达百万级)

  • 包含时间戳,支持按时间排序

  • 可自定义机器 ID 位数,适应不同集群规模

代码简化示例

public class SnowflakeIdGenerator {
    private final long workerId; // 机器ID
    private final long datacenterId; // 数据中心ID
    private long sequence = 0L; // 序列号
    private long lastTimestamp = -1L; // 上次生成ID的时间戳

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        // 校验workerId和datacenterId范围
        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");
        }
        // 同一毫秒内,序列号递增
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & 0xFFF; // 12位序列号掩码
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp); // 等待下一毫秒
            }
        } else {
            sequence = 0L; // 不同毫秒,序列号重置
        }
        lastTimestamp = timestamp;
        // 组合ID:时间戳 << 22 | 数据中心ID << 17 | 机器ID << 12 | 序列号
        return (timestamp - 1288834974657L) << 22 
               | (datacenterId << 17) 
               | (workerId << 12) 
               | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

注意事项

  • 时钟回拨问题:服务器时钟若倒退,可能生成重复 ID,需通过算法容错(如等待时钟追上)或引入闰秒处理
  • 机器 ID 分配:需确保集群中机器 ID 唯一,可通过配置中心或启动时争抢分布式锁获取

2. 数据库号段模式:简单可靠但有性能瓶颈

原理:从数据库预分配一段 ID(如 1-1000),本地缓存使用,用完再申请下一段。

  • 优势:实现简单,ID 绝对递增
  • 劣势:数据库可能成为瓶颈,需做好主从备份

3. Redis 自增:分布式环境下的 “计数器”

利用 Redis 的INCR命令生成自增 ID,结合 Lua 脚本确保原子性:

    // 生成ID示例(RedisTemplate)
    Long id = redisTemplate.opsForValue().increment("distributed:id:order", 1);
  • 优势:实现简单,性能高于数据库
  • 劣势:Redis 若宕机可能丢失 ID 生成状态,需持久化配置(AOF+RDB)

方案选择建议

  • 高并发场景(如电商订单):优先选雪花算法,兼顾性能与 ID 有序性

  • 中小规模系统:Redis 自增或数据库号段模式更易实现

  • 需兼容旧系统:可考虑 UUID(但无序,不适合做数据库主键)或基于业务字段拼接(如用户 ID + 时间戳 + 随机数)

分布式 ID 生成看似简单,实则关乎系统稳定性 —— 一个重复 ID 可能导致订单混乱、支付异常等严重问题,需结合业务规模和性能需求,选择最适合的方案。