活动名额被抢超了:用 Redis Lua 脚本加分布式锁把报名扣减做成原子操作

简介: 本文详解商协会活动报名系统库存扣减的演进:从数据库“查-判-更”超卖,到乐观锁重试、Redis DECR负数问题,最终通过原子化Lua脚本(含库存校验、去重、扣减、记名)彻底解决。辅以分布式锁防重复提交、异步落库+回滚+对账机制,实现零超卖、P99降至60ms,逻辑清晰可追溯。(239字)

商协会的活动报名有个特点:名额有限、开放时间集中。一场限五十人的活动,通知下午三点发出去,三点零几分流量就上来了。早先扣减逻辑是查库存、判断、更新三步,压测一跑就超卖——五十个名额卖出去五十六个。

这篇把改造过程整理一遍,重点不是贴代码,而是每一步为什么这么改。

第一版是纯数据库操作:SELECT 查剩余名额,大于零就 UPDATE 减一。并发下"查再改"不是原子的,两个请求同时查到还剩一个,都会去减。第二版加乐观锁,UPDATE 时带 stock > 0 的条件,用影响行数判成败。并发不高时够用,但冲突多了大量请求要重试,响应时间不可预测。

第三版换成 Redis,直接用 DECR 扣库存。这版有两个新毛病:DECR 会减成负数,只能事后发现;而且不知道是谁抢到的,补记录操作和扣减不是原子的,失败就出现"扣了库存但没报名记录"。

最终版把整个判断和扣减塞进一段 Lua。Redis 执行 Lua 是单线程的,脚本内多条命令不会被打断,天然原子。

-- KEYS[1]: 活动库存键    KEYS[2]: 已报名会员集合
-- ARGV[1]: 会员ID        ARGV[2]: 集合过期时间(秒)
-- 返回值: 1 成功 / 0 已售罄 / -1 库存未初始化 / -2 重复报名
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock == nil then return -1 end
if stock <= 0 then return 0 end
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then return -2 end
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
redis.call('EXPIRE', KEYS[2], tonumber(ARGV[2]))
return 1

判库存、判重、扣减加记名,一次调用完成。判重放在集合里做,同一会员重复提交直接返回 -2,不用依赖数据库的联合索引兜底。

服务端用 EVALSHA 调用,脚本首次加载后复用 SHA,减少网络传输。NOSCRIPT 报错时重新加载再执行一次即可。

服务端用 EVALSHA 调用,脚本首次加载后复用 SHA,减少网络传输,遇到 NOSCRIPT 报错时重新加载再执行一次即可。调用侧还包了一层分布式锁:锁的粒度是会员加活动,用 SET NX PX 拿锁,拿不到就返回并发冲突;执行完在 finally 里释放,释放前用 Lua 比对令牌,避免误删其他请求持有的锁。扣减成功后异步写库,写库失败要把库存加回去。

几个坑值得单独说。

一是锁不是用来扣库存的。库存扣减已由 Lua 保证原子,锁只挡同一会员在极短时间内的重复点击,粒度是会员加活动,不是整个活动。把锁加到活动上等于把并发串行化,跟没上 Redis 一样。

二是锁要带过期时间,释放时比对令牌。设了值不设过期,进程崩了锁就永远在那儿;释放时不比对令牌,可能删掉别的请求刚拿到的锁。这两条是老规矩,线上出问题的多半还是它们。

三是异步落库失败必须回滚。扣减成功、写库失败不 INCR 回去,名额就凭空少一个。回滚本身也可能失败,所以还要一层对账:定时任务比对 Redis 库存和数据库报名数,不一致时以数据库为准。

四是库存初始化要有保护。活动创建时把名额写进 Redis,这步失败或者 Redis 重启后键丢了,脚本返回 -1。这时回落到数据库查真实报名数,重设库存键再走一次流程,别直接把请求拒掉。

五是别把 Redis 当可靠存储。它是加速层,最终以数据库为准,报名记录入库时仍然依赖数据库的联合约束防重复。

改造之后压测,五十个名额并发抢购超卖为零,接口 P99 从四百多毫秒降到六十毫秒左右。收获不在性能,而在逻辑变得可解释:库存在哪、谁抢到了、失败原因是什么,每条都有出处。

我们这边在跑的商会管理系统叫未来漫城·商会互联平台,活动报名这块就是这套实现,热门活动开放报名时不再需要人为盯着库存。

最后提醒一句:Lua 脚本要保持短小。脚本执行期间 Redis 单线程阻塞,里面放循环或慢操作会拖垮整个实例。像"批量扣减多个活动"这种需求,宁可调用多次,也不要写成一次大循环。

相关文章
|
5天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1478 0
|
5天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1128 0
|
14天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3774 4
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
5天前
|
人工智能 安全 前端开发
刚刚 GPT-6 Astra 发布,全球最强,AGI 时代到来!
OpenAI 正式推出 GPT-6 Astra 模型,带大家看看这次 GPT 有哪些提升,跟 Claude Fable 5.1 有什么差距?AI 编程能力如何?AGI 真的来了么?
631 0
|
2天前
|
SQL 人工智能 前端开发
QoderWake 1.0 正式发布:从桌面里的 Agent,到工作现场的数字员工
QoderWake v1.0正式发布:企业级数字员工团队平台。支持“一句话建岗”,预置10类特训岗位;Waker常驻钉钉/飞书群,@即响应、自动协作、跨任务记忆;具备定时/事件/API多触发方式与统一任务看板;已沉淀27.6万条记忆、12.3万项技能,助力组织实现人机协同增效。
605 0
|
6天前
|
网络协议 Linux iOS开发
【2026实测】Wireshark下载+安装+汉化+使用教程(图文版,巨详细)
Wireshark 是一款免费开源的网络协议分析工具,可实时捕获、解析并可视化数据包,助你诊断网络故障、分析通信协议(如HTTP、DNS、TCP等)。支持Windows/macOS/Linux,含中文界面,新手入门便捷。(239字)