Redis 明明没有报错,为什么库存还是超卖了?一次线上事故完整复盘
2026/6/10 18:51:43 网站建设 项目流程

很多开发者认为:只要 Redis 扣库存成功,并且 Redis 没有报错,就不会发生超卖。真实情况并非如此。本文通过一次电商秒杀系统的线上事故复盘,完整分析从事故发生、排查过程、错误推断、根因定位到最终修复的全过程,并总结高并发库存系统的工程实践。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e32ed1231bf444d5a4229d47d53d7c48.png#pic_center

一、事故背景

项目是一套电商秒杀系统。

技术架构:

  • Spring Boot
  • Redis
  • MySQL
  • RabbitMQ
  • Nginx

业务流程:

用户下单 ↓ Redis扣库存 ↓ 发送MQ ↓ 创建订单 ↓ 更新数据库库存

活动开始前配置:

商品:运动耳机 库存:1000 预计流量:8万

活动开始后十分钟。

客服收到投诉:

已经下单成功 却显示库存不足

随后运营发现:

订单数:1037 库存:1000

系统出现超卖。


二、第一轮排查

怀疑Redis异常

第一反应自然是 Redis。

检查:

  • CPU
  • 内存
  • 网络
  • 慢查询日志
  • 主从状态

全部正常。

监控没有任何异常。

Redis 甚至没有一条错误日志。

检查扣库存代码

Longremain=redisTemplate.opsForValue().decrement("stock:1001");if(remain<0){thrownewRuntimeException("库存不足");}

代码没有明显问题。

Redis DECR 是原子操作。

理论上不会超卖。


三、错误方向分析

错误方向1:Redis不原子

很多开发者会认为:

高并发下是不是 Redis 自己出了问题?

实际上:

Redis 单线程执行命令。

DECR 本身具有原子性。

所以:

Redis原子 ≠ 整个业务原子

这是很多人的误区。

错误方向2:主从延迟

有人怀疑:

读到了从库旧数据。

检查后发现:

库存操作全部走主库。

没有读写分离。

排除。


四、真正的问题出现了

继续追踪链路。

发现订单系统引入了 MQ。

架构如下:

用户下单

Redis扣库存

发送MQ

订单服务

数据库

事故期间。

RabbitMQ 出现积压。

部分消息发送超时。

这里埋下了隐患。


五、根因分析

出现以下情况:

场景A:

Redis扣减成功 ↓ MQ发送失败 ↓ 订单创建失败

库存减少。

订单没有。

此时系统进入不一致状态。

为了恢复。

运维执行库存补偿。

库存+1

问题看似解决。

实际上部分消息后来恢复成功。

于是:

库存补偿 + 订单再次创建

最终导致超卖。


六、为什么监控没有发现

技术监控只关注:

  • Redis
  • MySQL
  • MQ
  • CPU
  • 内存

但缺少业务监控。

例如:

库存扣减成功数 订单创建成功数 库存回滚数

这些指标没人看。

于是:

技术正常。

业务异常。


七、如何复现问题

测试代码:

decreaseStock();sendMessage();thrownewRuntimeException();

结果:

Redis库存减少 订单失败 库存未恢复

数据开始不一致。

当补偿逻辑介入时。

风险进一步扩大。


八、解决方案对比

方案一:分布式锁

RLocklock=redissonClient.getLock("stock");lock.lock();try{//扣库存}finally{lock.unlock();}

优点:

简单。

缺点:

吞吐量下降。


方案二:Lua脚本

localstock=redis.call('get',KEYS[1])iftonumber(stock)<=0thenreturn-1endredis.call('decr',KEYS[1])return1

优点:

高性能。

缺点:

无法解决链路一致性。


方案三:库存预占

流程:

预占库存 ↓ 创建订单 ↓ 支付成功 ↓ 正式扣减

失败则释放。

这是很多大型电商的方案。


九、幂等设计

消费端必须保证:

if(orderExists(orderNo)){return;}

否则:

消息重复投递。

订单重复创建。

库存再次异常。


十、库存流水设计

建立流水表:

CREATETABLEstock_flow(idBIGINT,order_noVARCHAR(64),product_idBIGINT,change_numINT,create_timeDATETIME);

任何库存变化必须记录。

方便审计。

方便追溯。

方便补偿。


十一、最终架构

Redis预扣库存

发送MQ

订单创建

支付成功

正式扣减库存

失败回滚

释放库存

上线后经历多次大促。

未再出现超卖。


十二、事故复盘总结

这次事故最重要的结论:

很多开发者把:

Redis原子操作

等同于:

系统不会超卖

实际上:

Redis 只能保证命令原子。

不能保证整个业务链路一致。

真正需要关注的是:

  • 幂等
  • 补偿
  • 流水
  • MQ可靠性
  • 业务监控

我的建议

如果你的系统:

  • 日订单 < 1000

直接数据库事务即可。

如果:

  • 秒杀
  • 抢购
  • 高并发活动

一定要提前设计:

  1. 库存预占
  2. 幂等控制
  3. MQ重试
  4. 库存流水
  5. 业务监控

否则迟早会踩坑。


结语

线上事故最可怕的不是报错。

而是:

系统看起来一切正常。

监控一切正常。

用户却已经受到影响。

Redis 很强。

但 Redis 不是银弹。

真正解决超卖问题的,从来不是一个命令,而是一套完整的工程体系。

如果你长期使用 Cursor、Claude Code、ChatGPT Plus、Gemini Advanced、Grok、Kiro 等工具,也可以顺手了解 gpt108.com。它主要解决相关 AI 工具的订阅需求。但对于后端开发者来说,工程设计能力永远比工具更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询