• Redis实现全局唯一id


    需求:

    每个店铺都可以发布优惠券,而每张优惠券都是唯一的。当用户抢购时,就会生成订单并保存到 tb_voucher_order 这张表中,而订单表如果使用数据库自增 ID 就存在一些问题:

    • id 的规律太明显。如果 id 规律太明显,用户就能够根据 id 猜测出一些信息。比方说,某用户第一天下了一单,此时 id 为 10,第二天同一时刻,该用户又下了一单,此时 id 为 100,那么用户就能够推断出昨天一天店家卖出了 90 单,这就将一些信息暴露给用户。
    • 受单表数据量的限制。订单的一个特点就是数据量比较大,只要用户不停地产生购买行为,就会不停地产生新的订单。如果网站做到一定的规模,用户量达到数百万,这时候每天都会产生数十万甚至近百万的订单,一年下来就会达到数千万的订单,那么两年三年不断累积下来,订单量就会越来越庞大,此时单张表就无法保存这么多的订单数据,就需要将单张表拆分成多张表。MySQL 的每张表会自己计算自己的自增长,如果每张表都使用自增长,订单 id 就一定会重复。

    全局 ID 生成器,是一种在分布式系统下用来生成全局唯一 ID 的工具,一般要满足下列特性:

    • 唯一性
    • 高可用
    • 高性能
    • 递增型
    • 安全性

    为了增加 ID 的安全性,我们可以不直接使用 Redis 自增的数值,而是拼接一些其它信息:
     

    ID 组成部分:

    • 符号位:1 bit,永远为 0
    • 时间戳:31 bit,以秒为单位,可以使用 69 年
    • 序列号:32 bit,秒内的计数器,支持每秒产生 2^32 个不同的 ID

    代码实现 

    1. @Component
    2. public class RedisIdWorker {
    3. private static final long BEGIN_TIMESTAMP = 1640995200L;
    4. private static int COUNT_BITS = 32;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. public Long nextId(String keyPrefix) {
    8. //获取当前时间
    9. LocalDateTime now = LocalDateTime.now();
    10. //获取当前时间得秒数
    11. long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
    12. long time = nowSecond - BEGIN_TIMESTAMP;
    13. String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
    14. // Redis Incrby 命令将 key 中储存的数字加上指定的增量值。
    15. // 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
    16. Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + format);
    17. return time << COUNT_BITS | count;
    18. }
    19. // public static void main(String[] args) {
    20. // LocalDateTime of = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
    21. // long l = of.toEpochSecond(ZoneOffset.UTC);
    22. // // LocalTime类的toEpochSecond()方法用于
    23. // // 将此LocalTime转换为自1970-01-01T00:00:00Z以来的秒数
    24. // System.out.println(l);
    25. // }
    26. }

    测试

    1. @SpringBootTest
    2. class HmDianPingApplicationTests {
    3. @Autowired
    4. private RedisIdWorker redisIdWorker;
    5. private ExecutorService es = Executors.newFixedThreadPool(500);
    6. @Test
    7. void testIdWorker() throws InterruptedException {
    8. CountDownLatch latch = new CountDownLatch(300);
    9. Runnable task = () -> {
    10. for (int i = 0; i < 100; i++) {
    11. Long id = redisIdWorker.nextId("order");
    12. System.out.println("id = " + id);
    13. }
    14. latch.countDown();
    15. };
    16. long begin = System.currentTimeMillis();
    17. for (int i = 0; i < 300; i++) {
    18. es.submit(task);
    19. }
    20. latch.await();
    21. long end = System.currentTimeMillis();
    22. System.out.println("time = " + (end - begin));
    23. }
    24. }

     总结


    全局唯一 ID 生成策略:

    1. UUID:16进制的字符串ID,可以做唯一ID,但不支持自增
    2. Redis 自增
    3. snowflake 雪花算法:long 类型的 64 ID,性能更好,但是比较依赖于时钟,如果时间不准确,可能会出现异常问题
    4. 数据库自增:单独创建一张表,用于实现自增


    Redis 自增 ID 策略:

    1. 每天一个 key,方便统计订单量
    2. ID 构造是 时间戳 + 计数器
  • 相关阅读:
    6.linux磁盘分区、挂载
    js常用数组使用方法
    机器学习9衡量线性回归法的指标,MSE,RMS,MAE
    【手把手带你学JavaSE】第二篇:Java的main函数、数据类型
    一个实际工作中的sql的模拟
    C++:stack和queue的使用以及底层实现
    uni.getSystemInfo(OBJECT)
    PyQt5 的一些例子
    翻转单链表细节讲解
    echarts
  • 原文地址:https://blog.csdn.net/weixin_51472505/article/details/126389359