Skip to content

P05 秒杀系统如何设计 ​

面试题:让你设计一个秒杀系统(比如 1 万件商品、瞬间百万请求),你怎么设计?

总体思路:流量分层拦截 + 库存前置扣减 + 异步削峰 ​

核心原则:把大部分请求挡在数据库之外,只有真正能买到的人才会走到落库这一步。

经典五层架构 ​

1. 前端 / 接入层 ​

  • 秒杀页静态化,放到 CDN,动态接口不承担页面渲染压力;
  • 按钮置灰、倒计时、验证码/滑块,过滤机器请求;
  • 秒杀开始前不提前暴露接口地址。

2. 网关 / 接口层限流 ​

  • 按用户、IP、设备做限流(令牌桶/滑动窗口);
  • 接口鉴权 + 风控(新账号、异常频次直接拒绝);
  • 同一用户多次点击只放行一次。

3. Redis 层:库存预扣 + 原子操作 ​

用 Redis 缓存库存,扣减用 Lua 脚本保证原子性:

lua
-- 判断库存 > 0 再扣减,返回 1 成功 0 失败
if tonumber(redis.call('get', KEYS[1])) > 0 then
    return redis.call('decr', KEYS[1])
end
return -1
  • 预扣成功才允许下单;库存不足直接返回"已售罄";
  • 商品热点数据放 Redis 集群,避免单点;
  • 秒杀标记(用户是否已买过)也用 Redis 判断,防止重复秒杀。

4. MQ 削峰异步下单 ​

通过资格校验的请求写入消息队列,由消费端异步创建订单、扣库存、发通知:

text
请求 → 限流 → Redis 预扣库存 → MQ → 订单服务异步落库 → 用户轮询/推送结果

好处:数据库不再直接承受瞬间峰值,消费端按自身能力慢慢消化。

5. 数据库最终一致性 ​

  • 订单落库 + 库存最终扣减,保证不超卖(唯一索引/乐观锁兜底);
  • 下单超时未支付要释放 Redis 预扣库存(用延迟消息/定时任务);
  • 失败要补偿:MQ 消费失败重试、库存回补。

加分点 ​

  • 防超卖:数据库层用 UPDATE t_sku SET stock = stock - 1 WHERE id = ? AND stock > 0(乐观条件更新)+ 唯一索引兜底;
  • 防重复:用户+活动维度唯一索引,或 Redis setnx 幂等键;
  • 隔离:秒杀库独立,避免影响主站;商品、订单分库分表;
  • 兜底:限流降级(排队页)、活动结束后关闭入口;
  • 监控:库存、订单积压量、MQ 堆积数实时看板。

一句话总结 ​

静态化+限流挡住流量 → Redis 原子预扣库存 → MQ 异步下单削峰 → 数据库乐观锁兜底防超卖,层层削峰,保证核心链路不被冲垮。

基于 VitePress 重建