高频面试题深度解析:如何设计一个百万级并发的秒杀系统?

363 阅读4分钟

秒杀系统是典型的高并发、短时流量洪峰场景。用户可能在1秒内涌入百万次请求,但库存可能只有100件商品。如何保证系统不崩溃、不超卖、不卡顿?本文将结合实战经验,梳理秒杀系统的核心设计原则和关键技术方案。


一、为什么秒杀系统难?

假设一场秒杀活动:1000件商品,10万人同时抢购
系统可能面临:

  1. 数据库击穿:每秒数万次查询直接打垮MySQL。
  2. 超卖问题:并发扣减库存导致实际卖出数量超过库存。
  3. 服务雪崩:某个节点崩溃后,引发连锁反应。

设计目标
✅ 不超卖
✅ 高可用(99.99%可用性)
✅ 低延迟(用户秒级响应)


二、分层拦截:90%的流量不进后端

1. 前端优化:拦截无效请求

  • 静态资源CDN化
    商品详情页提前渲染为静态HTML,通过CDN分发,减少90%动态请求。

    <!-- 静态页面示例:直接展示倒计时,不请求服务端 -->
    <div class="countdown" data-start="2024-06-01 20:00:00"></div>
    

    运行 HTML

  • 按钮防重复点击
    点击后禁用按钮,防止用户疯狂连点。

    document.getElementById("buyButton").onclick = function() {
      this.disabled = true; // 禁用按钮
      submitOrder();
    };
    
  • 答题验证
    引入滑块/算术题验证,拦截机器人请求(类似12306)。

2. 网关层:限流与过滤

  • 全局限流:Nginx限制每秒最大请求数(如1万QPS)。
  • 用户级限流:同一用户5秒内只能请求一次(Redis记录状态)。
  • 黑名单机制:过滤异常IP、恶意UA(如爬虫特征)。

三、库存扣减:如何保证不超卖?

1. Redis原子化扣减

  • 预热库存:提前将库存加载到Redis集群。

    SET stock_sku_1001 1000  # 初始化库存
    
  • 原子操作:使用DECR命令扣减库存(无需事务)。

    Long remain = redisTemplate.opsForValue().decrement("stock_sku_1001");
    if (remain >= 0) {
      // 扣减成功,生成订单
    } else {
      // 库存不足
    }
    
  • Lua脚本兜底(应对极端并发):

    -- 查询库存并扣减(原子化)
    local stock = redis.call('GET', KEYS[1])
    if stock and tonumber(stock) > 0 then
      redis.call('DECR', KEYS[1])
      return 1  -- 成功
    end
    return 0  -- 失败
    

2. 库存分段:降低热点Key压力

将1000库存拆分为10个Key(如stock_sku_1001_1stock_sku_1001_10),每个存100件。用户随机选择一个Key扣减,分散竞争。


四、异步化处理:订单与支付解耦

1. 消息队列削峰

  • 抢购成功→发MQ→异步下单
    用户抢到资格后,立即返回“排队中”,同时发送消息到RocketMQ/Kafka。

    // 发送MQ消息
    SendResult result = producer.send(new Message("order_topic", "下单数据"));
    
  • 订单服务消费消息
    异步写入MySQL,避免瞬时数据库压力(从1万QPS降到100 QPS)。

2. 订单状态管理

  • 待支付状态:订单保留15分钟,超时释放库存(定时任务回补Redis)。
  • 支付回调:用户支付后更新订单状态,扣减最终库存。

五、容灾设计:如何应对突发故障?

1. 熔断降级

  • 监控系统负载(如CPU > 80%),自动触发限流策略。
  • 兜底页面:直接返回“活动太火爆,请稍后再试”。

2. Redis故障降级

  • 降级到数据库:使用数据库行锁(SELECT ... FOR UPDATE)扣减库存。
  • 分布式锁:通过ZooKeeper/Redisson加锁,防止超卖(性能较低,慎用)。

六、数据一致性保障

1. 最终一致性

  • Binlog监听:通过Canal监听MySQL变更,回滚超时未支付订单的库存。
  • 对账任务:每小时对比Redis库存与DB订单总数,自动修复差异。

2. 数据库优化

  • 分库分表:订单表按用户ID分片(如user_id % 16)。

  • 热点更新

    UPDATE product SET stock = stock - 1 WHERE id = 1001 AND stock > 0;
    

七、实战建议:压测与演练

1. 全链路压测

  • 模拟真实流量(如10倍峰值),验证限流、降级、库存回补逻辑。
  • 工具:JMeter、阿里云PTS。

2. 混沌工程

  • 随机杀死节点、模拟网络延迟,验证系统自愈能力。

总结:秒杀系统设计原则

核心原则关键技术实现
分层拦截前端静态化 + 网关限流
异步削峰MQ异步下单 + 订单状态机
数据强一致Redis原子扣减 + 最终一致性对账
容灾兜底熔断降级 + 数据库降级方案

通过以上方案,系统可支撑百万级QPS,同时保证不超卖、低延迟和高可用。实际项目中需结合业务需求调整(如是否允许排队、部分成功),但核心思路始终是:层层过滤、异步解耦、数据兜底