当前位置: 首页 > news >正文

基于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生成器代码如下

@Component
public class RedisIdWorker {private static final long BEGIN_TIMESTAMP = 1640995200L;private static final long COUNT_BITS = 32;@Resourceprivate StringRedisTemplate stringRedisTemplate;public long nextId(String KeyPrefix) {// 1.生成全局ID时间戳部分LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.生成全局ID序列号部分// 2.1获取当前日期,精确到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 2.2获取自增长值Long count = stringRedisTemplate.opsForValue().increment("icr:" + KeyPrefix + ":" + date);// 3.拼接时间戳和序列号并返回return timestamp << COUNT_BITS | count;}// 用于计算20220101时间戳给BEGIN_TIMESTAMP赋值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);}}

相关文章:

  • 解决日常问题的12个Python Pro Snippets
  • 华为云开年采购季云上云下一体化安全解决方案,为企业筑牢云上“安全网”
  • 代码随想录算法训练营第35天—动态规划03 | ● *343. 整数拆分 ● *96.不同的二叉搜索树
  • 08. Nginx进阶-Nginx动静分离
  • 构建cef基本框架及构建过程中的参数说明
  • Gafana Redis Overview dashboard
  • 看完不会来揍我 | 生存分析详解 | 从基础概念到生存曲线绘制 | 代码注释 + 结果解读
  • 什么是WhatsApp Business解决方案提供商?
  • docker-swarm集群管理命令
  • 数据结构从入门到精通——栈
  • docker使用笔记
  • [leetcode 189][轮转数组]
  • 【性能】JDK和Jmeter的安装与配置
  • NTFS安全权限
  • 手写分布式配置中心(四)增加实时刷新功能(长轮询)
  • dva中组件的懒加载
  • ES6系统学习----从Apollo Client看解构赋值
  • js操作时间(持续更新)
  • Just for fun——迅速写完快速排序
  • nodejs:开发并发布一个nodejs包
  • Python_OOP
  • QQ浏览器x5内核的兼容性问题
  • scala基础语法(二)
  • 关于字符编码你应该知道的事情
  • 规范化安全开发 KOA 手脚架
  • 深入浅出webpack学习(1)--核心概念
  • 使用 QuickBI 搭建酷炫可视化分析
  • 物联网链路协议
  • 用element的upload组件实现多图片上传和压缩
  • 正则学习笔记
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​MySQL主从复制一致性检测
  • $.ajax()方法详解
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (4)logging(日志模块)
  • (Java)【深基9.例1】选举学生会
  • (Matlab)使用竞争神经网络实现数据聚类
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (三) diretfbrc详解
  • (十五)使用Nexus创建Maven私服
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .net 8 发布了,试下微软最近强推的MAUI
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Micro Framework 4.2 beta 源码探析
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .net网站发布-允许更新此预编译站点
  • .NET中使用Protobuffer 实现序列化和反序列化
  • /3GB和/USERVA开关
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • [Big Data - Kafka] kafka学习笔记:知识点整理