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绝对不重复

相关推荐
REDcker2 小时前
RabbitMQ系列01 - 消息中间件与 MQ:在分布式系统里解决什么问题
分布式·rabbitmq
Albert Edison2 小时前
【RabbitMQ】七种工作模式
java·开发语言·分布式·rabbitmq
☞遠航☜2 小时前
rabbitmq 创建延迟队列
分布式·rabbitmq
Rick19932 小时前
RabbitMQ 死信队列(DLX)
分布式·rabbitmq
有味道的男人2 小时前
抖音关键词搜索,视频详情api
linux·数据库·音视频
丁丁点灯o2 小时前
Oracle中金额数字转换为大写汉字
数据库·oracle
fly spider2 小时前
MySQL之Buffer Pool
数据库·mysql
REDcker2 小时前
RabbitMQ系列02 - RabbitMQ 消息模型:Broker、交换器、队列与收发路径
分布式·rabbitmq·ruby
程序员老邢2 小时前
【技术底稿 13】内网 Milvus 2.3.0 向量数据库全流程部署(商助慧 AI 底座,Attu 可视化)
java·数据库·人工智能·ai·语言模型·milvus