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 异步下单削峰 → 数据库乐观锁兜底防超卖,层层削峰,保证核心链路不被冲垮。