如何设计一个秒杀功能?
- 标签:后端、系统设计、场景题
🏛 业务架构师
秒杀的业务本质先看清:海量读(几十万人刷页面)+ 极小写(100 件库存)+ 强防超卖。设计主线就是分层削流,让 100 件的写入量最终落到数据库:
1. 流量入口层(砍掉 99%)
- 页面静态化上 CDN,商品信息不回源;
- 答题/验证码错峰(打散同一秒的洪峰)+ 防机器人;
- 网关限流(令牌桶按预估容量放行,超出直接返回"已抢完")。
2. 预检查层(Redis + Lua 原子扣减)
- 库存预热到 Redis,Lua 脚本原子完成"查库存-判断-扣减",超卖在此被杜绝;
- 一人一单:Set 记录已购用户 ID,Lua 内同时判重;
- 扣减成功才允许进入下单,失败立即返回——保护下游。
3. 异步下单层
- Redis 扣减成功 → 发 MQ → 订单服务匀速消费落库、扣真实库存——DB 只承受等于库存量的写入,洪峰被队列抹平;
- 前端轮询/推送获知结果,用户体验上"排队中"。
4. 兜底与对账
- 数据库最终防线:库存行乐观锁(
update ... set stock=stock-1 where id=? and stock>0)——即使前面全失守也不超卖; - 订单超时未支付回补库存(延迟消息);
- 全链路对账:Redis 扣减量 vs DB 订单量,防各类缝隙;
- 降级预案:Redis 挂了熔断秒杀(宣布活动延迟),宁可活动失败不可资损。
架构心法:每一层的职责都是"把下一层的流量降一个数量级",逐层衰减到数据库能从容处理的量级——秒杀考的不是某个技术点,是流量漏斗的设计纪律。
🏗 系统架构师
秒杀是"瞬时超大流量 + 有限库存"的经典架构题,我按流量漏斗逐层衰减的思路设计(核心:让每一层把流量削掉一个量级,DB 只承受千级 TPS):
1. 前端/接入层(削 90%+):
- 页面静态化 + CDN;按钮置灰防连点;答题/验证码打散瞬时峰值;
- 网关限流(令牌桶按峰值 2 倍放行)、风控黑名单拦截机器流量。
2. 缓存层(扛住读 + 库存预判):
- 商品详情、库存预热进 Redis,读走本地缓存 + Redis 二级;
- 库存扣减在 Redis 原子完成(Lua 脚本:判断 + DECR 一体,防超卖),扣到 0 后设"售罄"标记,后续请求直接拒绝——90% 的失败请求到此为止。
3. 异步下单(削峰写):
- Redis 扣减成功 → 发 MQ → 订单服务按 DB 承受力消费建单(5000 TPS 级);
- 前端轮询/推送订单结果,用户侧体验"排队中"。
4. 数据层兜底:
- DB 层再防一道超卖:
update stock set n = n-1 where id=? and n > 0,影响行数为 0 即失败; - 订单唯一约束防重复下单(幂等)。
关键细节:库存分桶(100 库存拆 10 个子桶降低单 key 热点);热点 key 本地缓存;服务无状态水平扩容 + 独立秒杀集群(故障隔离,不拖垮主站);全链路压测定容量。
一句话总结:秒杀架构 = 限流 + 缓存 + 异步 + 兜底,让流量在到达 DB 前衰减三个数量级。
💻 全栈开发专家
我按"流量漏斗"从外到内设计,核心思想:每一层过滤掉一部分流量,让真正到 DB 的请求只剩有效订单。
text
100万请求
├─ 前端/CDN:按钮置灰、答题验证码(削峰+防脚本)
├─ 网关:限流(令牌桶,放行 10%)、黑名单、答题校验
├─ Redis 预扣库存(Lua 原子) ← 挡掉 90% 无效流量
├─ MQ 异步下单(削峰)
└─ DB:唯一索引防重 + 乐观扣减兜底关键代码:Redis 预扣库存(原子,防超卖核心):
lua
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock >= 1 then
redis.call('DECR', KEYS[1])
return 1 -- 预扣成功
end
return 0 -- 已售罄java
if (lua预扣成功 == 0) return "已抢完"; // 秒杀结束,后面全挡
sendMq(new CreateOrderMsg(userId, skuId)); // 异步落单DB 层兜底:
sql
-- 库存行乐观扣减(防止 Redis 与 DB 不一致时超卖)
UPDATE sku SET stock = stock - 1 WHERE id=? AND stock >= 1;
-- 防重复下单
ALTER TABLE orders ADD UNIQUE KEY uk_uid_sku_round (user_id, sku_id, round);必答的配套细节:库存与 Redis 用 binlog/对账同步;热点商品库存分桶(100 库存拆 10 个 key)避免单 key 打满单节点;未支付回补库存(延迟消息);降级预案:Redis 挂了宁可秒杀失败也不能穿透到 DB。