Lesson 0012 · 系统设计 · Phase 3 权衡 · 综合案例

大案例 II:秒杀系统

⏱ 预计 40 分钟 🎯 极端场景下的全武器库演习 📖 前置:L5 · L7 · L10
与你的 Mission 的关系

秒杀是「正常架构的全部假设都被打破」的场景:流量 100 倍、库存是热点中的热点、错一批就是真金白银。它把 L5 的缓存、L7 的队列、L10 的三件套全部逼到极限,是面试区分「会用组件」和「会做权衡」的试金石。工程参照:美团技术团队的秒杀实践[1]

§1 · Step 1澄清需求

类别关键约束(要主动确认!)
业务1 万件商品、预计 100 万人抢、开抢时间固定 20:00。铁律:绝不允许超卖;可以少卖(库存回补机制兜底)
公平性要不要绝对先到先得?(机器人面前没有公平——风控是不是需求?)
体验没抢到给明确结果(秒杀的差评大头是「一直转圈」);下单后 15 分钟不支付 → 回补库存

§2 · Step 2估算:极端在哪里

结论:常规「请求 → DB 扣库存」在数学上就不成立,必须让绝大多数请求到不了 DB。

§3 · Step 3高层设计:漏斗逐层放行

100 万用户
   │  ① 静态化 + CDN:商品页全静态,详情/倒计时都别回源(L3)
   ▼
   │  ② 按钮后置 + 前端限流:开抢前按钮禁用,点击后 5 秒内不可重复
   ▼
   │  ③ 网关限流 + 风控:全局限流 5 万 QPS(L10),机器人/黑名单直接拒
   ▼
   │  ④ Redis 预扣库存:Lua 原子「查+扣」,扣不到直接返回「已抢完」
   ▼   ←—— 90%+ 的请求死在这里,DB 无感
   │  ⑤ 消息队列:扣减成功 → 发「抢购成功」事件(L7),立刻返回「排队中」
   ▼
   │  ⑥ 订单服务消费:落库创建订单(DB 行级扣减此时只承担 1 万笔)
   ▼
   └── ⑦ 15 分钟未支付 → 超时回补库存(延迟消息)
本设计的灵魂一句话

把「抢」挡在 Redis,把「卖」放进 MQ,DB 只负责最终成交的那 1 万笔。漏斗每层的「拒绝率」就是每层的价值。

§4 · Step 4深入与权衡

深挖点讨论
为什么不超卖? Redis 预扣用 Lua 脚本把「读库存-1-写回」做成原子操作(单线程模型天然串行),扣到负数直接拒绝。应用层先读后写在并发下必然超卖(check-then-act 竞态)
Redis 扣了、MQ 丢了怎么办? 少卖了(库存没释放)。接受 or 修补:本地消息表/事务性 outbox 先落库再发事件(L7 幂等延伸)。权衡:秒杀场景「少卖可容忍、超卖不可容忍」——不对称代价决定不对称设计
用户视角的一致性 预扣成功 ≠ 订单已创建。前端轮询「抢购结果」接口(读订单创建状态),明确告知「排队中/成功/失败」——不确定状态要给出口,别让用户无止境等待(L9:此处接受最终一致)
幂等 用户连点/前端重试 = 同一用户重复请求 → 请求带 userId+活动 ID 幂等键,Redis SETNX 拦截重复预扣(L7
降级预案 Redis 挂 → 秒杀直接下线(开抢前必备演练)+ 页面静态兜底「活动太火爆」;订单库压力超限 → MQ 天然缓冲 + 消费端限速(L10 三件套总动员)

§5 · 随堂检测

§6 · 检索练习(本课最重要 25 分钟)

盖住全部内容,白板推演:设计「10 万件、500 万人抢」的秒杀系统。要求:画出漏斗并标注每层拒绝率、说明预扣为什么不超卖、说明「Redis 扣了但订单没落库」怎么办。

对照清单(自查 7 项)

① 页面静态化/CDN ② 按钮后置+前端限流 ③ 网关限流+风控 ④ Redis Lua 原子预扣(解释了为什么应用层读写会超卖)⑤ MQ 异步落单+用户轮询结果 ⑥ 幂等键防重复 ⑦ 超时回补+降级预案。漏哪条回哪课:①→L3、③→L10、④→L5、⑤⑥→L7

§7 · 本周行动

行动任务(约 30 分钟)

参加一次真实的秒杀/抢购(12306 候补、演唱会票、限量球鞋都行),全程记录:从点击到结果的每一步(按钮状态、排队提示、失败文案)。用本课的漏斗图反推它的架构,写一份 3 句话的侦探笔记——真实系统的设计决策藏在每一次等待里。

§8 · 延伸资源

💬 你的白板推演可以贴给我逐项点评。特别欢迎贴你「侦探笔记」——猜错了也没关系,猜的过程就是在练架构嗅觉。