如何解决使用Redis 事务不当产生的问题

redisTemplate.opsForValue() 懂这些就够了 hello,我是小索奇,redisTemplate.opsForValue() 是 Spring Data Redis 提供的一个操作字符串类型数据的工具方法。通过这个方法,你可以获取一个 ValueOperations 对象,然后使用该对象进行对 Redis 中字符串类型数据的操作,比如存储、读取、删除等。下是 ValueOperations 的一些常用方法哈。 阅读详情

一、前言

最近项目的生产环境遇到一个奇怪的问题:

现象:每天早上客服人员在后台创建客服事件时,都会创建失败。当我们重启这个微服务后,后台就可以正常创建了客服事件了。到第二天早上又会创建失败,又得重启这个微服务才行。

初步排查:创建一个客服事件时,会用到 Redis 的递增操作来生成一个唯一的分布式 ID 作为事件 id。代码如下所示:

return redisTemplate.opsForValue().increment("count", 1);

而恰巧每天早上这个递增操作都会返回 null,进而导致后面的一系列逻辑出错,保存客服事件失败。当重启微服务后,这个递增操作又正常了。

那么排查的方向就是 Redis 的操作为什么会返回 null 了,以及为什么重启就又恢复正常了。

二、排查

根据上面的信息,我们先来看看 Redis 的自增操作在什么情况下会返回 null。

2.1 推测一

根据重启后就恢复正常,我们推测晚上执行了大量的 job,大量 Redis 连接未释放,当早上再来执行 Redis 操作时,执行失败。重启后,连接自动释放了。

但是其他有使用到 Redis 的业务功能又是正常的,所以推测一的方向有问题,排除。

2.2 推测二

可能是 Redis 事务造成的问题。这个推测的依据是根据下面的代码来排查的。

直接看 redisTemplate 递增的方法 increment,如下所示:

​

官方注释已经说明什么情况下会返回 null:

  • 当在 pipeline(管道)中使用这个 increment 方法时会返回 null。

  • 当在 transaction(事务)中使用这个 increment 方法时会返回 null。

事务提供了一种将多个命令打包,然后一次性、有序地执行机制. 多个命令会被入列到事务队列中,然后按先进先出(FIFO)的顺序执行。 事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束。(内容来自 Redis 设计与实现)

继续看代码,发现在操作 Redis 的 ServiceImpl 实现类的上面添加了一个 @Transactional 注解,推测是不是这个注解影响了 Redis 的操作结果。

2.3 验证推测二

如下面的表格所示,第二行中没有添加 Spring 的事务注解 @Transactional时,执行 Redis 的递增命令肯定是正常的,而接下来要验证的是表格中的第一行:加了 @Transactional 是否对 Redis 的命令有影响。

​

为了验证上面的推论,我写了一个 Demo 程序。

Controller 类,定义了一个 API,用来模拟前端发起的请求:

​

Service 实现类,定义了一个方法,用来递增 Redis 中的 count 键,每次递增 1,然后返回命令执行后的结果。而且这个 Service 方法加了@Transactional 注解。

​

Postman 测试下,发现每发一次请求,count 都会递增 1,并没有返回 null。

​

然后到 Redis 中查看数据,count 的值也是递增后的值 38,也不是 null。

​

通过这个实验说明在 @Transactional 注解的方法里面执行 Redis 的操作并不会返回 null,结论我记录到了表格中。

​

所以说上面的推论不成立(加了 @Transactional 注解并不影响),到这里线索似乎断了。

2.4 推测三

然后跟当时做这块功能的开发人员说明了情况,告诉他可能是 Redis 事务造成的,然后问有没有其他同学在凌晨执行过 Redis 事务相关的 Job。

他说最近有同事加过 Redis 的事务功能,在凌晨执行 Job 的时候用到事务。我将这位同事加的代码简化后如下所示:

​

下面是针对这段代码的解释,简单来说就是开启事务,将 Redis 命令顺序放到一个队列中,然后最后一起执行,且保证原子性。

setEnableTransactionSupport表示是否开启事务支持,默认不开启。

难道开启了 Redis 事务,还能影响 Spring 事务中的 Redis 操作?

2.5 验证推测三

如下表,序号 3 和 序号 4 的场景都是开启了 Redis 的事务支持,两个场景的区别是是否加了 @Transactional 注解。

​

为了验证上面的场景,我们来做个实验:

  • 先开启 Redis 事务支持,然后执行 Redis 的事务命令 multi 和 exec 。

  • 验证场景 3:在 @Transactional 注解的方法中执行 Redis 的递增操作。

  • 验证场景 4:在非 @Transactional 注解的方法中执行 Redis 的递增操作

2.5.1 执行 Redis 事务

首先就用 Redis 的 multi 和 exec 命令来设置两个 key 的值。

​

如下图所示,设置成功了。

​

2.5.2 @Transactional 中执行 Redis 命令

接下来在标注有 @Transactional 注解的方法中执行 Redis 的递增操作。

​

多次执行这个命令返回的结果都是 null,这不就正好重现了!

​

再来看 Redis 中 count 的值,发现每执行一次 API 请求调用,都会递增 1,所以虽然命令返回的是 null,但最后 Redis 中存放的还是递增后的结果。

​

​

接下来我们验证下场景 4,先执行 Redis 事务操作,然后在不添加 @Transactional 注解的方法中执行 Redis 递增操作。

​

用 Postman 调用这个接口后,正常返回自增后的结果,并不是返回 null。说明在非 @Transactional 中执行 Redis 操作并没有受到 Redis 事务的影响。

​

四个场景的结论如下所示,只有第三个场景下,Redis 的递增操作才会返回 null。

​

问题原因找到了,说明 RedisTemplete 开启了 Redis 事务支持后,在 @Transactional 中执行的 Redis 命令也会被认为是在 Redis 事务中执行的,要执行的递增命令会被放到队列中,不会立即返回执行后的结果,返回的是一个 null,需要等待事务提交时,队列中的命令才会顺序执行,最后 Redis 数据库的键值才会递增。

三、源码解析

那我们就看下为什么开启了 Redis 事务支持,效果就不一样了。

找到 Redis 执行命令的核心方法, execute 方法。

​

然后一步一步点进去看,关键代码就是 211 行到 216 行,有一个逻辑判断,当开启了 Redis 事务支持后,就会去绑定一个连接(bindConnection),否则就去获取新的 Redis 连接(getConnection)。这里我们是开启了的,所以再到 bindConnection方法中查看如何绑定连接的。

​

接着往下看,关键代码如下所示,当开启了 Redis 事务支持,且添加了 @Transactional 注解时,就会执行 Redis 的 mutil 命令。

关键代码:conn.multi();

​

Redis Multi 命令用于标记一个事务块的开始,事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

真相大白,开启 Redis 事务支持 + @Transactional 注解后,最后其实是标记了一个 Redis 事务块,后续的操作命令是在这个事务块中执行的。

比如下面的的递增命令并不会返回递增后的结果,而是返回 null。

stringRedisTemplate.opsForValue().increment("count", 1);

而我们的生产环境重启服务后,开启的 Redis 事务支持又被重置为默认值了,所以后续的 Redis 递增操作都能正常执行。

四、修复方案

目前想到了两种解决方案:

  • 方案一:每次 Redis 的事务操作完成后,关闭 Redis 事务支持,然后再执行 @Transactional 中的 Redis 命令。(有弊端)

  • 方案二:创建两个 StringRedisTemplate,一个专门用来执行 Redis 事务,一个用来执行普通的 Redis 命令。

4.1 方案一

方案一的写法如下,先开启事务支持,事务执行之后,再关闭事务支持。

​

但是这种写法有个弊端,如果在执行 Redis 事务期间,在 @Transactional 注解的方法里面执行 Redis 命令,则还是会造成返回结果为 null。

​

4.2 方案二

弄两个 RedisTemplate Bean,一个是用来执行 Redis 事务的,一个是用来执行普通 Redis 命令的(不支持事务)。不同的地方引入不同的 Bean 就可以了。

先创建一个 RedisConfig 文件,自动装配两个 Bean。一个 Bean 名为 stringRedisTemplate 代表不支持事务的,执行命令后立即返回实际的执行结果。另外一个 Bean 名为 stringRedisTemplateTransaction,代表开启 Redis 事务支持的。

代码如下所示:

​

接下来在测试的 Service 类中注入两个不同的 StringRedisTemplate 实例,代码如下所示:

​

Redis 事务的操作改写成这样,且不需要手动开启 Redis 事务支持了。用到的 StringRedisTemplate 是支持事务的那个实例。

​

在 Spring 的 @Tranactional 中执行的 Redis 命令如下所示,用到的 StringRedisTemplate 是不支持事务的那个实例。

​

然后还是按照上面场景 3 的测试步骤,先执行 testRedisMutil 方法,再执行 testTransactionAnnotations 方法。

验证结果:Redis 递增操作正常返回 count 的值,修复完成。

使用 StringRedisTemplate 实现自增流水号 StringRedisTemplate 自增流水号 阅读详情

相关推荐

Redis的基础事务与ABA问题

文章目录Redis的基础事务一,Redis的事务命令二,Redis事务的基本流程2.1,没有添加监听的基本事务的执行流程2.1.1 控制台的执行效果2.1.2 代码实现简单事务流程2.2,添加监听的事务的执行流程2.2.1 执行流程图2.2.2 添加监视的控制台实现三,Redis不会产生ABA问题3.1 redis参考了多线程中的CAS3.2 什么是ABA问题 Redis的基础事务 一,Redis...

H1517043456的博客 1211

redisTemplate.opsForValue().increment

redis

weixin_50733737的博客 2万+

深入学习 Redis - 事务、实现原理、指令使用及场景

Redis 事务的主要意义就是为了 “打包” ,避免其他客户端的命令,插队到其中,那他具体是通过什么实现的呢?Redis 实现事务,是引入了队列(每一个客户端都有),开启事务的时候,客户端输入的命令,就会发给服务器,然后进入都这个队列中,此时并不会立即执行,当收到 “执行事务” 命令的时候,就会把队列中的这些任务按照顺序依次执行(这里是由 Redis 主线程执行的,他会把事务中的操作执行完,再去执行其他客户端).

CYK_byte的博客 1932

RedisTemplate方法详解

maven依赖 <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!--<version>2.1.4.RELEASE</ve

lanfeng_lan的博客 3万+

PSM倾向匹配详细步骤和程序

目录1.安装psmatch2统计包。2.数据准备3.数据分析及命令解读4.结果解读4.1模型拟合结果,此处无太多实际意义。4.2试验组可匹配的观测概览,按照命令中设定的匹配规则,试验组有8例患者未能匹配到合适对照。4.3结果解读的重点应该是对stata新生成的中间变量的解读。4.4均衡性检验结果4.5匹配结果的图示化5.Stata命令汇总最后留下两个问题给大家思考:题目 试验设计中,匹配的目的在于确保干预效应估计是建立在可比个体之间的不同结果的基础上。最简单的匹配方式是将干预组和对照组中协变量值相同的两个个

平平淡淡,戒急用忍,一生学闭嘴。 5万+

Ubuntu20.04下SRS流媒体服务器的安装与开机自启动优化指南

本文详细介绍了在Ubuntu20.04系统下安装SRS流媒体服务器并优化开机自启动的完整指南。从基础环境准备、服务管理方案对比到systemd自启动深度优化,提供了实用的配置技巧和排查方法,帮助用户快速搭建稳定的流媒体服务环境。特别推荐使用systemd方案实现高效的自启动管理。

weixin_29313239的博客 414

一次 Redis 事务使用不当引发的生产事故

现象:每天早上客服人员在后台创建客服事件时,都会创建失败。当我们重启这个微服务后,后台就可以正常创建了客服事件了。到第二天早上又会创建失败,又得重启这个微服务才行。初步排查:创建一个客服事件时,会用到 Redis 的递增操作来生成一个唯一的分布式 ID作为事件 id。而恰巧每天早上这个递增操作都会返回null,进而导致后面的一系列逻辑出错,保存客服事件失败。当重启微服务后,这个递增操作又正常了。那么排查的方向就是 Redis 的操作为什么会返回 null 了,以及为什么重启就又恢复正常了。

悟空聊架构的专栏 2255

【LCMM实战指南】从增长混合模型到潜在类别轨迹分析的纵向数据建模

本文深入解析了LCMM(潜在类别混合模型)在纵向数据建模中的应用,对比了LCGA与GMM的核心差异与选择策略。通过R语言lcmm包的实战演练,详细展示了从数据模拟、模型拟合、类别数选择到结果解读的全流程,为识别数据中不同的发展轨迹亚型提供了系统指南。

weixin_29070861的博客 381

详解Redis之事务

详解Redis的事务,包括概念,特性,常用命令及使用演示。

. 1万+

Zoom AI 技术架构研究:联合式方法与多模态集成

通过 Zoom AI 开放平台,企业可以自定义 AI 模块 (如法律合规审查、客户反馈分析),扩展应用场景。这种开放平台策略使企业能够根据自身需求定制 AI 功能,满足特定业务需求。

shao.bing的专栏 1318

uniapp - [全端兼容] 详解实现国际化多语言切换全流程及示例,全部页面中英文多语言随意切换即时生效,语言切换全项目立即变化可自定义新增各国其他语言,uniApp实现中英文语言切换完整源码教程!

uni-app,h5移动端网页,微信小程序,支付宝小程序,Android安卓,苹果ios,app,nvue,uniapp多语言实现,uni-app项目使用多语言切换功能,中英文切换,uniapp实现中英文语言切换,uniApp实现国际化多语言切换,中文英文之间的更换,Uniapp使用什么插件能支持多种语言,退出后重启依然能让语言保留,设置可切换中英文,uniapp国际化页面双语/顶部导航双语/根据系统语言双语,标题栏文字原生底部菜单tabbar怎么改,使用vue-i18n国际化多国语言,全平台i18n插件,

🏆 前端领域优质创作者 1万+

Redis事务详解

事务是指一个完整的动作,要么全部执行,要么什么也没有做。Redis 事务不是严格意义上的事务,只是用于帮助用户在一个步骤中执行多个命令。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以Redis 事务的执行并不是原子性的。Redis 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

怪咖@的博客 1万+

基于WebRTC的现代在线视频会议系统设计与实现

本文详细介绍了如何使用Java和WebRTC技术构建一个完整的在线视频会议系统。我们将从WebRTC的基础原理讲起,逐步深入到信令服务器设计、媒体服务器配置、前后端实现以及高级功能的开发。文章包含大量代码示例和架构图,旨在为开发者提供一套完整的视频会议解决方案。关键词:WebRTC、视频会议、Java、Spring Boot、信令服务器、STUN/TURN、媒体服务器:获取摄像头和麦克风访问权限:处理点对点连接和流传输:实现浏览器之间的任意数据交换。

2201_76125393的博客 189

浅谈Redis事务解决并发问题

简介 和其他大部分的 NoSQL 不同,Redis 是存在事务的,尽管它没有数据库那么强大,但是它还是很有用的,尤其是在那些需要高并发的网站当中。 使用 Redis 读/写数据要比数据库快得多,如果使用 Redis 事务在某种场合下去替代数据库事务,则可以在保证数据一致性的同时,大幅度提高数据读/写的响应速度。 在 Redis 中,存在多个客户端同时向 Redis 系统发送命令的可能性,因此同一个数据,可能在不同的时刻被不同的线程所操纵,这样就出现了并发下的数据一致的问题。 为了保证异性数据的安全性,Re

guotianqing的博客 1636

7 中望CAD二次开发-标注(一)(ZRX C++)

尺寸标注是CAD设计中最常用的功能之一,软件内置了很多标注功能,可以通过使用这些标注工具,对需要标注的长度、角度等一个个进行标注。但这显然效率很低,学习CAD二次开发,就是要把人从重复的劳动中解放出来,实现批量标注。本文介绍,如何通过二次开发进行标注。

cilvsuanna的博客 340

【GlobalMapper精品教程】027:路径剖面和和视线工具的使用

路径剖面视线工具允许您使用加载的高程数据集沿用户指定的路径获取垂直剖面。要定义生成3D路径剖面所遵循的路径,只需单击鼠标左键选择路径的点,然后石键单击以结束路径。出现一个带有剖面的对话框,允许您设置视线分析或优化端点。

智慧空间实验室 2051

redis中数据倾斜问题的产生和解决方案

在服务端系统服务开发中,缓存是一种常用的技术,它可以提高系统对请求的处理效率,而redis又是缓存技术栈中的一个佼佼者,广泛的应用于各种服务系统中。在大型互联网服务中,每天需要处理的请求和存储的缓存数据都是海量的,在这些大型系统中,使用单实例的redis,很难满足系统超高的并发请求以及海量数据缓存需求。大型的互联网服务中对于redis的使用,往往采用集群架构,通过横向扩展redis实例规模的方式,以较低的成本,来提升缓存系统对数据请求的处理效率和数据存储容量。 redis集群架构虽然有众多优点,但是事物往

weixin_45701550的博客 5929

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Redis原理之事务

为了解决该问题,Redis引入命令watch,该命令可以监控一个或多个key,一旦在事务期间(multi与exec之间,操作命令放入队列后),其他客户端对监控的key进行修改,就会让执行事务的客户端感知到key被修改了,从而告诉用户该问题的风险。可以发现最终结果是客户端1事务执行后的结果,原因是因为事务队列的命令虽然先到Redis服务器,但是并没有真正执行。假设在客户端1开启事务,把命令放入事务队列,此时客户端2不开启事务,对客户端1那个命令的key进行修改,客户端1再执行事务。

sniper_fandc的博客 988

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【Redis】事务处理

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。简单的解释就是一组命令,要吗全部执行,要吗全部执行。redis的事务虽然遵守ACID,但是与DB的事务相比,还是弱事务。总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。3,Redis 事务命令Redis 通过MULTI和EXEC命令执行事务操作,在执行EXEC。

精益求精 1079

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Redis 事务

Redis 事务: Redis事务相关的指令有五个,分别是MULTI、EXEC、DISCARD、WATCH、UNWATCH 4.3 EXEC(执行事务) 错误示例: 4.4 DISCARD(取消事务) 4.5 乐观锁、悲观锁 参考地址:点击查看何为事务冲突悲观锁:乐观锁 ☑️ 关于WATCH的细节和常见误区...

D的博客 2318

redis 7.乐观锁、悲观锁,Redis事务,秒杀案例,redisTemplate执行事务操作和LUA脚本

文章目录1.1 超卖问题1.2 超卖解决(1) RedisTemplate 的默认配置不支持事务1.3 解决库存遗留问题 1.1 超卖问题 大量用户并发的,向库存系统下订单: 1.2 超卖解决   利用乐观锁淘汰用户,解决超卖问题: @RestController @RequestMapping("/redisTest") public class RedisTestController { @Autowired private RedisTemplate redisTemplate

不是一枚开发 5万+

【redis】redis事务

乐观锁是一种并发控制策略,它假设在数据处理过程中,冲突的情况不常发生。因此,它不会在数据读取时立即上锁,而是在数据更新时检查是否有其他事务修改过数据。如果数据在读取和更新之间没有被其他事务修改,则更新操作成功;否则,更新操作失败。当客户端需要执行一个事务,并且希望在这个事务执行期间,某些键的值不被其他客户端修改时,可以使用。在Redis中,乐观锁的实现主要依赖于。4.2Redis事务乐观锁的实现。4.3乐观锁机制的优点和注意事项。4.1乐观锁的基本概念。

wosixiaokeai的博客 1365

Redis watch命令——监控事务

当 Redis 使用 exec 命令执行事务的时候,它首先会去比对被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;无论事务是否回滚,Redis 都会去取消执行事务前的 watch 命令,这个过程如图 1 所示。,而只是通过比较 watch 监控的键值对去保证数据的一致性,所以 Redis 多个事务完全可以在非阻塞的多线程环境中并发执行,而且 Redis 的机制是不会产生 ABA 问题的,这样就有利于在保证数据一致的基础上,提高高并发系统的数据读/写性能。

springdk2009的博客 1031

PHP使用Redis的Transaction(事务)命令

本篇笔记简单介绍了Redis的Transaction(事务)命令:discard,exec,multi,unwatch,watch作用和php的使用 1.Transaction命令 命令 作用 返回值 watch 监视一个或多个key 总是OK multi 声明事务开始,后续命令将排队按顺序等待exec执行 总是OK exec 顺序执行multi之后的命令,如果mult...

再现理想 1587

Redis事务详解,吃透数据库没你想的那么难

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。再分享一波我的Java面试真题+视频学习详解+技能进阶书籍《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!知识点,真正体系化!**

2401_84010901的博客 844
上一篇: 避开这些坑,你能更好的创建一个基于Redis分布式锁
下一篇: 700字带你认识什么是JVM中的进程和线程
Java_LingFeng
博客等级 码龄4年 505粉丝 259原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值