地产网站建设网镇江网
2026/2/27 18:39:04 网站建设 项目流程
地产网站建设网,镇江网,谷歌广告优化师,移动互联网时代的信息安全与防护超星网课答案兄弟们#xff0c;大家做电商或者秒杀系统时#xff0c;第一反应防什么#xff1f;肯定是**“超卖”**对吧#xff1f; 毕竟#xff0c;库存只有 100 个#xff0c;结果卖出去 101 个#xff0c;不仅要赔钱#xff0c;搞不好还要被老板请去喝茶。于是我们搬出了 Redis大家做电商或者秒杀系统时第一反应防什么肯定是**“超卖”**对吧毕竟库存只有 100 个结果卖出去 101 个不仅要赔钱搞不好还要被老板请去喝茶。于是我们搬出了 Redis祭出了 Lua 脚本觉得稳如老狗。但你有没有想过还有一种情况比超卖更让老板心痛那就是——“少卖”。少卖库存明明显示扣掉了Redis 里也没货了但数据库里订单压根没生成货烂在仓库里卖不出去原本能赚的钱飞了。今天我们就来扒一扒这个“少卖”是怎么发生的顺便聊聊在订单支付环节如何用状态机乐观锁把并发问题治得服服帖帖。库存到底什么时候扣在写代码之前产品经理通常会跑过来问你一个哲学问题“咱们是下单减库存还是支付减库存”这不仅仅是技术实现的问题更是业务体验的选择。我们先来看看这两种流派的爱恨情仇1. 支付减库存Pay-to-Deduct逻辑用户下单随便下库存不改。只有当用户真正付完钱那一刻才去扣库存。优点绝对不会产生“恶性占库存”的情况卖出去的都是真金白银。缺点用户体验极差。想象一下你双11抢到了手机开开心心去付款结果银行卡扣款时告诉你“没货了退款吧”。用户绝对会炸毛。而且在并发高时这会导致严重的超卖风险因为大家都能下单。2. 下单减库存Order-to-Deduct逻辑用户只要下单成功库存就锁住。优点用户体验好只要下单成功就一定能买到除非他不付钱。缺点容易被“恶作剧”或者“竞对”恶意刷单把库存占满但不付款导致真正想买的人买不到。最终方案Redis 预扣 数据库实扣在高并发/秒杀场景下我们通常采用折中方案下单阶段Redis 预扣为了抗住流量我们在 Redis 里进行库存扣减也就是上面的“下单减库存”逻辑。只要 Redis 扣成功就告诉用户“抢到了”。支付阶段DB 实扣用户支付成功后我们再异步或者同步地去扣减数据库里的真实库存。这就引出了我们接下来的核心技术点——如何在 Redis 里安全地扣库存第一回合Redis 挡在最前面防超卖在秒杀场景下直接怼数据库肯定是找死。通常我们会在 Redis 里做缓存扣减。为了保证“查库存”和“扣库存”这两个动作中间不被别人插队我们通常会用 Lua 脚本。这就好比你去买奶茶店员看库存、收钱、给号这一套动作必须是一口气做完的中间不能接电话。Redis Lua 扣减脚本-- KEYS[1]: 商品库存Key-- ARGV[1]: 要购买的数量localstocktonumber(redis.call(get,KEYS[1]))localamounttonumber(ARGV[1])ifstockandstockamountthen-- 库存充足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 分钟的最后一秒点击了支付。线程 A支付回调收到银行通知用户钱付了要把订单改成PAID。线程 B定时任务巡逻发现这单 30 分钟没付钱要把它改成CLOSED关单并释放库存。如果这两个线程同时执行会发生什么如果不加控制可能出现用户钱付了订单却被关闭了。解决方案状态机 乐观锁我们不能让订单状态随意跳转必须按规矩办事。1. 状态机设计立规矩我们要定义好状态流转的方向不能逆行。下单成功支付成功 (允许)超时未付 (允许)支付回调 (❌ 禁止)定时关单 (❌ 禁止)PENDINGPAIDCLOSED2. 乐观锁实现加版本号我们在更新数据库时利用 SQL 的原子性做一个 CASCompare And Swap操作。不要只是简单的update而是要带上前置条件。Java 伪代码感受一下// 支付成功的处理逻辑publicbooleanpaySuccess(longorderId){// 只有当前状态是 PENDING 的时候才允许改成 PAID// 这里的 where status PENDING 就是乐观锁的精髓introwsorderMapper.updateStatus(orderId,PAID,// 目标状态PENDING// 期望的前置状态);if(rows1){returntrue;// 支付状态更新成功}else{// 更新失败说明订单可能已经被定时任务抢先关闭了// 这时候应该发起退款逻辑而不是强行改状态returnfalse;}}同理定时关单的逻辑也是一样-- 只有在订单还是 PENDING 状态时才允许改成 CLOSEDUPDATEordersSETstatusCLOSEDWHEREid10086ANDstatusPENDING;谁先抢到谁赢如果是支付先到状态变为PAID。稍后定时任务执行发现where status PENDING不满足更新 0 行关单失败。符合预期用户支付成功如果是关单先到状态变为CLOSED。稍后支付回调执行发现where status PENDING不满足更新 0 行。系统检测到更新失败发起自动退款。符合预期避免了单子关了钱没退的尴尬总结做一个靠谱的交易系统真的全是细节防超卖Redis Lua 脚本原子扣减。防少卖警惕 Redis 与 DB 的数据不一致利用对账或回补机制。防状态错乱状态机定义流转方向乐观锁CAS解决并发冲突。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询