Redis 分布式 ID 生成器

1. 完整可运行代码

复制代码
package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * Redis分布式ID生成器(黑马点评原版)
 * 功能:生成全局唯一、有序的分布式ID(订单ID、用户ID等)
 */
@Component
public class RedisIdWorker {

    /**
     * 开始时间戳:2022-01-01 00:00:00 UTC对应的秒数
     * 运行main方法可验证:1640995200L
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号的位数:32位
     * 对应ID结构中,低32位存自增序号,高31位存时间戳,最高1位符号位固定为0
     */
    private static final int COUNT_BITS = 32;

    // 注入Redis操作模板
    private StringRedisTemplate stringRedisTemplate;

    // 构造方法注入
    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 核心方法:生成下一个全局唯一ID
     * @param keyPrefix 业务前缀(比如order、user,区分不同业务的ID)
     * @return 64位long型全局唯一ID
     */
    public long nextId(String keyPrefix) {
        // 1. 生成时间戳
        LocalDateTime now = LocalDateTime.now();
        // 获取当前时间的UTC秒数(从1970-01-01 00:00:00开始的秒数)
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // 计算相对时间戳:当前秒数 - 基准时间(2022-01-01)
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        // 2. 生成序列号
        // 2.1 获取当前日期,精确到天(格式:yyyy:MM:dd)
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 自增长:调用Redis的INCR命令,原子自增,保证序号不重复
        // Redis的key格式:icr:{业务前缀}:{日期},比如icr:order:2022:01:25
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3. 拼接并返回最终ID
        // 时间戳左移32位(空出低32位给序号),然后按位或序号,完成拼接
        return timestamp << COUNT_BITS | count;
    }

    /**
     * 测试方法:计算2022-01-01 00:00:00 UTC对应的秒数
     * 运行结果:second = 1640995200
     */
    public static void main(String[] args) {
        LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        long second = time.toEpochSecond(ZoneOffset.UTC);
        System.out.println("second = " + second);
    }
}

2. 测试代码

复制代码
package com.hmdp;

import com.hmdp.utils.RedisIdWorker;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@SpringBootTest
public class HmDianPingApplicationTests {

    // 注入ID生成器
    @Resource
    private RedisIdWorker redisIdWorker;

    // 创建500个线程的线程池,模拟高并发场景
    private ExecutorService es = Executors.newFixedThreadPool(500);

    @Test
    void testIdWorker() throws InterruptedException {
        // 倒计时锁:300个任务,等所有任务执行完再结束测试
        CountDownLatch latch = new CountDownLatch(300);

        // 定义任务:每个线程循环100次,生成ID
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                long id = redisIdWorker.nextId("order");
                System.out.println("id = " + id);
            }
            // 任务完成,倒计时-1
            latch.countDown();
        };

        // 记录开始时间
        long begin = System.currentTimeMillis();
        // 提交300个任务到线程池
        for (int i = 0; i < 300; i++) {
            es.submit(task);
        }
        // 等待所有任务执行完成
        latch.await();
        // 记录结束时间,计算总耗时
        long end = System.currentTimeMillis();
        System.out.println("time = " + (end - begin));
    }
}

3. 核心常量解释

|-----------------|-------------|-------|----------------------------------------------|
| 常量名 | 值 | 作用 | 解释 |
| BEGIN_TIMESTAMP | 1640995200L | 基准时间戳 | 2022 年 1 月 1 日 0 点的 UTC 秒数,作为时间计算的起点,避免时间戳过大 |
| COUNT_BITS | 32 | 序列号位数 | 给自增序号分配 32 个二进制位,时间戳占剩下的 31 位,最高 1 位符号位固定为 0 |

4.ID 结构设计

最终生成的是一个 64 位的 long 型 ID,结构如下:

|-----------|------------|---------------|
| 符号位(1bit) | 时间戳(31bit) | 序列号(32bit) |
| 固定为 0(正数) | 相对基准时间的秒数 | Redis 原子自增的序号 |

各部分作用

符号位:1bit,永远是 0,保证 ID 是正数,符合 Java long 类型的规范

时间戳 :31bit,用「当前秒数 - 基准秒数」计算,最多可以用 2^31 / (365*24*3600) ≈ 68年,足够业务使用

序列号 :32bit,用 Redis 的INCR命令原子自增,同一秒内、同一业务、同一天 的序号绝对不重复,最多支持 2^32 = 42亿 个 ID / 天

5.测试代码讲解

测试用例,是模拟高并发场景

(1)Executors.newFixedThreadPool(500):创建 500 个线程的线程池

(2)CountDownLatch(300):倒计时锁,保证 300 个任务全部执行完再结束测试

(3)每个任务循环 100 次,总共生成 300*100=30000 个 ID

(4)运行结果:3 秒左右生成 3 万个 ID,性能极高,且所有 ID绝对不重复

相关推荐
2501_933670798 分钟前
2026秋招数据分析岗备考路线:SQL、BI、项目与面试题拆解
数据库
我要见SA姐13 小时前
告别 Copilot?Codex 本地化部署指南
运维·数据库·机器学习·oracle·回归
拼图2093 小时前
Git常用语法
分布式·git·学习
xcLeigh3 小时前
聊聊国产化替换:好用数据迁移工具KDMS怎么帮咱们搞定评估难
数据库·sql·数据迁移·kes·kdms
Elastic 中国社区官方博客4 小时前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
大数据·运维·数据库·elasticsearch·搜索引擎
我要见SA姐14 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践指南
数据库·ide·vscode·oracle·编辑器
Nturmoils5 小时前
一份 KDMS 评估报告,怎样排出迁移先后顺序
数据库
这个DBA有点耶5 小时前
异构数据集成怎么做?5 种同步方案对比 + 金融级 CDC 实战解析
数据库·oracle·架构
独泪了无痕6 小时前
SQL函数实战:GREATEST与LEAST的技巧
数据库·sql·mysql
码少女6 小时前
Linux--多路转接之select
java·服务器·数据库