🧩 典型题干
场景:一个商品库存 100,活动开始瞬间 100 万 QPS。
要求:不超卖、尽量不少卖、系统不能被打挂,支持风控与限购。
💡 标准拆解:入口限流与拦截(挡住无效流量)→ 异步化下单(削峰)→ 库存模型(不超卖)→ 订单最终一致(可追溯)→ 风控与监控。
📚 学习正文:秒杀系统如何扛住百万 QPS 且不超卖?
交互图:秒杀关键链路闭环(点步骤看“为什么要这么做”)
面试时建议按“挡 → 削 → 扣 → 落 → 补 → 观”的顺序讲:先挡住无效流量,再用队列削峰,再用库存模型保证不超卖,最后用补偿/对账兜底一致性。
1) 入口拦截:把无效流量挡在系统外
- 静态化:活动页/商品信息尽量走 CDN,减少回源。
- 限流:网关按 IP/用户/接口限流,优先保护 DB/Redis/MQ。
- 防刷:验证码/滑块/设备指纹/动态路径;把黑产请求变“昂贵”。
2) 排队削峰:高峰期别做重逻辑
- 快速返回:允许先返回“排队中”,用查询接口返回最终状态。
- 队列吸收:MQ/队列把峰值请求摊平到系统可处理的吞吐。
- 降级策略:队列过长直接拒绝/降级,避免“排队雪崩”。
3) 库存扣减:不超卖的核心动作
- Redis 预扣:用原子 DECR 或 Lua 保证不超卖。
- 令牌化:提前生成 N 个库存令牌,拿到令牌才可下单。
- DB 兜底:最终落库仍需校验库存与唯一约束,防止边界异常。
4) 异步落单:靠幂等与唯一约束抵抗重试
- 可靠消息:Outbox/事务消息,保证“扣减成功 → 下单事件”不丢。
- 消费幂等:订单唯一键(用户+活动)或去重表,重试不重复下单。
- 状态机:订单状态可追溯(排队/成功/失败/超时)。
5) 补偿与对账:让最终一致“可自愈”
- 回补库存:落单失败/超时未支付等场景回补预扣。
- 对账任务:定期扫描“预扣成功但无订单/订单失败”的异常记录。
- 幂等补偿:补偿操作同样要幂等,避免二次回补。
6) 观测与风控:把系统做成“可运营、可防守”
- 关键指标:Redis 扣减成功率、队列堆积、落单成功率、P95/P99、库存一致性。
- 限购:用户维度限购与黑名单(避免一人多单)。
- 演练:压测 + 故障演练 + 灰度回滚,确保高峰期稳定。
系统架构图(面试时用它讲全链路)
用户/APP/小程序
|
v
CDN/静态化(活动页/商品信息)
|
v
网关/API Gateway(限流/黑白名单/验证码/动态路径)
|
v
秒杀接入层(参数校验/限购校验/用户幂等 Key)
|
+--> Redis(库存预扣:Lua/DECR 原子扣减;预扣记录/排队状态)
|
+--> MQ(下单事件:削峰/异步化;支持重试/顺序/死信)
|
v
下单服务(消费幂等:唯一约束/去重表;状态机)
|
+--> 订单库 MySQL(订单/预扣/支付状态)
|
+--> 支付/风控(可选)
|
+--> 失败补偿(回补库存/回滚预扣/对账修复)
观测与治理:监控告警(QPS/RT/错误率/队列堆积/库存一致性) + 压测演练 + 灰度回滚
1) 需求与量级澄清(面试先问)
- 库存模型:是否允许少卖?是否允许超卖?是否要严格不超卖?
- 下单链路:是否必须同步返回“下单成功”?能否先返回“排队中”?
- 限购与风控:每人限购几件?是否需要验证码、设备指纹、黑名单?
- 量级与 SLA:峰值 QPS、可接受 RT、失败率与降级策略。
2) 整体架构:分层拦截 + 异步化 + 最终一致
入口层:CDN/静态化 + 网关限流 + 防刷
核心层:Redis 预扣库存 + MQ 异步下单 + DB 落单
一致性:幂等/去重 + 失败补偿 + 对账回补
稳定性:降级/熔断/监控 + 灰度回滚
3) 数据模型:库存、订单、预扣记录
- 活动库存表:activity_stock(活动ID、总库存、剩余库存、版本号)。
- 预扣记录:stock_reserve(用户、活动、数量、状态、过期时间)。
- 订单表:order(订单ID、用户、活动、数量、状态)。
4) 关键链路:从请求到下单的完整闭环
1) 校验前置(时间/黑名单/限购)
2) Redis 预扣库存(原子 DECR)
3) 发送 MQ(可靠事件)
4) 异步落单(幂等/唯一约束)
5) 失败补偿(回补库存/对账)
5) 可靠性与一致性保障
- 请求幂等:用户/活动/商品维度幂等键,避免重复下单。
- 可靠事件:Outbox/事务消息,保证“预扣成功 → 订单创建事件”不丢。
- 消费幂等:订单表唯一约束或去重表;重试不会重复扣款。
- 最终一致:对账任务周期性扫描异常并补偿。
6) 稳定性治理
限流降级:网关限流、排队、降级返回
熔断:Redis/DB/MQ 异常时快速失败
监控:库存、QPS、RT、错误率、队列堆积
演练:压测、故障演练、灰度发布