news 2026/7/4 21:49:29

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
防住了超卖,却输给了“少卖”?



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

毕竟,库存只有 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)解决并发冲突。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 21:26:07

Docker中TensorFlow-GPU部署与CUDA问题解决

Docker中TensorFlow-GPU部署与CUDA问题解决 在企业级AI系统开发中,一个看似简单的任务——让TensorFlow在Docker容器里用上GPU——常常成为项目推进的“拦路虎”。你可能已经写好了模型代码、配置好了服务接口,结果一运行却发现GPU压根没被识别。日志里…

作者头像 李华
网站建设 2026/7/2 2:46:55

NPM安装Socket.IO实现实时推送TensorRT状态

NPM安装Socket.IO实现实时推送TensorRT状态 在AI推理系统日益复杂、部署场景愈发多样的今天,一个常见的工程难题浮出水面:我们如何让“黑盒”般的高性能推理过程变得透明可感? 设想这样一个场景——你在边缘设备上运行着基于TensorRT优化的Re…

作者头像 李华
网站建设 2026/6/29 6:08:06

Linly-Talker:支持图片上传的AI数字人对话系统

Linly-Talker:支持图片上传的AI数字人对话系统(一站式全栈解决方案,支持实时语音交互与任意图像驱动) 欢迎访问项目仓库并点亮 ⭐ 支持我们:GitHub - Linly-Talker B站持续更新演示视频,直观展示效果 → …

作者头像 李华
网站建设 2026/7/4 19:29:35

Qwen3-VL-8B实现近实时视频流分析的实践探索

Qwen3-VL-8B实现近实时视频流分析的实践探索 在智能摄像头铺满城市的今天,我们早已不满足于“看得见”——真正想要的是“看得懂”。可部署一个能理解视频语义的大模型动辄需要数张A100,推理延迟高得让人怀疑人生。有没有一种方式,既能用上强…

作者头像 李华