系统设计:秒杀系统设计

秒杀系统设计的整体设计:商户、运营、活动这些功能性需求,以及系统上的非功能性的性能、稳定性要求

1. 需求分析

功能性需求特点:

  1. 运营中心能够向商家发起秒杀活动通知,商家可以看到并参加秒杀活动;
  2. 运营中心可以设置秒杀商品的开始时间、库存、发布、活动时效、单用户购买数量等等;
  3. 用户能够参与秒杀活动:并完成完整的下单、付款;

业务特性:

  1. 秒杀活动页面:高页面访问量,且用户可能不停刷新页面;
  2. 限时:秒杀活动有时效,一般持续时间短;
  3. 限量:秒杀活动商品量很少;

非功能性需求:

  1. 瞬时高并发:秒杀开始瞬间流量可能达到百万级请求。
  2. 读多写少;
  3. 数据一致性:库存有限,需防止超卖(超库存售卖)。保证库存扣减、订单生成的强一致性。
  4. 容灾:避免因高并发导致服务雪崩、数据库崩溃。
  5. 防御恶意请求:防御黄牛、机器人刷单等攻击。

2. 核心流程

2.1 运营角度

运营中心、商家以及用户的简化交互流程如下:

  1. 活动秒杀详情页的静态页面可以存放CDN;
  2. 商品定时上架,在活动开始之前,
  3. 秒杀按钮是动态开启的,CDN无法缓存,可以执行JS的定时器;

2.2 秒杀时序图

Loading Mermaid Chart...

3. 关键点

3.1 限流策略

秒杀的大部分流量是无效的,只有极少请求是有效的,因此:

  1. 尽量将流量在前置拦截,下面的步骤已经可以拦截掉大部分的流量了。可以让到达服务端的流量接近真实的用户个数;
    1. 前端:
      1. 静态的活动详情页面由CDN托管;
      2. 秒杀的按钮增加用户行为判断,减少脚本、秒杀器等流量;
      3. 限制秒杀按钮的访问频次,10s只能点击一次;
      4. 增加图形验证码,通过后才能执行秒杀;
    2. 网关层:
      1. 最外层的网关,要做到恶意攻击防御、IP黑名单等;
      2. 权限网关,校验好用户登录态;
    3. 业务层:
      1. 最外层需要判断活动是否已经结束、商品是否已经抢购完成;
      2. 单用户只能参与一次秒杀,在执行下单之前,先判断用户是否已经参与完成;
  2. 核心接口的阈值保护:在活动前压测,保证正常的系统各项指标的情况下,设置一个最大QPS;最低限是系统不能崩;

3.2 库存管理

库存管理主要包括:

  1. 秒杀活动开始前,商品库存需要预热到缓存中;
  2. 用户秒杀时,执行库存的预扣;
  3. 用户超时未支付,回补对应商品的缓存数量;

预扣逻辑

保证预扣不超卖:也就是库存不能扣成负数,就必须:

库存查询
库存预扣
是原子操作;

再加上是秒杀防止超卖,就必须上缓存执行

库存查询
库存预扣
的原子操作;考虑Lua脚本:

-- KEYS[1]=库存Key, ARGV[1]=扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1  -- 成功
else
    return 0  -- 失败
end

超时未支付逻辑

  1. 延时消息;RocketMQ、RabbitMQ;
  2. 定时任务轮询订单状态;

3.3 防止数据丟失

秒杀的过程中,通过缓存的库存扣减,无法瞬时将订单写入数据库,通常通过MQ异步落库,那么就要考虑MQ发送失败、MQ消息丟失;

针对MQ消息丟失的情况,参考:MQ消息丟失

3.4 系统的容灾

秒杀系统尽量减少外部服务依赖,特别是无强依赖;