当前位置:首页 > 文章列表 > 数据库 > Redis > Redis Functions 部署库存扣减逻辑的幂等边界

Redis Functions 部署库存扣减逻辑的幂等边界

来源:17golang原创 2026-10-01 20:27:48 0浏览 收藏

库存扣减真正难处理的不是“减一”,而是客户端超时后不知道第一次调用是否已经成功。如果调用方直接重试,同一订单可能再次扣减;如果一律不重试,又可能把一次网络抖动误判成失败。Redis Functions 适合把“查幂等回执、校验库存、扣减、写回执”封装成一个服务端原子操作,但它只解决 Redis 内部这一段,不会自动把订单数据库、支付和消息系统变成同一个事务。

官方文档:https://redis.io/docs/latest/develop/programmability/functions-intro/

业务负载:一次扣减为什么会被调用两次

假设下单接口按单个 SKU 扣减库存。请求带有 sku_id、order_id、扣减数量和幂等回执有效期。服务端处理完成后,响应可能在网络中丢失;网关、消息消费者或客户端会按同一个 order_id 重试。此时系统需要返回第一次的结果,而不是再次执行扣减。

本文把结果契约限定为四种状态:

返回码含义是否写成功回执
1首次扣减成功是
2重复请求,返回首次成功后的剩余库存读取已有回执
0当前库存不足本文示例不写
-2库存键不存在否

“库存不足是否也要幂等”必须由业务决定。示例只固化成功结果,因此补货后同一订单重试可能成功;如果业务要求第一次库存不足后永远返回不足,就要把失败原因也写入回执,并明确它的有效期。

约束条件:原子执行不等于永久幂等

Redis 函数的执行具有原子性,执行期间会阻塞其他客户端操作,因此检查库存与扣减之间不会被另一条命令插入。但原子性只覆盖当前 Redis 实例上的函数调用,还存在五个必须显式承认的边界:

  • 回执键过期或被淘汰后,同一订单再次到达会被当作新请求。
  • Redis Cluster 中函数访问的键必须显式作为 key 参数传入,并满足槽位约束。
  • 函数运行越久,阻塞其他客户端的时间越长,不能在函数里扫描大集合或做慢循环。
  • Lua 运行时错误不会提供关系型数据库式的自动回滚,因此所有可预检条件应在第一次写入前完成。
  • Redis 成功不代表订单数据库已提交;跨系统一致性仍需事务消息、补偿或对账。

Redis Functions 从 Redis 7.0 起可用。函数库会作为数据库的一等对象被持久化和复制,但最终耐久性仍取决于实际的 AOF/RDB、复制、故障切换和备份策略。

方案对比:为什么这里选择 Redis Functions

方案优点主要代价
客户端先 GET 再 DECRBY实现简单检查与扣减之间存在并发窗口,重复请求也无回执
WATCH / MULTI使用原生命令冲突时客户端重试,热点 SKU 下重试成本明显
EVALSHA可原子组合命令脚本缓存可能丢失,应用需要处理加载与 NOSCRIPT
Redis Functions命名函数、函数库统一部署,并随数据持久化与复制要求 Redis 7+,部署和升级属于数据库运维动作

对稳定复用的库存 API,Redis Functions 的价值不只是减少一次网络往返,而是把函数名、参数、返回码和版本变成可部署契约。调用方只需要使用 FCALL,不再随请求发送整段脚本。

推荐架构:库存键与回执键同槽

每次调用传入两把 key:库存键保存可用数量,幂等键保存首次成功后的剩余数量。Redis Cluster 使用大括号中的哈希标签计算槽位,因此下面两把键都会按 {sku-42} 路由:

  • stock:{sku-42}:available
  • idem:{sku-42}:order-9001

把订单号放入幂等键可以隔离不同订单,把 SKU 哈希标签放在两把键中可以确保函数访问同一槽。若一张订单同时扣多个 SKU,这个模型就不再成立;应拆分为每 SKU 预占,再由订单层编排和补偿,而不是用跨槽函数硬凑成全局事务。

Redis Functions 库存键与幂等回执键静态结构图
图1:结构示意图,展示库存服务、数量参数、同槽库存键、幂等回执键、扣减函数和返回结果之间的静态关系。

实现函数:先完成校验,再执行写入

下面的函数只处理一个 SKU。它先读取幂等回执,再检查参数和库存;只有全部条件满足后,才执行 DECRBY 与 SET EX。写函数不要声明 no-writes 标志,Redis 默认会把未声明标志的函数按可能读写处理。

#!lua name=inventory_lib_v1

-- 返回:{1, remaining} 首次成功;{2, remaining} 重复成功;
--      {0, current} 库存不足;{-2, 0} 库存键不存在。
local function inventory_deduct(keys, args)
  if #keys ~= 2 then
    return redis.error_reply('ERR expected stock key and receipt key')
  end

  local quantity = tonumber(args[1])
  local ttl_seconds = tonumber(args[2])
  if not quantity or quantity 

代码里没有使用 SET NX,因为同一个函数调用期间不存在另一位客户端插入执行的机会;真正的幂等判断是函数开头对回执键的读取。这里仍然先读取两个键并校验参数,避免常见的类型错误发生在扣减之后。

部署与调用:把函数库当成数据库制品

函数库内容以 shebang 开头,当前 Lua 引擎名称为 lua。加载是管理动作,调用是业务动作,二者应使用不同权限。发布流水线可以通过标准输入加载文件:

# 用同名函数库整体替换旧定义,加载前应在预发环境完成兼容测试。
redis-cli -x FUNCTION LOAD REPLACE 

REPLACE 替换的是同名函数库,库内函数不能单独更新。为了避免旧调用方在参数或返回码变化时被突然破坏,建议保持 inventory_deduct_v1 契约不变;不兼容变更注册为 inventory_deduct_v2,调用方逐步切换后再清理旧版本。

调用时,FCALL 后面的数字 2 表示紧随其后的两个参数是 key,其余是普通参数:

# 扣减 sku-42 的 3 件库存,成功回执保留 7 天。
redis-cli FCALL inventory_deduct_v1 2 \
  'stock:{sku-42}:available' \
  'idem:{sku-42}:order-9001' \
  3 604800

# 使用同一订单号重试时读取首次回执,不应再次扣减。
redis-cli FCALL inventory_deduct_v1 2 \
  'stock:{sku-42}:available' \
  'idem:{sku-42}:order-9001' \
  3 604800

生产调用方应把数组返回值映射成稳定的领域结果,不要把 Redis 错误文本直接暴露给终端用户。连接超时、MOVED/ASK 路由和服务端错误仍由 Redis 客户端层处理;只有使用同一订单号、同一 SKU 和同一参数重试,才符合本文的幂等契约。

Redis Functions 函数库部署与调用边界静态结构图
图2:结构示意图,展示函数库、版本化函数名、管理部署、FCALL 调用、ACL、持久化与副本之间的边界。

风险点:这段函数不能替你保证什么

1. 回执 TTL 就是重复扣减防线的时间边界

示例设置 7 天只是展示参数,不是通用推荐值。TTL 至少应覆盖 API 重试、消息重投、人工补偿和订单回放的最长窗口。若业务要求订单永久不可重复扣减,应把最终结果写入持久订单账本,并让 Redis 回执只承担快速判重。

2. maxmemory 淘汰可能提前删除回执

即使 TTL 尚未到期,允许淘汰的内存策略仍可能删除幂等键。高价值库存不能把 Redis 中一个可淘汰键当作唯一事实来源。需要结合独立实例、合适的淘汰策略、内存水位监控和业务账本来设计。

3. 原子执行不是回滚事务

函数执行期间其他客户端看不到中间状态,但 Lua 运行时发生错误时,Redis 不会像关系型数据库那样自动撤销已经执行的写命令。因此函数要保持短小,所有参数、键类型和可预见条件尽量在首个写命令之前检查,不在写入后调用可能因输入而失败的复杂命令。

4. Redis 成功与订单提交仍可能分叉

函数返回成功后,订单数据库写入可能失败;反过来,Redis 响应也可能丢失。常见做法是把库存扣减视为预占,订单系统记录同一个业务幂等键,再通过超时释放、事务消息或周期对账修复分叉。本文函数提供的是可靠的 Redis 侧原语,不是跨系统的最终一致性方案。

5. 慢函数会阻塞整个 Redis 执行线程

函数应只做固定次数的读写,不执行 SCAN、大范围集合遍历或外部 I/O。Redis 脚本运行在沙箱中,不能访问文件系统和网络;超过忙碌阈值也不会被安全地自动中断已写入的函数。

落地清单

  • 给函数名、参数顺序、返回码和错误语义建立版本化契约。
  • 让库存键与幂等键使用相同哈希标签,并全部通过 FCALL 的 key 参数传入。
  • 保证同一订单重试时 SKU、数量和业务语义不变;参数变化应使用新业务请求。
  • 按消息重投和人工补偿周期确定回执 TTL,不照搬示例值。
  • 在首个写命令前校验参数、键存在性、数据类型和库存值域。
  • 把函数库加载权限与业务调用权限分离,记录每次库版本发布。
  • 确认 AOF/RDB、复制、故障切换、备份和 maxmemory 策略与库存等级匹配。
  • 为库存预占建立释放、对账和人工修复通道,覆盖 Redis 与订单库分叉。

相关问题

Redis Functions 比 Lua EVAL 一定更快吗?

选择 Functions 的主要理由是可管理、命名、持久化和统一部署,不应把它简单宣传成无条件更快。真实性能仍取决于命令数量、数据大小和函数执行时间。

库存不足结果要不要写幂等键?

如果补货后允许原订单重试成功,就不要固化库存不足;如果首次结果必须稳定,就应保存失败回执,并把有效期写入业务契约。

可以在一个函数里扣减多个 SKU 吗?

单机模式技术上可以访问多键,但 Cluster 要受槽位约束。多个 SKU 通常分布在不同槽,更适合拆成单 SKU 预占,由订单层协调补偿,而不是依赖跨槽函数。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL utf8mb4 排序规则迁移的字段检查MySQL utf8mb4 排序规则迁移的字段检查
上一篇
MySQL utf8mb4 排序规则迁移的字段检查
painter绘画助手画布大小有限制吗?内存、图层与设备性能说明
下一篇
painter绘画助手画布大小有限制吗?内存、图层与设备性能说明
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    289次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    342次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    344次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    308次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    130次使用