redis实现分布式全局唯一id
目录
- 一、前言
- 二、如何通过Redis设计一个分布式全局唯一ID生成工具
- 2.1 使用 Redis 计数器实现
- 2.2 使用 Redis Hash结构实现
- 三、通过代码实现分布式全局唯一ID工具
- 3.1 导入依赖配置
- 3.2 配置yml文件
- 3.3 序列化配置
- 3.4 编写获取工具
- 3.5 测试获取工具
- 四、运行结果
一、前言
在很多项目中生成类似订单编号、用户编号等有唯一性数据时还用的UUID工具,或者自己根据时间戳+随机字符串等组合来生成,在并发小的时候很少出问题,当并发上来时就很可能出现重复编号的问题了,单体项目和分布式项目都是如此,要想解决这个问题也有很多种方法,可以自己写一个唯一ID生成规则,也可以通过数据库来实现全局ID生成这个和使用Redis实现其实类似,还可以使用比较成熟的雪花算法工具实现,每种方法都有各自的优缺点这里不展开说明,这里详细说明如何使用Redis实现生成分布式全局唯一ID。
还有一个问题为什么不能直接使用数据库的自增ID,而是需要单独生成一个分布式全局唯一ID,类似订单IDON202311090001,在数据库中有自增ID,对于当前业务来说就是唯一的为什么不能用,还要去生成一个独立的订单ID,对于这个问题要从几个方面分析:
1、数据库自增ID是有序增长的很容易就被人猜到,比如我现在下一单看到的订单ID为999那么就知道你的系统里最多只有999单,还有如果接口设计不合理,比如取消订单接口只校验了用户是否登录没有校验订单是否属于该用户,接收一个订单ID就能将订单取消,那么这样很容易就被人抓住漏洞,类似的情况有很多,也很多人写接口是不会注意这个问题。2、这种自增ID没有意义,而且不同业务的自增ID是重合的,对于信息区分度很低,而且考虑到多业务交互和用户端展示也都是不合适的,想想看要是你在某宝下单,订单ID是999,或者在对接别人订单系统时,给你的订单ID是999是不是很奇怪。3、分库分表时自增ID会重复
全局ID生成器:是一种在【分布式系统下】用来生成全局唯一ID的工具;
全局ID需要满足的特性:
1.唯一性
2.高可用:集群、哨兵机制;
3.高性能
4.递增性:Redis中的String数据类型的有自增特性!
5.安全性:将自增数值进行拼接,不容易猜出来;
ID结构:
符号位(1位) + 时间戳(31位) + 序列号(32位);
时间戳为从起始时间到现在的时间差;
理论上支持1秒钟2^32个订单;

二、如何通过Redis设计一个分布式全局唯一ID生成工具
用户下单调用下单逻辑,先进行业务逻辑处理,然后携带订单ID标识通过分布式全局唯一ID工具获取一个唯一的订单ID,这个订单ID标识就是用于区分业务的,获取到订单ID后将数据组装入库,分布式全局唯一ID工具可以做成一个内嵌的utils,也可以封装成一个独立的jar,还可以做成一个分布式全局唯一ID生成服务供其它业务服务调用。

2.1 使用 Redis 计数器实现
Redis的String结构提供了计数器自增功能,类似Java中的原子类,还要优于Java的原子类,因为Redis是单线程执行的缓存读写本身就是线程安全的,也不用进行原子类的乐观锁操作,每一次获取分布式全局唯一ID时就将自增序列加1。
# 给key为GENERATEID:NO的value自增1,如果这key不存在则会添加到Redis中并且设置value为1
## GENERATEID:key前缀
## NO:订单ID标识
127.0.0.1:6379> incr GENERATEID:NO
(integer) 1
2.2 使用 Redis Hash结构实现
Redis Hash结构中的每一个field也可以进行自增操作,可以用一个Hash结构存储所有的标识信息和自增序列,方便管理,比较适合并发不高的小项目所有服务都是用的一个Redis,如果并发较高就不合适了,毕竟Redis操作普通String结构肯定比操作Hash结构快。
# 给key为GENERATEID,field为no的value自增1,如果这key不存在则会添加到Redis中并且设置value为1
## GENERATEID:分布式全局唯一ID Hash key
## NO:Hash结构中的field
127.0.0.1:6379> hincrby GENERATEID NO 1
(integer) 1
三、通过代码实现分布式全局唯一ID工具
这里使用Redis 计数器实现,自增序列以天为单位存储,在实际业务中,比如生成订单编号组成规则都类似NO1699631999000-1(业务标识key+当前时间戳+自增序列),这个规则可以自己定义,保证最终生成的订单编号不重复即可,不建议直接一个自增序列干到底,订单编号这类型的数据都是有长度限制的,或者是要求生成20字符的订单编号,如果增长的过长反而不好处理。
3.1 导入依赖配置
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>test</scope>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version>
</dependency>
3.2 配置yml文件
spring:#redis配置信息redis:## Redis数据库索引(默认为0)database: 0## Redis服务器地址host: 127.0.0.1## Redis服务器连接端口port: 6379## Redis服务器连接密码(默认为空)password:## 连接超时时间(毫秒)timeout: 1200lettuce:pool:## 连接池最大连接数(使用负值表示没有限制)max-active: 8## 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1## 连接池中的最大空闲连接max-idle: 8## 连接池中的最小空闲连接min-idle: 1
3.3 序列化配置
@Configuration
public class RedisConfig {//编写我们自己的配置redisTemplate@Bean@SuppressWarnings("all")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// JSON序列化配置Jackson2JsonRedisSerializer jsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper=new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jsonRedisSerializer.setObjectMapper(objectMapper);// String的序列化StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();//key和hash的key都采用String的序列化方式template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);//value和hash的value都采用jackson的序列化方式template.setValueSerializer(jsonRedisSerializer);template.setHashValueSerializer(jsonRedisSerializer);template.afterPropertiesSet();return template;}
}
3.4 编写获取工具
@Component
public class RedisGenerateIDUtils {@Resourceprivate RedisTemplate<String, Object> redisTemplate;// key前缀private String PREFIX = "GENERATEID:";/*** 获取全局唯一ID* @param key 业务标识key*/public String generateId(String key) {// 获取对应业务自增序列Long incr = getIncr(key);// 组装最后的结果,这里可以根据需要自己定义,这里是按照业务标识key+当前时间戳+自增序列进行组装String resultID = key + System.currentTimeMillis() + "-" + incr;return resultID;}/*** 获取对应业务自增序列*/private Long getIncr(String key) {String cacheKey = getCacheKey(key);Long increment = 0L;// 判断Redis中是否存在这个自增序列,如果不存在添加一个序列并且设置一个过期时间if (!redisTemplate.hasKey(cacheKey)) {// 这里存在线程安全问题,需要加分布式锁,这里做简单实现String lockKey = cacheKey + "_LOCK";// 设置分布式锁boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS);if (!lock) {// 如果没有拿到锁进行自旋return getIncr(key);}increment = redisTemplate.opsForValue().increment(cacheKey);// 我这里设置24小时,可以根据实际情况设置当前时间到当天结束时间的插值redisTemplate.expire(cacheKey, 24, TimeUnit.HOURS);// 释放锁redisTemplate.delete(lockKey);} else {increment = redisTemplate.opsForValue().increment(cacheKey);}return increment;}/*** 组装缓存key*/private String getCacheKey(String key) {return PREFIX + key + ":" + getYYYYMMDD();}/*** 获取当前YYYYMMDD格式年月日*/private String getYYYYMMDD() {LocalDate currentDate = LocalDate.now();int year = currentDate.getYear();int month = currentDate.getMonthValue();int day = currentDate.getDayOfMonth();return "" + year + month + day;}
}
3.5 测试获取工具
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisUniqueIdDemoApplication.class)
class RedisUniqueIdDemoApplicationTests {@Resourceprivate RedisGenerateIDUtils redisGenerateIDUtils;@Testpublic void test() throws InterruptedException {// 定义一个线程池 设置核心线程数和最大线程数都为100,队列根据需要设置ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));CountDownLatch countDownLatch = new CountDownLatch(10000);long beginTime = System.currentTimeMillis();// 获取10000个全局唯一ID 看看是否有重复CopyOnWriteArraySet<String> ids = new CopyOnWriteArraySet<>();for (int i = 0; i < 10000; i++) {executor.execute(() -> {// 获取全局唯一IDlong beginTime02 = System.currentTimeMillis();String orderNo = redisGenerateIDUtils.generateId("NO");System.out.println(orderNo);System.out.println("获取单个ID耗时 time=" + (System.currentTimeMillis() - beginTime02));if (ids.contains(orderNo)) {System.out.println("重复ID=" + orderNo);} else {ids.add(orderNo);}countDownLatch.countDown();});}countDownLatch.await();// 打印获取到的全局唯一ID集合数量System.out.println("获取到全局唯一ID count=" + ids.size());System.out.println("耗时毫秒 time=" + (System.currentTimeMillis() - beginTime));}
}
知识小贴士:关于countdownlatch
countdownlatch名为信号枪:主要的作用是同步协调在多线程的等待于唤醒问题
我们如果没有CountDownLatch ,那么由于程序是异步的,当异步程序没有执行完时,主线程就已经执行完了,然后我们期望的是分线程全部走完之后,主线程再走,所以我们此时需要使用到CountDownLatch
CountDownLatch 中有两个最重要的方法
-
countDown
-
await
await 方法 是阻塞方法,我们担心分线程没有执行完时,main线程就先执行,所以使用await可以让main线程阻塞,那么什么时候main线程不再阻塞呢?当CountDownLatch 内部维护的 变量变为0时,就不再阻塞,直接放行,那么什么时候CountDownLatch 维护的变量变为0 呢,我们只需要调用一次countDown ,内部变量就减少1,我们让分线程和变量绑定, 执行完一个分线程就减少一个变量,当分线程全部走完,CountDownLatch 维护的变量就是0,此时await就不再阻塞,统计出来的时间也就是所有分线程执行完后的时间。
四、运行结果
redis结果

代码运行结果,id没有出现重复:

代码地址:Github
觉得有用的话还请来个三连!!!
相关文章:
redis实现分布式全局唯一id
目录 一、前言二、如何通过Redis设计一个分布式全局唯一ID生成工具2.1 使用 Redis 计数器实现2.2 使用 Redis Hash结构实现 三、通过代码实现分布式全局唯一ID工具3.1 导入依赖配置3.2 配置yml文件3.3 序列化配置3.4 编写获取工具3.5 测试获取工具 四、运行结果 一、前言 在很…...
Sora引发安全新挑战
文章目录 前言一、如何看待Sora二、Sora加剧“深度伪造”忧虑三、Sora无法区分对错四、滥用导致的安全危机五、Sora面临的安全挑战总结前言 今年2月,美国人工智能巨头企业OpenAI再推行业爆款Sora,将之前ChatGPT以图文为主的生成式内容全面扩大到视频领域,引发了全球热议,这…...
Android 14.0 Launcher3定制化之桌面分页横线改成圆点显示功能实现
1.前言 在14.0的系统rom产品定制化开发中,在进行launcher3的定制化中,在双层改为单层的开发中,在原生的分页 是横线,而为了美观就采用了系统原来的另外一种分页方式,就是圆点比较美观,接下来就来分析下相关…...
SemiDrive E3 MCAL 开发系列(3)– Wdg 模块的使用
一、 概述 本文将会介绍 SemiDrive E3 MCAL Wdg 模块的基本配置,并且会结合实际操作的介绍,帮助新手快速了解并掌握这个模块的使用,文中的 MCAL 是基于 PTG3.0 的版本,开发板是官方的 E3640 网关板。 二、 Wdg 模块的主要配置 …...
AI推荐算法的演进之路
推荐算法 基于大数据和AI技术,提供全流程一站式推荐平台,协助企业构建个性化推荐应用,提升企业应用的点击率留存率和永久体验。目前,主要的推荐方法包括:基于内容推荐、协同过滤推荐、基于关联规则推荐、基于效用推荐…...
Tomcat安装,配置文件、组件
一、Tomcat的基本功能 1.1.Tomcat是什么? Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。一般来说,T…...
精读《React Hooks 最佳实践》
简介 React 16.8 于 2019.2 正式发布,这是一个能提升代码质量和开发效率的特性,笔者就抛砖引玉先列出一些实践点,希望得到大家进一步讨论。 然而需要理解的是,没有一个完美的最佳实践规范,对一个高效团队来说&#x…...
varFormatter 数据格式化库 以性能优先的 快速的 内存对象格式转换
varFormatter 数据格式化 技术 开源技术栏 对象/变量格式化工具库,其支持将一个对象进行按照 JSON XML HTML 等格式进行转换,并获取到结果字符串! 目录 文章目录 varFormatter 数据格式化 技术目录介绍获取方式 使用实例格式化组件的基本使…...
基于PHP的在线英语学习平台
有需要请加文章底部Q哦 可远程调试 基于PHP的在线英语学习平台 一 介绍 此在线英语学习平台基于原生PHP开发,数据库mysql。系统角色分为学生,教师和管理员。(附带参考设计文档) 技术栈:phpmysqlphpstudyvscode 二 功能 学生 1 注册/登录/…...
基于微信小程序电影院订票选座系统 (后台JSP+JDBC+Mysql)答辩常规问题和如何回答(答辩指导)
博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程ÿ…...
C++知识点总结(22):模拟算法真题 ★★★☆☆《安全警报》
安全警报 1. 审题 题目描述 Z市最大的金融公司:太平洋金融遭到了入侵,一名黑客潜入到了公司中,公司紧急启动安保程序,将大楼封锁,并安排作为安全主管的你对楼层进行搜查。所以你准备写一个程序,输入搜查楼…...
蓝桥杯练习系统(算法训练)ALGO-993 RP大冒险
资源限制 内存限制:64.0MB C/C时间限制:200ms Java时间限制:600ms Python时间限制:1.0s 问题描述 请尽情使用各种各样的函数来测试你的RP吧~~~ 输入格式 一个数N表示测点编号。 输出格式 一个0~9的数。 样例输入 0 样…...
Unity的相机跟随和第三人称视角
Unity相机跟随和第三人称视角 介绍镜头视角跟随人物方向进行旋转的镜头视角固定球和人的镜头视角 思路跟随人物方向进行旋转的镜头视角固定球和人的镜头视角 镜头旋转代码人物移动的参考代码注意 介绍 最近足球项目的镜头在做改动,观察了一下实况足球的视角&#x…...
哪个超声波清洗机品牌值得入手?销量榜品牌值得选购!
在科技日益发展的今天,超声波清洗技术以其高效、便捷和深度清洁的特点,已经深入到生活的诸多领域,从精密仪器到珠宝首饰,从眼镜框到假牙,甚至是厨房用品的日常护理,都能见到超声波清洗机的身影。面对市场上…...
Stwo:基于Circle STARK和M31的下一代STARK证明系统
1. 引言 StarkWare团队和Polygon Labs团队,历时数月,构造了基于Mersenne素数域M31的Circle STARK协议,通过使用M31 over a circle,可基于任意有限域构造高效STARKs,具体见2024年2月19日论文《Circle STARKs》。 基于…...
笔记本以太网集线器Hub充电可能导致网络异常(貌似是我把服务器网关写错了)
文章目录 笔记本以太网集线器(Hub)充电导致网络异常概述原理分析电源与信号干扰设备热度设备兼容性问题 解决方案升级固件提高设备散热效率选择兼容性好的设备 总结 今天用笔记本以太网直连服务器,一开始能连通,结果以太网hub插上…...
npm ERR! code ETIMEDOUT
在新建vue项目的时候报错 npm ERR! code ETIMEDOUT npm ERR! syscall connect npm ERR! errno ETIMEDOUT npm ERR! network request to https://registry.npmjs.org/vue%2fcli failed, reason: connect ETIMEDOUT 104.16.2.35:443 npm ERR! network This is a problem relate…...
动态规划(算法竞赛、蓝桥杯)--分组背包DP
1、B站视频链接:E16 背包DP 分组背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int v[N][N],w[N][N],s[N]; // v[i,j]:第i组第j个物品的体积 s[i]:第i组物品的个数 int f[N][N]; // f[i,j]:前i组物品,能放…...
太阳能供电井盖-物联网智能井盖监测系统-旭华智能
在这个日新月异的科技时代,城市的每一个角落都在悄然发生变化。而在这场城市升级的浪潮中,智能井盖以其前瞻性的科技应用和卓越的安全性能,正悄然崭露头角,变身马路上的智能“眼睛”,守护城市安全。 传统的井盖监测系统…...
贪心 Leetcode 455 分发饼干
分发饼干 Leetcode 455 学习记录自代码随想录 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸࿱…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
