面试知识库
极高 困难

秒杀系统设计#

一句话答案#

秒杀三层削峰:前端限流(按钮/验证码)→Redis Lua 原子预扣库存→MQ 异步下单,尽量在上游拦截请求。

核心要点

架构分层:

  1. 前端:按钮灰显/验证码/限制频率
  2. 网关层:限流/黑名单/令牌桶
  3. 服务层:Redis Lua 原子预扣库存 → 成功则发 MQ
  4. 异步层:消费 MQ → 创建订单 → 扣减DB库存
  5. 数据层:乐观锁防超卖 UPDATE SET stock=stock-1 WHERE stock>0

核心: 尽量在上游拦截,减少到达数据库的请求

防超卖原子性深挖#

Redis Lua 预扣为何不超卖: Redis 单线程串行执行命令,一段 Lua 脚本被当作一个整体命令投递,执行期间不会插入任何其他客户端的命令——「读库存 → 判断 >0 → 扣减」三步天然不可分割,杜绝”两个请求同时读到库存 1 都扣减”的竞态。注意这是”执行不被打断”而非数据库的事务隔离,脚本中途报错不回滚已执行的写。

DB 乐观锁 WHERE stock>0 本质是 CAS: UPDATE 在 InnoDB 里走当前读 + 排他行锁,并发 UPDATE 在行锁上排队串行,每个扣减都基于前一个提交后的最新库存值,不会丢失更新;当库存扣到 0,WHERE stock>0 不成立,影响行数返回 0 即售罄。库存值本身充当版本号,无需显式 version 列。

为何用对账而非分布式事务: Redis 预扣与 DB 落库跨两个系统,强一致需 2PC/TCC 会把高并发链路拖成同步阻塞、性能崩塌。故选最终一致——预扣成功即返回,MQ 异步落库,定时对账任务比对 Redis/DB 库存差异做补偿修正。(机制展开见 库存扣减方案

面试回答(2分钟版)

秒杀系统的核心原则是把请求挡在离用户最近的地方,像漏斗一样层层过滤。第一层前端:按钮点击后立即置灰防重复提交,加验证码拦截脚本,这一层能挡掉90%以上请求。第二层网关:Nginx做限流和IP黑名单,用令牌桶算法控制放行速率。第三层服务层是关键:用Redis加Lua脚本做原子预扣库存,一个Lua脚本里完成库存判断和扣减,保证原子性不会超卖,预扣成功才发MQ消息。第四层异步处理:消费者从MQ拿消息创建订单,用DB乐观锁落库,SQL里加WHERE stock大于0兜底。这样百万级请求经过层层过滤,最终到达数据库的可能只有几百个。Redis预扣和DB扣减之间靠MQ保证最终一致性,如果出现不一致就用对账任务补偿。Redis挂了的降级方案是直接走DB乐观锁,性能会下降但不会超卖。

追问与易错

追问方向:

  • “Redis 预扣库存和 DB 扣减不一致怎么办?”→ MQ 保证最终一致性,消费失败自动重试;定时对账任务比对 Redis 库存和 DB 库存差异,发现不一致则触发补偿修正
  • “如何防止黄牛/脚本?”→ 多层防御:前端验证码(滑块/图形)+ 设备指纹识别同一设备多账号 + 接口限频(同 IP/同用户每秒 N 次)+ 风控规则引擎实时拦截异常行为
  • “如果 Redis 挂了怎么兜底?”→ 降级方案:跳过 Redis 预扣直接走 DB 乐观锁(UPDATE WHERE stock>0),同时开启限流保护 DB(QPS 降到 DB 可承受范围);性能下降但不会超卖

易错点:

  • ❌ “直接查 DB 判断库存”——高并发下 DB 扛不住
  • ❌ “先扣 DB 再发 MQ”——应该先 Redis 预扣,成功后再发 MQ 异步落 DB