电商秒杀场景下,如何用Redis+Lua脚本实现库存原子性扣减(附完整代码)

电商秒杀场景下,如何用Redis+Lua脚本实现库存原子性扣减(附完整代码)

又到了一年一度的大促季,技术团队最紧张的时刻也随之而来。去年双十一,我们团队负责的核心商品秒杀模块,在峰值流量冲击下,后台日志里惊现了几笔“库存为负”的诡异订单。虽然通过紧急人工核对与补偿机制解决了问题,但那次惊心动魄的经历,让我们彻底明白:在高并发扣减库存这个战场上,任何理论上的“大概率可行”都是危险的,必须追求绝对的原子性与一致性。经过后续几个月的重构与压测,我们最终将一套基于Redis与Lua脚本的方案打磨成熟,成功扛住了后续数次大促的考验。今天,我就把这套经过实战检验的“防超卖”核心方案拆解开来,从设计思想到每一行代码,毫无保留地分享给你。

这套方案的核心在于,将库存扣减这个最关键的“读-判断-写”操作,从应用层下推到Redis服务器内部,通过Lua脚本保证其执行的原子性。这听起来简单,但魔鬼藏在细节里:如何设计键结构?如何处理预扣减与最终扣减?脚本异常了怎么办?缓存与数据库如何最终一致?我会结合具体的业务场景,一步步带你搭建起这个既能扛住流量洪峰,又能保证数据准确性的系统。

1. 为什么传统方案在秒杀场景下会“失灵”?

在讨论具体技术实现之前,我们必须先理解秒杀场景带来的独特挑战。它不仅仅是“高并发”那么简单,而是具备几个鲜明的特征:瞬时流量极高资源竞争极度集中(热门SKU)、对响应延迟极其敏感要求绝对的数据一致性(不能超卖)。在这种背景下,很多常规的并发控制方案会显得力不从心。

数据库乐观锁是最常被首先考虑的方案。它的原理是为库存记录增加一个版本号字段,更新时校验版本号是否变化。在中小流量下,这确实有效。但在秒杀场景,当一万个请求同时查询到同一个版本号并尝试更新时,最终只有一个请求能成功,其余九千九百九十九个请求都会更新失败,需要重试。这会导致两个严重问题:一是数据库承受了大量无效的更新操作,连接池迅速被占满;二是用户体验极差,用户反复点击却总是提示“库存不足”或“系统繁忙”。本质上,这是将并发控制的压力完全转移给了数据库,而数据库的锁竞争和行锁开销在这种场景下是灾难性的。

数据库悲观锁(如SELECT FOR UPDATE) 则走入了另一个极端。它确实能保证强一致性,但在秒杀开始的一瞬间,所有请求都会在数据库层面排队等待锁释放,系统吞吐量会急剧下降,甚至可能因为大量连接等待超时而导致服务雪崩。这相当于用一条极其狭窄的通道去疏导洪水。

纯应用层的分布式锁,例如使用Redis的SETNX命令,是一个进步。它通过一个中心化的锁服务来串行化请求。然而,其性能瓶颈在于网络往返(RTT)和锁的粒度。每一次扣减都需要“获取锁->查询库存->判断->扣减库存->释放锁”至少三次网络通信。在每秒数万次请求下,这些网络开销累积起来非常可观。更棘手的是,如果在执行扣减业务逻辑时客户端发生GC停顿或网络抖动,可能导致锁超时被其他请求获取,进而出现重复扣减的超卖风险。

提示:在高并发场景下,网络通信次数是影响性能的关键因素之一。应尽可能将多个操作合并,减少客户端与服务器之间的往返交互。

那么,理想的方案应该是什么样的?它需要满足以下几个条件:

  1. 原子性:库存查询、判断、扣减必须作为一个不可分割的整体执行。
  2. 高性能:操作必须在内存中完成,延迟极低。
  3. 高吞吐:能够并行处理大量请求,避免串行化瓶颈。
  4. 可扩展:能够轻松应对分布式部署。

Redis + Lua脚本 的组合,恰好能完美契合这些要求。Redis提供内存级的高速访问,而Lua脚本则在Redis服务器端原子化地执行一系列命令。

2. 核心架构设计:分层与状态流转

直接上代码之前,我们需要一个清晰的顶层设计。我们的目标不是简单地用Redis替代数据库,而是构建一个缓存为主、数据库兜底、异步同步的协同体系。整个库存管理的生命周期被划分为几个关键状态,如下图所示(概念模型):

用户可售库存 (Available Stock)
        |
        | 下单预扣减
        v
冻结库存 (Frozen Stock)
        |
        | 支付成功
        v
已售库存 (Sold Stock)
        |
        | 支付失败/取消
        v
用户可售库存 (Available Stock)

为了在Redis中高效映射这个状态模型,我们设计了以下键结构:

Redis键名 类型 描述 示例
stock:avail:{sku_id} String 商品的可售库存数量。秒杀开始时从数据库加载。 stock:avail:1001
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值