AI摘要

文章对比Redis自增、号段模式、雪花算法三种分布式ID方案:Redis上手快但高并发易成瓶颈;号段模式一次取批号,性能高、最稳,适合分库分表;雪花算法纯内存、无依赖、吞吐量最高,但需解决机器ID分配与时间回拨。选型按并发量级:低并发用Redis,业务系统用号段,高并发用雪花。

作为有着多年经验的 Java 后端开发,在做微服务、分库分表、订单流水、操作日志这些业务时,分布式 ID 是绕不开的基础组件。以前单体应用用数据库自增 ID 就能搞定,但是到了分布式环境,数据库自增 ID 会出现重复、扩展性差、数据量一大就扛不住等问题。

这篇文章我不讲什么“生产突发故障”,只聊​平常开发、联调、压测过程中真实遇到的问题、踩过的坑​,把雪花算法、号段模式、Redis 实现这三种最常用的分布式 ID 方案,从原理、代码、优缺点、适用场景一次性讲透,你看完就能直接在项目里落地。

一、为什么需要分布式 ID?

平常开发时,我们很容易遇到这些场景:

  • 订单表分库分表后,多个库的自增 ID 会重复;
  • 多个服务同时生成业务编号(如支付号、退款号、物流号);
  • 前端需要唯一标识做幂等、去重;
  • 日志、链路追踪需要全局唯一 ID。

一个合格的分布式 ID,一般要满足:

  1. 全局唯一​,不重复;
  2. 趋势递增​,方便数据库索引;
  3. 高可用​,不依赖单点;
  4. 高性能​,能抗住高并发;
  5. 长度可控​,最好是数字或固定长度字符串。

下面分别讲三种最常用的实现。

二、Redis 实现分布式 ID

2.1 实现思路

平常开发里最简单、上手最快的就是 Redis 方案。

核心原理:

  • 利用 Redis 单线程原子自增 特性:INCR / INCRBY
  • 保证多服务、多线程调用时不会重复;
  • 可以拼接前缀、时间戳,做成有业务意义的 ID。

2.2 简单实现(日常开发最常用)

直接用 RedisTemplate 调用自增命令:

public Long generateId(String key) {
    // 原子自增,每次+1
    return redisTemplate.opsForValue().increment(key, 1);
}

如果你需要​带日期的业务号​,比如:20250303000012345,可以这样拼:

public String generateOrderId() {
    String date = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDate.now());
    String key = "id_generator:order:" + date;
    // 自增
    Long seq = redisTemplate.opsForValue().increment(key, 1);
    // 设置当天过期
    redisTemplate.expire(key, 1, TimeUnit.DAYS);
    // 拼接成 8 位日期 + 8 位序列
    return date + String.format("%08d", seq);
}

2.3 平常开发遇到的问题

  1. Redis 挂了,ID 生成就不可用
  2. 本地测试、联调时经常遇到 Redis 重启,接口直接报错。
  3. 集群环境下不能直接多实例自增
  4. 如果你用多个 Redis 实例,不能简单各自自增,会重复。
  5. 高并发下 Redis 会成为瓶颈
  6. 压测时能明显感觉到,QPS 一高,自增接口 RT 会上升。

2.4 优点 & 缺点

  • 优点:实现简单、上手快、适合低并发业务编号;
  • 缺点:强依赖 Redis、高并发性能一般、不适合超大规模分布式系统。

三、号段模式(业务系统最稳、最常用)

3.1 为什么要用号段模式?

平常开发中,​号段模式是我最推荐的方案​,兼顾性能、可靠性、实现难度。

它的思路很简单:

  • 不是每次去数据库拿 1 个 ID;
  • 而是​一次拿一批(比如 1000 个)​,放到本地内存;
  • 用完一批再去数据库取下一批。

3.2 数据库表设计(通用模板)

建一张 ID 生成表:

CREATE TABLE id_generator (
    id int primary key auto_increment,
    biz_type varchar(32) not null comment '业务类型',
    max_id bigint not null default 0 comment '当前最大ID',
    step int not null default 1000 comment '每次取多少',
    unique key uk_biz_type (biz_type)
);

插入一条业务数据:

INSERT INTO id_generator (biz_type, max_id, step) 
VALUES ('order', 0, 1000);

3.3 核心流程(开发时一定要理解)

  1. 服务启动,查询 biz_type = 'order',拿到 max_idstep
  2. 计算本地可用段:start = max_id + 1end = max_id + step
  3. 更新数据库:max_id = end
  4. 本地从 start 到 end 逐个分配 ID;
  5. 快用完时,异步提前加载下一个号段。

3.4 平常开发遇到的真实问题

  1. 号段用完再去加载,会有瞬间性能抖动
  2. 所以实际开发时,一般​号段使用到 10% 或 20% 就提前加载下一段​,避免卡顿。
  3. 服务重启会浪费一段 ID
  4. 比如号段只用了 100 个就重启了,剩下 900 个直接丢弃,ID 会不连续,但不影响使用。
  5. 并发更新数据库会出现超发
  6. 必须用 ​CAS 思想更新​:

    UPDATE id_generator 
    SET max_id = #{newMaxId} 
    WHERE biz_type = #{bizType} AND max_id = #{oldMaxId}

    这是我在联调时多次踩坑后才固定下来的写法。

3.5 优点 & 缺点

  • 优点:性能极高、不依赖中间件、数据库即可、适合分库分表;
  • 缺点:实现比 Redis 复杂一点、会有少量 ID 浪费、依赖数据库可用性。

实际公司内部的中间件、通用 ID 生成服务,绝大多数都是基于​号段模式​。

四、雪花算法 Snowflake(高并发分布式首选)

4.1 算法结构

雪花算法是 Twitter 开源的,一个 64 位 long 型 ID 结构:

  • 1 位:符号位,固定 0;
  • 41 位:时间戳;
  • 10 位:机器 ID(5 位数据中心 + 5 位机器);
  • 12 位:序列号,同一毫秒内自增。

最大优势:

  • 纯内存生成​,不依赖任何中间件;
  • 性能极高,单机每秒能生成几十万 ID;
  • 趋势递增,对 MySQL 索引非常友好。

4.2 手写一个雪花算法工具类

平常开发我自己封装过一版,稳定用了很多项目:

public class SnowflakeIdGenerator {

    // 起始时间戳(2025-01-01)
    private static final long EPOCH = 1735689600000L;

    // 机器ID 5位 + 数据中心ID 5位
    private long workerId;
    private long dataCenterId;
    private long sequence = 0L;

    // 位数
    private static final long WORKER_ID_BITS = 5L;
    private static final long DATA_CENTER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;

    // 最大值
    private static final long MAX_WORKER_ID = (1L << WORKER_ID_BITS) - 1;
    private static final long MAX_DATA_CENTER_ID = (1L << DATA_CENTER_ID_BITS) - 1;

    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("workerId 非法");
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId 非法");
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    public synchronized long nextId() {
        long now = System.currentTimeMillis();

        if (now < lastTimestamp) {
            throw new RuntimeException("时间回拨,拒绝生成ID");
        }

        if (now == lastTimestamp) {
            sequence = (sequence + 1) & ((1L << SEQUENCE_BITS) - 1);
            if (sequence == 0) {
                // 同一毫秒序列号用完,等待下一毫秒
                now = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = now;

        return ((now - EPOCH) << (WORKER_ID_BITS + DATA_CENTER_ID_BITS + SEQUENCE_BITS))
                | (dataCenterId << (WORKER_ID_BITS + SEQUENCE_BITS))
                | (workerId << SEQUENCE_BITS)
                | sequence;
    }

    private long waitNextMillis(long lastTimestamp) {
        long now = System.currentTimeMillis();
        while (now <= lastTimestamp) {
            now = System.currentTimeMillis();
        }
        return now;
    }
}

4.3 平常开发遇到的真实问题

  1. 机器 ID 如何分配?
  2. 这是最常见的问题。如果手动配置,多实例部署容易重复;
  3. 可以用:IP 后 10 位、Docker 容器序号、Redis 自增注册机器 ID。
  4. 时间回拨问题
  5. 本地测试、服务器同步时间时,会出现时间往回跳。
  6. 简单处理:

    • 小幅度回拨:等待时间追上来;
    • 大幅度回拨:直接抛异常,避免重复 ID。
  7. 只能用 69 年
  8. 因为只有 41 位时间戳,从一个固定起点算,大概能用 69 年。
  9. 平常开发基本不用考虑,属于工程上可接受的限制。

4.4 优点 & 缺点

  • 优点:性能极高、不依赖第三方、趋势递增、long 型存储友好;
  • 缺点:依赖机器时间、机器 ID 要规划、强内存生成。

五、三种方案怎么选?(开发直接照这个来)

我在平常做技术选型时,基本按这个逻辑来:

  1. 简单业务编号、低并发
  2. 用 ​Redis 自增​,实现最快,成本最低。
  3. 公司内部通用 ID、分库分表、稳定性优先
  4. 用 ​号段模式​,最稳、最可控、最适合业务系统。
  5. 高并发、微服务集群、链路追踪、订单 ID
  6. 用 ​雪花算法​,性能天花板最高。

六、总结

其实分布式 ID 不算复杂技术,但​细节非常多​,很多坑都是在平常开发、联调、压测里一点点踩出来的。

  • Redis 方案:简单、适合小业务;
  • 号段模式:稳定、通用、业务系统首选;
  • 雪花算法:高性能、分布式系统首选。

实际开发中,不用追求“最牛逼”,而是​和业务量级匹配、团队能维护、出问题能快速定位​,就是最合适的方案。

版权声明 ▶ 本网站名称:黄磊的博客
▶ 本文标题:分布式 ID 生成方案:雪花算法、号段模式、Redis 实现
▶ 本文链接:https://www.huangleicole.com/backend-related/122.html
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!

如果觉得我的文章对你有用,请随意赞赏