登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis Lua脚本保证库存扣减与记录写入原子性的设计

来源:17golang原创

时间:2026-09-20 12:58:44 226浏览 收藏

库存扣减最怕两件事被拆开:可用库存已经减掉,但请求记录没有写入;或者两个客户端都读到“还有库存”,随后各自扣减。Redis Lua 的合适边界是把校验、扣减和记录写入放进同一次脚本执行,并让调用方只传入显式的键和业务参数。这样解决的是 Redis 内部操作的原子执行,不等于给跨系统支付、发货或数据库写入提供回滚。

要点速览
  • 库存键和请求记录键都放进 KEYS,数量、请求号和时间放进 ARGV
  • 先校验参数、已有请求和库存,再执行 DECRBYHSET,避免错误发生在写操作之后。
  • 返回码要区分成功、重复请求、库存不足和参数错误;业务副作用仍需应用层补偿。

Redis 官方地址:https://redis.io/docs/latest/

先把库存和请求记录设计成同一组键

示例使用一个商品维度的库存字符串和一个请求记录 Hash:stock:{sku-42}:available 保存剩余数量,stock:{sku-42}:ordersrequest_id 为 field 保存扣减结果。两者共享 {sku-42} hash tag,放到 Redis Cluster 时会落在同一 hash slot;脚本不需要根据库存值拼接新的键名。

输入示例语义
KEYS[1]stock:{sku-42}:available可扣减库存,字符串整数
KEYS[2]stock:{sku-42}:orders请求记录 Hash
ARGVrequest_id、quantity、now业务参数,不是键名
Redis Lua库存扣减中可用库存字符串、请求记录Hash与KEYS ARGV参数边界的结构说明图
图1:Redis Lua 库存脚本的键与参数边界说明图,展示库存值、请求记录和 KEYS/ARGV 的静态关系,非运行截图。

把校验放在扣减之前,再让两个写操作共享原子边界

下面的脚本用 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 脚本执行期间会阻塞其他服务器活动,因此两个写入不会被另一个客户端插入。这里仍要保留一个重要边界:脚本原子执行不是事务回滚;如果把不可预测的命令错误放在扣减之后,之前已经完成的写入可能留下。生产脚本应先验证类型、参数和键结构,并尽量保持短小。

Redis Lua脚本校验库存后原子扣减并写入请求记录的结果关系说明图
图2:库存校验、扣减结果与请求记录的原子关系说明图,展示成功、重复和库存不足三种返回边界,非运行截图。

返回码要让调用方能处理重复和失败

建议把返回值的第一个元素固定为机器可判断的码:1 表示本次扣减成功,2 表示相同请求号已经处理过,0 表示库存不足,-1 表示参数或库存初始化错误。第三个元素统一返回剩余库存,调用方无需再发一次 GET 才能展示结果。

返回码调用方动作不能做什么
1记录成功并继续业务流程不要再次扣减
2按幂等成功处理或查询原记录不要把重复请求当库存不足
0提示无库存或进入候补流程不要重试同一请求制造热循环
-1告警并修复参数或初始化不要静默吞掉脚本错误

上线前检查集群键位、记录保留和脚本耗时

上线前至少检查四项:库存键是否为整数、请求号是否真正唯一、两个键是否使用相同 hash tag、请求记录是否有清理策略。记录 Hash 如果无限增长,会把幂等保护变成新的内存问题;如果过早删除,重复请求又可能被当成新请求。还要监控脚本耗时,避免在 Lua 中遍历大量订单或调用外部系统。

常见问题

Lua 脚本能保证数据库和 Redis 同时成功吗?

不能。脚本只覆盖 Redis 内部的命令边界;数据库、支付和消息系统需要事务、幂等事件或补偿流程。

为什么不能把库存键名放在 ARGV 里?

Redis 要求脚本访问的键显式列在 KEYS 中,这对集群路由和脚本可分析性都更安全。ARGV 应只承载数量、请求号等普通参数。

重复请求返回成功还是重复更合适?

建议返回独立的重复码,并带回当前库存或原记录摘要。这样调用方可以把“已完成”与“本次新扣减”区分开,避免重复发货。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>