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

模块四:Redis 事务与Lua

注意: Spring的@Transactional和Redis事务是两回事,不要混淆。

哥们,你的Redis事务翻车了吗?——从一次库存超卖事故聊起

看我把Redis事务、Lua脚本、Pipeline那点破事给你捋明白

开场白:那个加班的夜晚

兄弟们,先给大家讲个真实发生在我身上的事儿。

那是去年双11前夜,我们系统要做库存扣减。我寻思着,Redis单线程、速度快,用MULTI/EXEC包一下不就完事儿了?原子性?那不手拿把攥吗!

结果呢?超卖了。

凌晨两点,运营小姐姐一个电话把我从被窝里薅起来:"哥,库存变负数了..."

我当时脑子"嗡"的一下,赶紧爬起来看日志。你猜怎么着?MULTI里面套了三个命令,第二个命令报错了,第一个和第三个照常执行了。

那一刻我才明白——Redis事务,它压根就不是你想的那种"事务"。

今天咱不背八股文,就着这个翻车现场,把Redis事务、Lua脚本、Pipeline这些玩意儿的底裤扒干净。


一、Redis事务:披着事务外衣的"假事务"

1.1 命令长啥样?看一眼就懂

bash

# 开整 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key1 value1 QUEUED 127.0.0.1:6379> INCR key2 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (integer) 1

看着挺像那么回事儿对吧?MULTI开启,命令排队,EXEC一气呵成。

但这里有个坑,面试官最爱问

Q:Redis事务支持回滚吗?

A:不支持。想都别想。

你一条命令写错了,语法报错?那整个事务都废了,这个倒是会"原子性"失败。

但如果是运行时错误,比如你用INCR去加一个字符串类型的key——

bash

127.0.0.1:6379> SET key1 "abc" OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET key2 value2 QUEUED 127.0.0.1:6379> INCR key1 QUEUED 127.0.0.1:6379> SET key3 value3 QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) ERR value is not an integer or out of range 3) OK

看到了吗?key1那行报错了,但key2key3照写不误。

这就是我那天晚上超卖的元凶——Redis事务不具备原子性,没有回滚机制。

官方文档原话:"如果事务中一条命令失败,其他命令仍然会继续执行。"

1.2 那WATCH是干啥的?乐观锁了解一下

你说不行,我得加锁。于是你用了WATCH

bash

WATCH balance GET balance # 假设拿到100 MULTI SET balance 120 EXEC

如果在你WATCH之后、EXEC之前,有其他客户端把balance给改了,那EXEC会返回(nil),整个事务取消。

这其实就是乐观锁(CAS)的实现思路。

但说实话,工作中用WATCH的兄弟多吗?我反正见得不多。为啥?

  1. 你得自己处理重试逻辑,代码里写一堆循环,烦不烦?

  2. 高并发下冲突频繁,性能堪忧

  3. 只能监视key有没有被改,没法做复杂判断

所以你看,Redis事务这玩意儿吧——能用,但不好用。


二、Lua脚本:真·原子操作救世主

2.1 为啥要用Lua?一个字:稳

那天晚上翻车之后,我就把库存扣减改成了Lua脚本。为啥?

因为Lua脚本在执行的时候,整个脚本是一气呵成的,Redis单线程在执行期间不会插入任何其他命令。

这才是真正的原子性。

而且你想想,原来你要扣库存,得三步走:

  1. GET查库存

  2. Java里判断够不够

  3. DECR扣减

这三步分开走,中间万一有并发请求插进来,数据就乱了。

现在用Lua,三步合一,一次发给Redis,中间谁也插不了队。

2.2 命令咋写?三分钟上手

最简单的Lua脚本长这样:

bash

# EVAL "脚本内容" 键数量 key列表 arg列表 EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 key1 value1

来个实战——库存扣减:

bash

EVAL " local stock = redis.call('GET', KEYS[1]) if stock and tonumber(stock) > 0 then redis.call('DECR', KEYS[1]) return 1 else return 0 end " 1 stock:123

这段脚本的逻辑是:

  • 查库存

  • 如果大于0,扣减,返回1(成功)

  • 否则返回0(失败)

一个请求,一次网络交互,原子完成。

你要是觉得每次发整个脚本太浪费带宽,可以先用SCRIPT LOAD把脚本缓存到Redis服务端:

bash

SCRIPT LOAD "return redis.call('GET', KEYS[1])" # 返回一个sha1值,比如:b5a8f5b8f5b8f5b8f5b8f5b8f5b8f5b8f5b8f5b8 # 后面用EVALSHA执行,省带宽 EVALSHA b5a8f5b8f5b8f5b8f5b8f5b8f5b8f5b8f5b8f5b8 1 key1

2.3 Java里咋写?Redisson一把梭

在Java项目里,我一般用Redisson:

java

String script = "local stock = redis.call('GET', KEYS[1]) " + "if stock and tonumber(stock) > 0 then " + " redis.call('DECR', KEYS[1]) " + " return 1 " + "else " + " return 0 " + "end"; Boolean result = redisson.getScript().eval( RScript.Mode.READ_WRITE, script, RScript.ReturnType.BOOLEAN, Collections.singletonList("stock:123"), Collections.emptyList() ); if (result) { // 扣减成功 } else { // 库存不足 }

就这么简单。一行脚本,原子扣库存,再也不用担心超卖了。

2.4 踩坑提醒:脚本超时别忽视

有个事得提醒兄弟们——Lua脚本是有超时时间的,默认配置是lua-time-limit 5000ms(5秒)。

你写了个死循环或者特别复杂的逻辑,超时了,Redis会怎么做?

  1. 脚本继续跑,但Redis开始拒绝其他命令

  2. 5秒后还没结束,Redis会记录日志,但不会主动kill脚本

  3. 只有执行SCRIPT KILL才能停(但如果是写操作,SCRIPT KILL都不好使,只能用SHUTDOWN NOSAVE重启)

所以,Lua脚本里别写循环!别写复杂逻辑!就做纯粹的Redis原子操作,复杂业务逻辑放Java层。


三、Lua脚本 vs 事务:别纠结,直接用Lua

我知道你们面试时肯定被问过这个问题,我直接给你个对比表,背就完了

维度Lua脚本事务(MULTI)
原子性✅ 整个脚本原子执行❌ 命令间可能失败,无回滚
条件判断✅ 支持if/else,随心所欲❌ 只能WATCH,不能做逻辑判断
网络开销小(一次发送)小(一次发送)
适用场景复杂原子操作简单命令队列(已过时)
推荐程度⭐⭐⭐⭐⭐ 优先使用⚠️ 建议放弃

面试话术来一个:

"我们项目中用Redis Lua脚本实现库存扣减、接口限流等场景,因为它能保证原子性,支持条件判断,比原生的MULTI/EXEC事务更灵活可靠。"


四、Pipeline:我要的是速度,不是原子性

4.1 Pipeline是干啥的?

兄弟们,你们有没有遇到过这种情况:要批量设置100个key,挨个发命令,来回100次网络请求,慢得要死。

Pipeline就是干这个的——把一堆命令打包,一次发过去,一次收回来。省去了每次请求的网络往返时间(RTT)。

java

// Lettuce的Pipeline示例 List<RedisFuture<Long>> futures = new ArrayList<>(); for (int i = 0; i < 100; i++) { futures.add(redisAsyncCommands.incr("counter:" + i)); } // 一次性提交 for (RedisFuture<Long> future : futures) { future.get(); // 等待所有结果 }

4.2 三个容易搞混的概念,我帮你捋清楚

很多新手分不清Pipeline、事务、Lua脚本的区别,我一句话给你说明白:

维度Pipeline事务(MULTI)Lua脚本
原子性❌ 不保证❌ 不保证✅ 保证
减少网络开销
后一条依赖前一条结果❌ 不行❌ 不行✅ 可以
适用场景批量无关命令已过时,不建议用复杂原子操作

简单说:

  • Pipeline= 打包发送,不保证原子性,适合批量写数据

  • 事务= 打包发送 + 部分原子性(但不回滚),鸡肋,别用

  • Lua脚本= 打包发送 + 完全原子性 + 逻辑判断,王道


五、Spring集成:别把@Transactional和Redis事务搞混了

最后说个容易踩的坑——Spring里集成了Redis事务,但别跟数据库的@Transactional混为一谈。

java

@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 开启事务支持 template.setEnableTransactionSupport(true); return template; } } // 使用时 @Service public class OrderService { @Autowired private RedisTemplate<String, Object> redisTemplate; public void test() { redisTemplate.multi(); // 开始Redis事务 try { redisTemplate.opsForValue().set("key1", "value1"); redisTemplate.opsForValue().set("key2", "value2"); // 注意!这里的异常不会触发Redis回滚! redisTemplate.exec(); // 执行事务 } catch (Exception e) { redisTemplate.discard(); // 手动丢弃 } } }

重要的事情说三遍:

Spring的@Transactional管的是MySQL,管不了Redis!

Spring的@Transactional管的是MySQL,管不了Redis!

Spring的@Transactional管的是MySQL,管不了Redis!

别指望在方法上贴个@Transactional,Redis事务出错了能自动回滚——那是做梦。


最后总结(面试官最爱听的那段)

兄弟们,干了这么多年,我给你们总结一句掏心窝子的话:

Redis事务(MULTI/EXEC)这玩意儿,知道就行,生产环境尽量别用。真要保证原子性,上Lua脚本。

如果面试官问你们"Redis如何实现原子操作",你直接回答:

"Redis的Lua脚本可以保证原子性,因为整个脚本会被当成一个命令执行,期间不会插入其他操作。我们项目里用Lua实现库存扣减、限流、分布式锁释放等场景,比原生的MULTI/EXEC事务更可靠。另外Pipeline可以用来批量发送命令减少网络开销,但它不保证原子性。"

http://www.jsqmd.com/news/1241538/

相关文章:

  • SpringBoot 3实现邮箱验证功能的最佳实践
  • 边缘AI智能相机:工业视觉检测从复杂部署到简易实战
  • 江诗丹顿中国售后服务网络全攻略|网站权威公布(2026年7月最新) - 江诗丹顿中国服务中心
  • 微信图片消息接收与解密全流程:从XML解析到AES解密实战
  • 谁说低代码只能 CRUD?现在表单里能塞一个“真按钮“了
  • 娄底GEO优化代理推荐?AI获客服务商怎么选 - 红枫叶GEO优化公司
  • 3分钟打造纯净Windows:小白也能上手的系统优化指南
  • Unity粒子系统制作逼真烟雾特效:从原理到性能优化全解析
  • Tiva™ TM4C129LNCZAD ADC高级配置:采样序列、相位控制与数字比较器实战
  • 2026北京劳力士官方直营服务中心公示,售后门店地址及官方咨询电话,腕表维保预约指南 - 劳力士中国维修中心
  • 从一次下单开始,彻底理解 Spring Cloud 与微服务:调用、容错、一致性、高并发到部署
  • Ubuntu 26.04 中文输入法安装
  • GEO优化中的SoA指标:定义及其优化指导意义
  • 电子合同作为证据:法院认定效力的核心逻辑与司法实践
  • 终极指南:如何快速掌握Linux设备驱动开发
  • AI代码仓库目录结构必须包含这8个核心文件夹,少1个就触发CI/CD阻断——2024年GitHub Top 100开源项目实证分析
  • 卖土壤检测仪器这些年,AI获客工具怎么让客户主动找到我? - 红枫叶GEO优化公司
  • 开源商城安全评估与加固实战:从漏洞修复到生产环境部署
  • 快速查询上海劳力士官方售后服务网点信息|官方热线电话、网点地址权威公示(2026年7月最新) - 劳力士中国维修中心
  • LangChain学习笔记(一):基础入门与核心概念详解
  • 深入解析TI AM18xx Bootloader与AIS引导脚本实战
  • 2026论文工具黑榜VS红榜!难怪大家都弃坑,只留PaperXie✅
  • 宿州黄金回收上门服务,璟安黄金回收,提前预约,准时抵达! - 新芸鼎珠宝首饰
  • 安阳全屋定制工厂哪家靠谱?八千业主实拍实景 + 真实评价,板材工艺干货一次性讲透 - 米諾
  • 医疗 Function Calling:辅助问诊工具链的安全校验层设计
  • 2026届学术党必备的十大AI辅助写作方案横评
  • OpenClaw开源AI平台安装与配置全指南
  • 嵌入式以太网MAC层硬件过滤与流控制配置实战
  • JumpServer开源堡垒机:企业运维安全与审计实践指南
  • 青岛做水产养殖加工的客户,AI获客系统怎么帮您应对客户犹豫? - 红枫叶GEO优化公司