秒杀库存怎么答

秒杀场景下的库存扣减,核心矛盾是数据库行锁模型扛不住瞬时高并发。直接用 update stock = stock - 1 语法本身没有错误,但在 10 万人抢 100 件库存的极端场景下会直接触发服务雪崩:所有请求争抢同一行记录的行锁,99% 的请求都会进入阻塞等待,单条请求响应时间从毫秒级飙升到秒级,最终数据库连接池被占满,上游服务请求堆积超时,整体服务不可用。

行业内的标准解法是 Redis 预扣库存方案:把库存扣减的并发压力从数据库转移到内存级的 Redis,用 Redis 的高性能承接瞬时洪峰,数据库只做最终数据落地。

这套方案有三个必须解决的核心问题,对应三个落地方案:


问题一:库存数据如何在数据库和 Redis 之间同步

落地方案:活动预热初始化 + 活动结束回写,非实时同步
  • 活动预热:秒杀活动开始前,主动将商品库存数量从数据库一次性写入 Redis 对应的库存 Key,完成初始化。
  • 活动回写:秒杀活动结束后,将 Redis 中剩余的库存数量统一回写到数据库,作为最终结果。

整个过程中,数据库不承担实时的并发扣减压力,只负责存储最终一致性数据;Redis 承接全部实时扣减流量。

兜底补充:可增加低峰期定时校准任务,比对 Redis 和数据库的库存差值,修复极端异常导致的数据偏差。


问题二:如何保证库存扣减操作的原子性

落地方案:Lua 脚本打包「校验+扣减」,原子执行

Redis 的命令执行是单线程模型,单个命令天然具备原子性,但「判断库存是否大于 0 → 扣减库存」是两步操作,中间存在并发窗口,直接分开执行依然会出现超卖。

正确做法是将库存校验、扣减、结果返回整套逻辑封装在一个 Lua 脚本中提交给 Redis:

  • Redis 执行 Lua 脚本的整个过程是原子的,执行期间不会插入其他命令;
  • 请求进来先判断库存 > 0,满足则扣减 1 并返回成功;不满足则直接返回失败,没有中间状态,彻底消除并发窗口。

为什么不用 DECR 直接扣减:DECR 会把库存扣成负数,无法前置校验库存充足性,业务上不成立。


问题三:Redis 扣减成功,但后续订单创建失败,库存如何处理

落地方案:同步回滚兜底 + 消息队列异步补偿(生产级推荐)
  • 轻量场景:同步回滚
    链路较短、失败概率低的场景,订单创建失败时,立即调用 Redis 执行库存加回操作,快速回滚。
    缺点是耦合度高,回滚操作本身也可能失败。

  • 生产级方案:消息队列异步补偿(最终一致性)
    Redis 扣减库存成功后,立即发送一条「创建订单」消息到消息队列,由独立的消费者负责创建订单:

    • 订单创建成功则流程结束;
    • 创建失败则自动重试,超过最大重试次数后,触发库存回滚逻辑,将库存加回 Redis。

通过消息队列的可靠性保证,实现库存扣减和订单创建的最终一致性,同时解耦两条链路,避免相互影响。


方案核心总结

这套设计的本质是内存削峰 + 最终一致性:

  1. 用 Redis 的内存级高性能,承接秒杀场景的瞬时高并发扣减,解决数据库行锁的性能瓶颈;
  2. 通过 Lua 脚本保证扣减操作的原子性,避免超卖;
  3. 通过消息队列异步补偿,保证数据最终一致,兼顾性能和可靠性。
posted @ 2026-09-15 17:24  堭鍙銤  阅读(3)  评论(0)    收藏  举报