Skip to content

秒杀系统架构设计 ​


❓ 面试官:如果让你设计一个 10 万人抢 100 台 iPhone 的秒杀系统,你怎么防止超卖?如何扛住高并发?【中高级】 ​

频率:🔥🔥🔥🔥🔥

这是一个极其经典的系统设计题,几乎是中级后端面试的必考项目。千万不要一上来就说查数据库。

💡 一句话总结(先抛整体架构结论): 秒杀的核心思想是:“层层拦截、流量削峰、异步处理、内存预扣”。绝对不能让 10 万个请求直接打到 MySQL 上。通过 Nginx、网关、Redis、MQ 构建多道防线,最终真正落库的可能只有几百个请求。

📝 详细的架构推演设计(按生命周期分层答):

第一道防线:前端与 CDN(拦截 80% 的无效/重复请求)

  1. 防连点:用户点击“抢购”按钮后,按钮立刻置灰变暗 5 秒,防止小白用户疯狂疯狂狂点。
  2. 页面静态化:秒杀的商品图片、详情介绍等全部做成静态 HTML,推送到 CDN。用户刷新页面根本不会打到我们的后端接口。

第二道防线:网关与风控拦截(防刷与限流)

  1. IP 与黑名单限流:在 Nginx 或 SpringCloud Gateway 层面,利用 Redis 记录同一个 IP 一秒钟的访问次数,超过 10 次直接返回“系统繁忙”(拦截掉机器刷单脚本)。
  2. 隐藏秒杀接口:秒杀没开始前,前端拿不到真实的秒杀 URL。开抢瞬间才通过一个接口返回真实的、带动态 Token 的秒杀地址,防止黑客提前写脚本死循环调用真实接口。

第三道防线:Redis 内存预扣减(核心防超卖) 这是真正的抗压核心!绝对不能用 update sku set stock = stock -1 where id = 1 and stock > 0 这种方式直接去压测数据库,数据库的行锁会瞬间死锁或者连接池打满。

  • 预热库存:秒杀前,提前把这 100 台 iPhone 的库存数量扔到 Redis 里(比如 set seckill_sku_1001 100)。
  • Lua 脚本扣库存(极其关键):使用 Redis 的 Lua 脚本进行扣减(因为 Lua 脚本在 Redis 里执行是原子性的,不会有并发覆盖问题)。脚本逻辑是:“查库存,如果 > 0,则扣减 1,返回成功;否则返回失败”。
  • 这个阶段,10 万个并发打到 Redis,Redis 单机能扛 8-10 万 QPS,非常轻松。最后只有 100 个请求拿到了 成功 的标志。

第四道防线:MQ 异步落库(保护数据库)

  • 上面那 100 个在 Redis 里抢到库存的用户,系统不是立刻给他们创建订单查数据库。
  • 而是把这 100 个人包装成一条消息发到 RocketMQ/RabbitMQ,然后立刻给用户前端返回:“您已抢购成功,正在排队生成订单中...”。
  • 后台有几个平缓的消费者去订阅这个 MQ,慢条斯理地、以每秒几十个的速度去写 MySQL 订单表。这样 MySQL 就如同被保护在温室里,毫无压力。

❓ 面试官:你在上面提到用 Redis 预扣减库存,那如果用户下了单(Redis 库存扣了),但是最后没付款取消了,这时候怎么回退库存保证别人还能买? ​

频率:🔥🔥🔥🔥

💡 一句话总结(先抛结论): 这就需要结合延迟队列或者定时任务做“库存回滚”机制。

📝 实战解决方案:

  1. 发送下单消息给普通队列的同时,发送一条延迟时间为 15 分钟的延迟消息。
  2. 15 分钟后,消费者收到这条延迟消息,去查一下数据库的订单状态。
  3. 如果订单状态是“已支付”,那没事了,直接丢弃消息。
  4. 如果订单状态是“待支付”或“已取消”,说明用户鸽了。这时候消费者去执行回滚操作:调用 Redis 将刚才扣掉的那 1 个库存加回来(利用 Lua 脚本或者 incr 命令),同时把数据库的订单标记为关闭。这样其他用户就可以继续抢了。