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

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

文章插图
下面是针对这段代码的解释,简单来说就是开启事务,将 Redis 命令顺序放到一个队列中,然后最后一起执行,且保证原子性 。
setEnableTransactionSupport表示是否开启事务支持,默认不开启 。
一次 Redis 事务使用不当引发的生产事故

文章插图
难道开启了 Redis 事务,还能影响 Spring 事务中的 Redis 操作?
2.5 验证推测三如下表,序号 3 和 序号 4 的场景都是开启了 Redis 的事务支持,两个场景的区别是是否加了 @Transactional 注解 。
一次 Redis 事务使用不当引发的生产事故

文章插图
为了验证上面的场景,我们来做个实验:
  • 先开启 Redis 事务支持,然后执行 Redis 的事务命令 multi和 exec。
  • 验证场景 3:在 @Transactional 注解的方法中执行 Redis 的递增操作 。
  • 验证场景 4:在非 @Transactional 注解的方法中执行 Redis 的递增操作
2.5.1 执行 Redis 事务首先就用 Redis 的 multi 和 exec 命令来设置两个 key 的值 。
一次 Redis 事务使用不当引发的生产事故

文章插图
如下图所示,设置成功了 。
一次 Redis 事务使用不当引发的生产事故

文章插图
2.5.2 @Transactional 中执行 Redis 命令接下来在标注有 @Transactional 注解的方法中执行 Redis 的递增操作 。
一次 Redis 事务使用不当引发的生产事故

文章插图
多次执行这个命令返回的结果都是 null,这不就正好重现了!
一次 Redis 事务使用不当引发的生产事故

文章插图
再来看 Redis 中 count 的值,发现每执行一次 API 请求调用,都会递增 1,所以虽然命令返回的是 null,但最后 Redis 中存放的还是递增后的结果 。
一次 Redis 事务使用不当引发的生产事故

文章插图

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

文章插图
接下来我们验证下场景 4,先执行 Redis 事务操作,然后在不添加 @Transactional 注解的方法中执行 Redis 递增操作 。
一次 Redis 事务使用不当引发的生产事故

文章插图
用 Postman 调用这个接口后 , 正常返回自增后的结果,并不是返回 null 。说明在非 @Transactional 中执行 Redis 操作并没有受到 Redis 事务的影响 。
一次 Redis 事务使用不当引发的生产事故

文章插图
四个场景的结论如下所示,只有第三个场景下,Redis 的递增操作才会返回 null 。
一次 Redis 事务使用不当引发的生产事故

文章插图
问题原因找到了,说明 RedisTemplete 开启了 Redis 事务支持后,在 @Transactional 中执行的 Redis 命令也会被认为是在 Redis 事务中执行的 , 要执行的递增命令会被放到队列中,不会立即返回执行后的结果,返回的是一个 null,需要等待事务提交时,队列中的命令才会顺序执行,最后 Redis 数据库的键值才会递增 。
三、源码解析那我们就看下为什么开启了 Redis 事务支持,效果就不一样了 。
找到 Redis 执行命令的核心方法,execute 方法 。
一次 Redis 事务使用不当引发的生产事故

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

文章插图
接着往下看,关键代码如下所示,当开启了 Redis 事务支持 , 且添加了 @Transactional 注解时,就会执行 Redis 的 mutil 命令 。
关键代码:conn.multi();
一次 Redis 事务使用不当引发的生产事故

文章插图
Redis Multi 命令用于标记一个事务块的开始,事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行 。
真相大白,开启 Redis 事务支持 + @Transactional 注解后,最后其实是标记了一个 Redis 事务块,后续的操作命令是在这个事务块中执行的 。
比如下面的的递增命令并不会返回递增后的结果,而是返回 null 。

推荐阅读