Files
zhuangang/scenarios/flash-sale.md
T

4.2 KiB

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
scenario.flash-sale 秒杀 / 高并发抢购:流量削峰、Redis 原子扣库存、异步下单、防超卖和防刷 scenario cross
redis
mq
postgresql
gin
nginx
vue
秒杀
高并发
库存
Lua
削峰
异步下单
幂等
限流
防刷
advanced
backend.redis.index
backend.mq.index
3
backend.redis.scenario.stock-reservation
backend.mq.scenario.delayed-order-cancel
backend.redis.lock-ratelimit
3
14
16

秒杀 / 高并发抢购:流量削峰、Redis 原子扣库存、异步下单、防超卖和防刷

经典的后端面试场景题。你的主线是 AI 应用,但“高并发怎么设计”几乎每个后端 / 全栈面试都会问。

业务背景与问题

电商平台做限量 1000 件的秒杀,预计瞬间几十万请求:

  1. 上次活动数据库被打垮,整个商城不可用;
  2. 出现超卖(卖出 1003 件);
  3. 同一用户用脚本抢到多件;
  4. 用户付款超时后库存没有释放。

约束

  • 活动时间固定,流量可预期。
  • 不能影响商城的其他功能。
  • 库存数据最终以数据库为准。

方案设计

  1. 前端和网关削峰:静态页面走 CDN;按钮到点才可点击、点击后置灰;验证码或答题打散请求;Nginx / 网关按 IP 和用户限流。
  2. 独立部署:秒杀服务独立,和主站隔离,挂了不影响主站。
  3. 预热:活动前把库存加载到 Redis。
  4. 原子扣减:Redis Lua 脚本一次完成“检查是否已购买 → 检查库存 → 扣减 → 记录用户”,避免超卖和重复购买。
  5. 异步下单:扣减成功后发消息,下单服务按数据库能承受的速率消费;数据库层 UPDATE stock SET n = n - 1 WHERE id = ? AND n > 0 再兜底一次,订单表用户 + 活动唯一约束保证幂等。
  6. 结果查询:前端轮询“排队中 / 成功 / 失败”。
  7. 超时释放:订单创建时投递延迟消息,到期未支付则取消订单、库存加回 Redis 和数据库。
  8. 防刷:登录态 + 设备指纹 + 风控规则;隐藏真实下单地址(到点才下发带签名的令牌)。

关键代码

Redis Lua 原子扣减(可以直接在 Redis 中运行):

-- KEYS[1]=库存 key  KEYS[2]=已购用户集合  ARGV[1]=用户 id
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
  return -1                              -- 已经抢过
end
local stock = tonumber(redis.call('GET', KEYS[1]) or '0')
if stock <= 0 then
  return 0                               -- 售罄
end
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
return 1                                 -- 成功,随后发消息异步下单

预占库存与超时释放的完整讨论见 Redis 库存预占、延迟消息取消订单。

取舍

决策 选择 原因
扣库存位置 Redis 原子扣减 + 数据库兜底 数据库扛不住瞬时流量
下单 异步(消息队列) 削峰,按数据库能力消费
一致性 最终一致 + 对账 Redis 和数据库之间可能短暂不一致
用户体验 排队中 + 轮询结果 换取系统稳定

易错点

  • 先查库存再扣减(两步操作),并发下超卖。
  • Redis 扣减成功但消息发送失败,库存“丢失”:需要补偿(本地消息表 / 对账)。
  • 数据库下单没有唯一约束,消息重复消费产生重复订单。
  • 超时取消时没有检查订单是否已支付。
  • 秒杀服务和主站共用数据库连接池。

面试怎么讲

“秒杀我分层削峰:CDN 静态页、按钮控制和验证码打散、网关限流;秒杀服务独立部署。库存提前加载到 Redis,用 Lua 脚本原子完成‘是否已购、库存检查、扣减、记录用户’,杜绝超卖和重复购买。扣减成功后发消息异步下单,数据库用条件更新再兜底,订单表唯一约束保证幂等;未支付由延迟消息超时释放库存。Redis 和数据库之间通过对账保证最终一致。”