当前位置: 首页 > news >正文

防住了超卖,却输给了“少卖”?



兄弟们,大家做电商或者秒杀系统时,第一反应防什么?肯定是**“超卖”**对吧?

毕竟,库存只有 100 个,结果卖出去 101 个,不仅要赔钱,搞不好还要被老板请去喝茶。于是我们搬出了 Redis,祭出了 Lua 脚本,觉得稳如老狗。

但你有没有想过,还有一种情况比超卖更让老板心痛?那就是——“少卖”

少卖:库存明明显示扣掉了,Redis 里也没货了,但数据库里订单压根没生成!货烂在仓库里卖不出去,原本能赚的钱飞了。

今天我们就来扒一扒这个“少卖”是怎么发生的,顺便聊聊在订单支付环节,如何用状态机+乐观锁把并发问题治得服服帖帖。


库存到底什么时候扣?

在写代码之前,产品经理通常会跑过来问你一个哲学问题:“咱们是下单减库存,还是支付减库存?”

这不仅仅是技术实现的问题,更是业务体验的选择。我们先来看看这两种流派的爱恨情仇:

1. 支付减库存(Pay-to-Deduct)

  • 逻辑:用户下单随便下,库存不改。只有当用户真正付完钱那一刻,才去扣库存。
  • 优点:绝对不会产生“恶性占库存”的情况,卖出去的都是真金白银。
  • 缺点用户体验极差。想象一下,你双11抢到了手机,开开心心去付款,结果银行卡扣款时告诉你“没货了,退款吧”。用户绝对会炸毛。而且在并发高时,这会导致严重的超卖风险(因为大家都能下单)。

2. 下单减库存(Order-to-Deduct)

  • 逻辑:用户只要下单成功,库存就锁住。
  • 优点:用户体验好,只要下单成功,就一定能买到(除非他不付钱)。
  • 缺点:容易被“恶作剧”或者“竞对”恶意刷单,把库存占满但不付款,导致真正想买的人买不到。

最终方案:Redis 预扣 + 数据库实扣

在高并发/秒杀场景下,我们通常采用折中方案

  1. 下单阶段(Redis 预扣):为了抗住流量,我们在 Redis 里进行库存扣减(也就是上面的“下单减库存”逻辑)。只要 Redis 扣成功,就告诉用户“抢到了”。
  2. 支付阶段(DB 实扣):用户支付成功后,我们再异步或者同步地去扣减数据库里的真实库存。

这就引出了我们接下来的核心技术点——如何在 Redis 里安全地扣库存?


第一回合:Redis 挡在最前面(防超卖)

在秒杀场景下,直接怼数据库肯定是找死。通常我们会在 Redis 里做缓存扣减。

为了保证“查库存”和“扣库存”这两个动作中间不被别人插队,我们通常会用 Lua 脚本。这就好比你去买奶茶,店员看库存、收钱、给号这一套动作必须是一口气做完的,中间不能接电话。

Redis Lua 扣减脚本

-- KEYS[1]: 商品库存Key-- ARGV[1]: 要购买的数量localstock=tonumber(redis.call('get',KEYS[1]))localamount=tonumber(ARGV[1])ifstockandstock>=amountthen-- 库存充足redis.call('decrby',KEYS[1],amount)return1-- 成功elsereturn0-- 库存不足end

利用 Redis 单线程执行 Lua 脚本的特性,我们完美解决了原子性问题,超卖?不存在的。


第二回合:隐秘的角落——“少卖”是怎么来的?

上面那步做完,Redis 库存是扣了,接下来我们要把订单落库。为了不把数据库打挂,我们通常是异步的。

问题就出在这个异步链路里。

正常流程 vs 少卖流程

用户Redis缓存消息队列数据库正常流程1. 扣减库存 (成功)2. 发送创建订单消息3. 消费消息写入订单下单成功"少卖"事故现场1. 扣减库存 (成功 -1)此时 Redis 库存已减少2. 发送消息失败 (网络抖动/服务挂了)消息丢失!3. 没收到消息,不写入数据库没订单,但库存被扣了!用户Redis缓存消息队列数据库

结果就是:Redis 里的库存已经少了(被你扣了),但数据库里并没有生成订单。

这就像是你去买票,售票员把票撕下来给你留着(Redis库存-1),结果你付钱的时候断网了,人走了。这张票就被“锁死”在售票员手里,别人买不到,你也买不走。这就是“少卖”。

怎么解决?
除了保证 MQ 的可靠性投递(本地消息表、ACK机制),最稳妥的办法是引入**“库存回补”机制或者“对账”**。如果一定时间内订单没创建成功,要把 Redis 里的库存加回去。


第三回合:支付与关单的“生死时速”(状态机+乐观锁)

好,假设现在库存没问题,订单也生成了,状态是PENDING(待支付)。

这时候,真正的并发大坑来了。

场景模拟:
用户小明在订单快超时(比如 30 分钟)的最后一秒,点击了支付。

  1. 线程 A(支付回调):收到银行通知,用户钱付了,要把订单改成PAID
  2. 线程 B(定时任务):巡逻发现这单 30 分钟没付钱,要把它改成CLOSED(关单)并释放库存。

如果这两个线程同时执行,会发生什么?如果不加控制,可能出现:用户钱付了,订单却被关闭了。

解决方案:状态机 + 乐观锁

我们不能让订单状态随意跳转,必须按规矩办事。

1. 状态机设计(立规矩)

我们要定义好状态流转的方向,不能逆行。

下单成功
支付成功 (允许)
超时未付 (允许)
支付回调 (❌ 禁止!)
定时关单 (❌ 禁止!)
PENDING
PAID
CLOSED
2. 乐观锁实现(加版本号)

我们在更新数据库时,利用 SQL 的原子性做一个 CAS(Compare And Swap)操作。不要只是简单的update,而是要带上前置条件

Java 伪代码感受一下:

// 支付成功的处理逻辑publicbooleanpaySuccess(longorderId){// 只有当前状态是 PENDING 的时候,才允许改成 PAID// 这里的 where status = 'PENDING' 就是乐观锁的精髓introws=orderMapper.updateStatus(orderId,"PAID",// 目标状态"PENDING"// 期望的前置状态);if(rows==1){returntrue;// 支付状态更新成功}else{// 更新失败,说明订单可能已经被定时任务抢先关闭了!// 这时候应该发起退款逻辑,而不是强行改状态returnfalse;}}

同理,定时关单的逻辑也是一样:

-- 只有在订单还是 PENDING 状态时,才允许改成 CLOSEDUPDATEordersSETstatus='CLOSED'WHEREid=10086ANDstatus='PENDING';

谁先抢到谁赢:

  • 如果是支付先到:状态变为PAID。稍后定时任务执行,发现where status = 'PENDING'不满足,更新 0 行,关单失败。(符合预期,用户支付成功)
  • 如果是关单先到:状态变为CLOSED。稍后支付回调执行,发现where status = 'PENDING'不满足,更新 0 行。系统检测到更新失败,发起自动退款。(符合预期,避免了单子关了钱没退的尴尬)

总结

做一个靠谱的交易系统,真的全是细节:

  1. 防超卖:Redis Lua 脚本原子扣减。
  2. 防少卖:警惕 Redis 与 DB 的数据不一致,利用对账或回补机制。
  3. 防状态错乱:状态机定义流转方向,乐观锁(CAS)解决并发冲突。
http://www.jsqmd.com/news/99176/

相关文章:

  • YOLOv5血细胞检测实战:从训练到部署
  • PaddlePaddle动态图编程入门:git下载示例代码并导入conda环境
  • 大专营销人跨领域突围难?基础薄弱也能啃下 AI 硬骨头!CAIE 认证成逆袭密码
  • Dify开源LLM应用开发平台本地部署指南
  • FaceFusion:领先的人脸融合技术平台使用指南
  • 如何在ComfyUI中加载GPT-SoVITS节点进行语音生成?
  • Qwen3-VL-30B实现航空航天器高精度识别
  • anything-llm Docker本地部署指南
  • Qwen-Image微调实战:让模型学会新车图生成
  • 《60天AI学习计划启动 | Day 05: 项目实战 - 简单聊天机器人》
  • ACE-Step:开源高效音乐生成大模型解析
  • 35、【Ubuntu】【远程开发】内网穿透:连接可靠性(三) - 指南
  • 【每日算法】LeetCode 19. 删除链表的倒数第 N 个结点
  • 2025-2026 北京靠谱工程律所指南:权威评测与核心解决方案对比 - 苏木2025
  • 16、Linux系统下外设使用指南
  • 2025年度北京五大靠谱大平层设计品牌企业推荐:资质齐全的大 - 工业品牌热点
  • Claude Code最佳助手Serena入门指南
  • LobeChat能否评估项目风险?提前预警潜在问题
  • 当毕业论文从“熬出来的任务”变为“走出来的路径”:一位本科生如何在AI协研工具的陪伴下,完成一次有方法、有规范、有成长的学术初旅?
  • TensorRT-LLM离线环境搭建与Bloom模型量化推理
  • FLUX.1-dev本地部署与镜像下载全指南
  • 腾讯混元开源HunyuanVideo-Foley:实现声画合一的AI音效革命
  • Excalidraw应用案例与最佳实践解析
  • 《60天AI学习计划启动 | Day 04: 流式响应实现 - 打造流畅的AI对话体验》
  • 抢占AI流量入口:深度云海如何以全链路GEO方案赋能企业增长 - 深度智识库
  • EmotiVoice:开源多情感TTS重塑声音表达
  • 2025隐形车衣厂家TOP5权威推荐:甄选靠谱制造厂助力爱车 - 工业推荐榜
  • 期末复习分析+改错
  • LobeChat能否标记不确定性?避免过度自信输出
  • 22、Perl正则表达式与程序交互全解析