Redis Lua脚本保证库存扣减与记录写入原子性的设计
来源:17golang原创
时间:2026-09-20 12:58:44 226浏览 收藏
库存扣减最怕两件事被拆开:可用库存已经减掉,但请求记录没有写入;或者两个客户端都读到“还有库存”,随后各自扣减。Redis Lua 的合适边界是把校验、扣减和记录写入放进同一次脚本执行,并让调用方只传入显式的键和业务参数。这样解决的是 Redis 内部操作的原子执行,不等于给跨系统支付、发货或数据库写入提供回滚。
- 库存键和请求记录键都放进
KEYS,数量、请求号和时间放进ARGV。 - 先校验参数、已有请求和库存,再执行
DECRBY与HSET,避免错误发生在写操作之后。 - 返回码要区分成功、重复请求、库存不足和参数错误;业务副作用仍需应用层补偿。
Redis 官方地址:https://redis.io/docs/latest/
先把库存和请求记录设计成同一组键
示例使用一个商品维度的库存字符串和一个请求记录 Hash:stock:{sku-42}:available 保存剩余数量,stock:{sku-42}:orders 以 request_id 为 field 保存扣减结果。两者共享 {sku-42} hash tag,放到 Redis Cluster 时会落在同一 hash slot;脚本不需要根据库存值拼接新的键名。
| 输入 | 示例 | 语义 |
|---|---|---|
| KEYS[1] | stock:{sku-42}:available | 可扣减库存,字符串整数 |
| KEYS[2] | stock:{sku-42}:orders | 请求记录 Hash |
| ARGV | request_id、quantity、now | 业务参数,不是键名 |

把校验放在扣减之前,再让两个写操作共享原子边界
下面的脚本用 request_id 做幂等记录。已有记录直接返回重复,不再重复扣减;库存不足返回当前库存;只有所有前置检查通过,才执行扣减和记录写入。代码中的两个键都来自 KEYS,不要在脚本里把 ARGV 拼成键名。
-- KEYS[1] 是库存字符串,KEYS[2] 是请求记录 Hash -- ARGV[1] 请求号,ARGV[2] 扣减数量,ARGV[3] 记录时间 local request_id = ARGV[1] local quantity = tonumber(ARGV[2]) local now = ARGV[3] -- 先拒绝空请求、非正数量和重复请求,避免进入写阶段 if not request_id or request_id == '' or not quantity or quantity
调用时把键数量写成 2,后面依次传两个键和三个普通参数:
# 两个 KEY 必须显式列出;request_id、数量和时间属于 ARGV
redis-cli EVAL "$(cat stock.lua)" 2 \
stock:{sku-42}:available stock:{sku-42}:orders \
req-20260920-0007 2 1789891200
Redis 文档说明 Lua 脚本执行期间会阻塞其他服务器活动,因此两个写入不会被另一个客户端插入。这里仍要保留一个重要边界:脚本原子执行不是事务回滚;如果把不可预测的命令错误放在扣减之后,之前已经完成的写入可能留下。生产脚本应先验证类型、参数和键结构,并尽量保持短小。

返回码要让调用方能处理重复和失败
建议把返回值的第一个元素固定为机器可判断的码:1 表示本次扣减成功,2 表示相同请求号已经处理过,0 表示库存不足,-1 表示参数或库存初始化错误。第三个元素统一返回剩余库存,调用方无需再发一次 GET 才能展示结果。
| 返回码 | 调用方动作 | 不能做什么 |
|---|---|---|
1 | 记录成功并继续业务流程 | 不要再次扣减 |
2 | 按幂等成功处理或查询原记录 | 不要把重复请求当库存不足 |
0 | 提示无库存或进入候补流程 | 不要重试同一请求制造热循环 |
-1 | 告警并修复参数或初始化 | 不要静默吞掉脚本错误 |
上线前检查集群键位、记录保留和脚本耗时
上线前至少检查四项:库存键是否为整数、请求号是否真正唯一、两个键是否使用相同 hash tag、请求记录是否有清理策略。记录 Hash 如果无限增长,会把幂等保护变成新的内存问题;如果过早删除,重复请求又可能被当成新请求。还要监控脚本耗时,避免在 Lua 中遍历大量订单或调用外部系统。
常见问题
Lua 脚本能保证数据库和 Redis 同时成功吗?
不能。脚本只覆盖 Redis 内部的命令边界;数据库、支付和消息系统需要事务、幂等事件或补偿流程。
为什么不能把库存键名放在 ARGV 里?
Redis 要求脚本访问的键显式列在 KEYS 中,这对集群路由和脚本可分析性都更安全。ARGV 应只承载数量、请求号等普通参数。
重复请求返回成功还是重复更合适?
建议返回独立的重复码,并带回当前库存或原记录摘要。这样调用方可以把“已完成”与“本次新扣减”区分开,避免重复发货。
-
234 收藏
-
141 收藏
-
466 收藏
-
130 收藏
-
142 收藏
-
426 收藏
-
476 收藏
-
489 收藏
-
485 收藏
-
349 收藏
-
236 收藏
-
130 收藏
-
364 收藏
-
309 收藏
-
354 收藏
-
223 收藏
-
324 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习