设计秒杀系统:存储与缓存方案怎么落地?

结论先行:秒杀的矛盾是海量请求打极窄的库存资源,方案核心是分层挡流 + 原子扣减 + 异步落库:静态流量打到 CDN,资格校验与限流放网关,库存预扣放 Redis(Lua 原子),最终扣减回数据库事务,订单与通知走 MQ 削峰异步化。防超卖靠“同一条库存记录的原子写”,而不是靠应用锁堆叠。

一、流量分层

承担什么关键动作
CDN/页面缓存静态页与活动页动静分离、秒杀页不做动态渲染
网关/入口防刷、限流、风控验证码、令牌桶、黑名单
应用层资格校验、Redis 预扣只放行拿到资格的请求
MQ削峰、解耦下单请求排队异步处理
DB最终扣减与落单事务 + 行锁,防超卖收口

二、库存扣减:防超卖的关键

-- 数据库原子扣减(同一行串行化,天然防超卖)
UPDATE stock SET remain = remain - 1 WHERE sku_id = ? AND remain > 0;
-- 影响行数 = 0 说明库存不足
-- Redis 预扣:Lua 保证“检查 + 扣减”原子
local r = tonumber(redis.call('GET', KEYS[1]))
if r and r > 0 then
    redis.call('DECR', KEYS[1])
    return 1   -- 抢到资格
end
return 0       -- 已售罄
  • 预热:活动开始前把库存写入 Redis,DB 库存是最终对账的底账。
  • 兜底对账:Redis 扣减流水与 DB 库存定期对账,差异靠补偿单修正。

三、缓存与存储的配合

  • 商品详情/价格缓存:Redis 或本地缓存,秒杀期命中率极高;防穿透加布隆过滤器/空值缓存。
  • 用户已购状态:Redis SETNX 防重复下单,幂等键落库。
  • 下单异步化:抢到资格即返回排队中,订单创建、扣库存、发券全部走 MQ 消费。

四、设计红线

  • 超卖红线:任何路径都不能绕过 remain > 0 的原子更新。
  • 少卖红线:MQ 消费失败要有重试与死信,DB 扣减失败要回补 Redis 库存。
  • 体验红线:请求打满前先被限流/排队挡住,而不是把数据库打挂后再 502。

常见追问与记忆点

  • 追问:能不能不加 Redis 直接全走 DB?能,扛得住就行;秒杀本质是省掉昂贵的 DB 写与行锁争用。
  • 追问:减库存放在下单前还是支付后?下单前预扣防“下单成功却无货”,支付成功才算终态,超时未支付回补库存。
  • 记忆点:CDN 挡静态、网关挡流量、Redis 原子预扣、MQ 削峰、DB 行锁收口;防超卖靠原子更新,对账保最终一致。
笔记加载中…