秒杀库存扣减用redis原子自增的巨坑

这是一个非常经典的**"看似可行,实则巨坑"**的方案。

因为为了防止库存回补,所以你需要保证库存不能为负数, 所以,你在代码层面的逻辑是:

  1. redis.decr(key)
  2. if (返回值 < 0) { redis.incr(key); return "失败"; }

结论是:千万不要这样写。

虽然逻辑上看起来"借了再还"最后是平的,但在生产环境(特别是高并发或不稳定网络环境)下,这会引发数据永久不一致性能雪崩

主要有以下 3 个致命缺陷:

1. 致命缺陷:网络中断或服务崩溃导致"只减不加"

这是最大的风险点。因为 DECRINCR两次独立的网络请求,它们之间没有原子性保护。

场景推演:

  1. 当前库存为 0。
  2. 线程 A 发起 DECR,Redis 执行成功,库存变为 -1。
  3. 线程 A 收到返回值 -1,准备发起 INCR 进行补偿回滚。
  4. 就在这毫秒之间,发生了意外:
    • 情况一: 线程 A 所在的服务器宕机/重启了(比如被 Kubernetes OOM Kill 了)。
    • 情况二: 网络抖动,线程 A 发出的 INCR 请求包丢了,或者 Redis 的响应包丢了导致超时异常。
  5. 结果: INCR 永远没有被执行。
  6. 后果: Redis 里的库存永久停留在 -1
    • 接着有人退款(真正的回滚),库存变成了 0。
    • 本来应该恢复成 1 个库存给别人抢,现在因为那个永久的 -1,这 1 个库存被"吞"了。
    • 这就是典型的"库存泄露"。

2. 性能缺陷:Redis 写压力翻倍(写放大)

Redis 的读性能极高,但写性能相对昂贵(涉及到 AOF 刷盘、主从同步)。

场景推演: 假设库存 0,这时候来了 10,000 个并发请求。

  • 使用 Lua 脚本:

    • 10,000 个请求 -> Redis 执行 Lua -> 判断 stock <= 0 -> 直接返回 0。
    • Redis 实际发生的写操作:0 次(因为没有实际扣减)。
    • Redis 只是做了一次内存读取和逻辑判断,速度飞快,不产生 AOF 日志。
  • 使用 DECR + INCR 补偿方案:

    • 10,000 个请求 -> 10,000 次 DECR(库存狂掉到 -10000)。
    • 10,000 个请求发现小于 0 -> 10,000 次 INCR(库存慢慢加回 0)。
    • Redis 实际发生的写操作:20,000 次!
    • 这 2 万次操作全部要写 AOF 日志,全部要同步给从节点(Slave)。
    • 后果: 在没货的时候,反而把 Redis 的磁盘 IO 和网络带宽打满了,可能直接把 Redis 搞挂。

3. 竞态与监控干扰

  • 监控报警失效: 正常运维中,我们通常会设置"库存 < 0 报警"。如果你用这种方案,库存会频繁在负数跳动,报警系统会一直响,导致你无法区分是"真的出 Bug 了"还是"正常的补偿逻辑"。
  • 并发视觉干扰: 在高并发瞬间,你通过 Redis 客户端查看库存,可能会看到 -500 这种数字,很难排查问题。

总结

Lua 脚本的本质不仅仅是逻辑判断,更是为了"原子性"和"减少无效写操作"。

特性 Lua 脚本方案 DECR + INCR 补偿方案
原子性 完全原子(要么全做,要么不做) 无原子性(两个独立步骤,中间可能断开)
故障后果 即使服务崩了,库存依然准确 服务崩了 = 库存永久变负
Redis 写压力 库存不足时,0 写操作 库存不足时,2倍 写操作(最致命)
推荐指数 ⭐⭐⭐⭐⭐ ☠️ (绝对禁止)

所以,宁愿多写几行 Lua 脚本代码,也不要为了省事用 DECR 后再 INCR,风险成本太高了。

相关推荐
码事漫谈4 分钟前
C++高并发编程核心技能解析
后端
码事漫谈4 分钟前
C++与浏览器交织-从Chrome插件到WebAssembly,开启性能之门
后端
源代码•宸1 小时前
goframe框架签到系统项目(BITFIELD 命令详解、Redis Key 设计、goframe 框架教程、安装MySQL)
开发语言·数据库·经验分享·redis·后端·mysql·golang
⑩-1 小时前
SpringCloud-Nacos 配置中心实战
后端·spring·spring cloud
java1234_小锋2 小时前
[免费]SpringBoot+Vue勤工助学管理系统【论文+源码+SQL脚本】
spring boot·后端·mybatis·勤工助学
踏浪无痕3 小时前
从 Guava ListenableFuture 学习生产级并发调用实践
后端·面试·架构
❀͜͡傀儡师3 小时前
SpringBoot 扫码登录全流程:UUID 生成、状态轮询、授权回调详解
java·spring boot·后端
可观测性用观测云4 小时前
观测云在企业应用性能故障分析场景中的最佳实践
后端
一 乐4 小时前
酒店预约|基于springboot + vue酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端