微服务

使用redis生成分布式全局唯一ID

2022-04-16  本文已影响0人  生不悔改

背景

分布式系统中由于跨进程跨系统,在某些场景中,我们需要生成全局的唯一ID,例如订单系统,并发情况下,不同的系统需要同时生成不一样的订单ID方便后续的订单下单与查询等等。

解决

网上有很多解决方法,例如:雪花算法,薄雾算法,利用单台数据库生成唯一主键方法,以及redis生成唯一ID方法,等等

redis生成全局唯一ID的原理

我们生成的订单号一般需要存在Long类型中,正好Long类型是64位,所以将第一位永远设置成0,表示正数。后面31位表示时间戳,可以表示的数字为2的31次方(0-2147483648),单位秒,再后面的32位可以表示成2的32次方的订单号(0-4294967296)。这种思想主要是借鉴雪花算法的原理。

全局唯一ID.png
解释
1.符号位:1bit,永远为0,表示正数
2.时间戳:31bit,最大2147483648秒,大概69年
3.序列号:32bit,最大4294967296,表示一秒中内能生成的不同的订单数(接近43亿)
一般一秒中能产生43亿个不一样的订单号,基本满足各种电商场景了。

java代码实现

@Component
public class RedisIdMaker {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 时间戳开始时间,从2022年1月1号0点0时0分开始
     */
    private static final Long START_TIME = 1640995200L;

    /**
     * 订单生成数量  每天最多2的31次方个订单数量
     */
    private static final int COUNT_BITS = 32;

    private static final String ORDER_COUNT_KEY = "order:";


    /**
     * 根据redis生成唯一订单号
     *
     * @return
     */
    public Long generateNextId() {
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        long currentStamp = now.toEpochSecond(ZoneOffset.UTC);
        // 获取当前时间戳(秒)
        long timeStamp = currentStamp - START_TIME;
        // 组装成key=order:2022:01:01(组装成这种形式方便日后根据日期统计当天的订单数量)
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        String redisKey = ORDER_COUNT_KEY + date;
        // 订单自增长
        long orderCount = stringRedisTemplate.opsForValue().increment(redisKey);
        // 返回唯一订单号(拼接而来的)
        return timeStamp << COUNT_BITS | orderCount;
    }

    /**
     * 获取2022年1月1号0点0时0分的时间戳
     * @param args
     */
    public static void main(String[] args) {
        LocalDateTime startLocalTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        long startTime = startLocalTime.toEpochSecond(ZoneOffset.UTC);
        System.out.println(startTime);
        LocalDateTime now = LocalDateTime.now();

        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        System.out.println(date);
    }
}

解释
时间戳开始是从1970年开始的,当31位时间戳全部是0的情况下,那么就是最开始,当当31位时间戳全部是1的情况下那么就是2039年,也就是说只能用到2039年

64位表示2022年1月1号0点0时0分.png
图中后32位表示2022年1月1号0点0时0分的秒时间戳。
timeStamp << COUNT_BITS

这行代码表示将上图中的时间戳往前位移32位,就变成了下面的


左移32位后.png

最后就是这段代码

timeStamp << COUNT_BITS |orderCount

| 是把某两个数中, 只要其中一个的某一位为1,则结果的该位就为1。
由于我们现在表示成二进制,只有0和1,所以这样运算后变成了


第一笔订单.png

测试

 @Test
 public void test() throws InterruptedException {
        System.out.println(redisIdMaker.generateNextId());
    }

结果

39001021761978369

进制转换


image.png

批量生成测试

 @Test
 public  void contextLoads() throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(300);
        // 定义任务
        Runnable task = ()->{
            for (int i = 0; i < 100; i++) {
                long id = redisIdMaker.generateNextId();
                System.out.println(id);
            }
            countDownLatch.countDown();
        };
        for (int i = 0; i < 300; i++) {
            es.submit(task);
        }
        countDownLatch.await();
    }

结果:控制台一共生成了30000条ID


image.png

在看我们的redis,同样记录了,这一秒中生成的订单号数是30000

image.png

总结

利用redis生成全局唯一ID,其实redis扮演的角色就是一个计数器的作用,方便后续的统计。
优点:高性能,高并发,唯一性,递增性,安全性。
缺点:需要依赖redis去实现

上一篇 下一篇

猜你喜欢

热点阅读