• 基于Redis自增实现全局ID生成器(详解)


     本博客为个人学习笔记,学习网站与详细见:黑马程序员Redis入门到实战 P48 - P49 

    目录

    全局ID生成器介绍

    基于Redis自增实现全局ID

    实现代码


    全局ID生成器介绍

    背景介绍
    当用户在抢购商品时,就会生成订单并保存到数据库的某一张表中,而订单表如果使用数据库自增ID就会存在一些问题:
    1. id的规律性太明显
    2. 受单表数据量的限制

    基于使用数据库自增ID带来的两个问题,我们来做场景分析:
    1. 场景分析一:如果我们的id具有太明显的规则,用户或者说商业对手很容易猜测出来我们的一些敏感信息,比如商城在一天时间内,卖出了多少单,这明显不合适。
    2. 场景分析二:MySQL的单表容量不宜超过500万条记录。随着我们商城规模的扩大,数据量增长到一定程度后,我们需要进行数据库拆分和表拆分。拆分后,这些表在逻辑上仍然属于同一张表,因此它们之间的数据ID不能相同。因此,我们必须确保全局ID的唯一性。

    全局ID生成器
    全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
    1. 唯一性
    2. 高性能
    3. 高可用
    4. 递增性
    5. 安全性


    基于Redis自增实现全局ID

    全局ID组成结构图:

    序列号:由于Redis的自增操作是原子性的,保证了在并发情况下生成ID的唯一性,避免了传统数据库中的锁竞争和性能瓶颈。因此我们可以利用Redis的自增原子性,让序列号由Redis自增的数值组成,因此我们确保了全局ID序列号的唯一性,从而确保了整个全局ID的唯一性。

    同时,我们还需要考虑一个问题,我们利用Redis自增实现全局ID,但如果我们只设置一个Key值,随着业务的日积月累,自增值将会达到上限。为避免这种情况发生,我们需要设置不同的Key值,于是我们决定用年月日的格式 yyyy:MM:dd 来添加到Key值的前缀当中,因此一个Key值的自增量不再是用来表示所有时间的业务量,而只是用来表示某年某月某天的业务量,而一天的业务量是不可能超过 2^32 (几十亿) 这么大的数值的,我们从而确保了Key值不会达到上限。

    而这种做法也方便了我们对业务数据的统计,当我们想查询一年中的业务量时,我们只需要查询前缀为 yyyy 的Key值自增量即可,如果我们想查询某年某月的业务量时,我们只需要查询前缀为 yyyy:MM 的Key值自增量即可。

    时间戳:为了增加全局ID的安全性,我们并能不直接把Redis的自增值(序列号)当作全局ID,而是应该在此基础上拼接一些其它信息,我们可以先设置某一个时间的时间戳作为参照时间戳,如2000年1月1日0时0分0秒,之后每当用户下单,我们可以获取下单时间的时间戳,再与参照时间戳做差,得到的差值用来组成全局ID的时间戳这一部分。(显然,我们全局ID设置的时间戳只有32位,因此我们需要确保差值是在2^32大小内,而2^32秒相当于136年的时间,因此是妥妥够用的,或者我们也可以选择对参照时间差进行调整来确保差值不会超过2^32)


    实现代码

    全局ID生成器代码如下

    1. @Component
    2. public class RedisIdWorker {
    3. private static final long BEGIN_TIMESTAMP = 1640995200L;
    4. private static final long COUNT_BITS = 32;
    5. @Resource
    6. private StringRedisTemplate stringRedisTemplate;
    7. public long nextId(String KeyPrefix) {
    8. // 1.生成全局ID时间戳部分
    9. LocalDateTime now = LocalDateTime.now();
    10. long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
    11. long timestamp = nowSecond - BEGIN_TIMESTAMP;
    12. // 2.生成全局ID序列号部分
    13. // 2.1获取当前日期,精确到天
    14. String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
    15. // 2.2获取自增长值
    16. Long count = stringRedisTemplate.opsForValue().increment("icr:" + KeyPrefix + ":" + date);
    17. // 3.拼接时间戳和序列号并返回
    18. return timestamp << COUNT_BITS | count;
    19. }
    20. // 用于计算20220101时间戳给BEGIN_TIMESTAMP赋值
    21. public static void main(String[] args) {
    22. LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
    23. long second = time.toEpochSecond(ZoneOffset.UTC);
    24. System.out.println("second = " + second);
    25. }
    26. }

  • 相关阅读:
    [Flask]Pycharm+Flask零基础项目搭建入门
    MYSQL数据库-表的约束
    中药配方专利申请时间有多久?
    代码随想录二刷 Day 20
    【数字化】分享-广东省企业首席数据官建设指南
    为什么每个有影响力的内容创作者都需要一个Kadence WordPress网站
    虚拟局域网
    141.环形链表
    操作系统与进程调度
    AC8015笔记
  • 原文地址:https://blog.csdn.net/aaa131420030415/article/details/136555545