秒杀系统设计
10 万请求抢 100 件库存,先削峰,再防超卖,最后异步落库。
面试官问题
如果让你设计一个秒杀系统,10 万 QPS 同时涌入但库存只有 100 件,你怎么保证不超卖、数据库不被打挂、失败还能回滚?
考分层削峰、热点库存原子扣减、异步下单、最终一致、失败回滚与补偿对账。
把秒杀拆成过滤、预扣、排队、落库、补偿五段。入口只放行有资格请求,Redis 原子预扣库存,MQ 把洪峰削平,DB 用 CAS 做最终扣减,失败路径回滚 Redis 并对账。
业务背景
秒杀活动流量集中、库存极少,系统要快速拒绝大部分请求,只让有资格且预扣成功的请求进入下单链路。
- 秒杀为什么要分层削峰?
- Redis Lua 如何防超卖和重复购买?
- 预扣成功但落库失败如何补偿?
关键约束
- 卖出数量不能超过库存。
- 数据库不能承受入口洪峰。
- 失败路径要能回滚预扣库存。
- 用户要尽快知道抢购状态。
系统建模
- 1
资格网关:活动窗口、预约、风控和限流。
- 2
Redis 库存池:Lua 原子预扣库存和限购。
- 3
MQ 队列:把瞬时洪峰削成可消费的稳定流量。
- 4
DB 真相源:最终扣减库存并创建订单。
- 5
补偿任务:修复 Redis、MQ、DB 之间的不一致。
动画推演
看请求粒子流如何被资格网关、风控和 Redis 库存池逐层削减;只有预扣成功的少量请求进入 MQ,消费者再平稳写 DB。
- 01 流量洪峰进入网关。
- 02 活动窗口 / 预约资格 / 风控过滤。
- 03 Redis 原子预扣库存。
- 04 MQ 异步削峰。
- 05 DB CAS 最终扣减。
- 06 失败回滚库存。
- 07 补偿任务修复不一致。
逐层观察流量从网关、Redis、MQ 到 DB 被削峰和校正的过程。
待命
资格校验通过
最终真相源
故障注入 / 错误做法
请求进来直接查数据库库存,库存大于 0 就扣减并创建订单。并发高就把数据库机器加多一点。
把秒杀拆成过滤、预扣、排队、落库、补偿五段。入口只放行有资格请求,Redis 原子预扣库存,MQ 把洪峰削平,DB 用 CAS 做最终扣减,失败路径回滚 Redis 并对账。
错误做法是所有请求同步扣 DB。动画会注入入口洪峰,展示数据库锁等待、连接池耗尽,以及 Redis 预扣 + MQ 后指标如何恢复。
- Redis 扣了库存但 MQ 或 DB 下单失败。
- 同一用户重复请求绕过限购。
- 热点商品单 key 被打爆。
- MQ 堆积导致用户长时间只看到排队中。
30 秒面试表达
- 01
秒杀不是让所有请求打数据库,而是分层削峰。
- 02
入口先做活动窗口、预约资格、风控和限流,把无效流量挡掉。
- 03
真正防超卖放在 Redis,用 Lua 原子判断库存和用户限购,预扣成功才发 MQ。
- 04
消费者异步下单,DB 用 update stock=stock-1 where stock>0 兜最终一致;失败就回滚 Redis 库存,并用补偿任务修复 Redis 和 DB 的差异。
常见追问
Redis 预扣成功但下单失败怎么办?▶
失败路径要补回 Redis 库存,并记录补偿流水;对账任务周期性比对 Redis、订单和 DB 库存,发现漂移再修正。
如何防止一个人抢多件?▶
资格校验和 Redis Lua 脚本里都带用户维度去重,常见是 userId+activityId 限购键,重复请求直接拒绝。
MQ 堆积后用户体验怎么办?▶
前端展示排队中并轮询结果;后台扩消费者、批量消费或限速回放,必要时临时降级新请求保护交易链路。
复盘卡片
- 入口削峰负责保护系统,不负责最终一致。
- Redis 预扣负责快速判库存,DB CAS 负责最终账。
- 所有失败路径都要有库存回滚和对账补偿。