Zer0e's Blog

秒杀系统设计

字数统计: 5.9k阅读时长: 19 min
2025/03/15 Share

前言

如何设计一个秒杀系统,想必大家一定在互联网上看了各种文章,本文结合内部系统的一些实践还有内部分享,总结了一些系统设计要点,供没有相关经验的同学参考。

什么是秒杀

在电商领域,存在着典型的秒杀业务场景,简单来说就是一件商品的购买人数远远大于这件商品的库存,而且这件商品在很短的时间内就会被抢购一空。即特定SKU的瞬时访问需求呈现指数级增长,而可售库存量级则维持极低水平,最终形成供不应求的”库存击穿”现象。

比如每年的618、双11等业务场景,就是典型的秒杀业务场景。例如2023年双11期间,热门3C单品的每秒订单峰值达12万笔,库存释放后平均3.2秒即告售罄。

秒杀活动的目的是拉新和促活。
拉新:通过实施”锚定商品”策略,选取高认知度、强引流属性的商品设置超低折扣,利用价格敏感型消费者的逐利心理,构建用户转化漏斗。最终形成”流量磁铁”效应。
促活:为主站引流,进而带动高客单价商品的销售。

秒杀的特点

  • 业务特点

    • 限时限量限价

    • 活动预热

    • 持续时间短

  • 技术特点

    • 瞬时流量突增

    • 读多写少

    • 数据一致性要求高

秒杀系统的挑战

1. 巨大的瞬时流量

作为网站运营体系中的补充性营销手段,秒杀活动具有显著的突发性特征:其业务周期通常以分钟级计算,却在极短时间内会产生指数级增长的并发访问请求。若将此类活动与常规业务系统进行混合部署,将面临双重风险:其一,突发流量可能引发服务器资源过载,导致正常业务响应延迟;其二,更严重的情况下可能造成系统级联故障,致使整体服务平台瘫痪。因此,构建针对瞬时流量洪峰的防御体系,需要从多维度建立系统化的应对策略。

2. 热点问题

在分布式系统架构中,热点问题始终是高并发场景的核心挑战,主要表现为两种形态:操作型热点(如高频接口调用)和数据型热点(如特定商品的库存访问)。对热点的有效识别与治理直接关系到系统稳定性。

  • 异常热点请求的资源侵占效应:即使恶意请求仅占全局流量的百万分之一量级,若未建立有效的请求过滤机制,这类无效操作可能占据90%以上的计算资源,形成严重的资源错配现象。这不仅导致基础设施成本的无谓损耗,更会挤压正常业务请求的处理能力。
  • 合法热点请求的优化必要性:对于经过验证的有效热点请求,系统架构需要建立分级处理机制。通过热点预判、动态缓存策略、读写分离架构等手段,实现服务资源的精准调度。例如,针对秒杀商品的库存访问,可采用缓存替代传统数据库操作。
  • 热点扩散的连锁风险防控:未受控的热点可能引发次生问题,如缓存击穿导致的数据库雪崩,或分布式锁竞争引发的服务阻塞。这要求系统设计时需建立热点监控体系,实现毫秒级的热点识别与动态降级能力。
热点操作 热点数据
零点刷新、零点下单、零点添加购物车都属于热点操作 对于秒杀活动,大家抢购的都是同一个商品,所以这个商品直接就被推到了热点的位置,不管你是用的数据库,还是分布式缓存”都无法支持几十万、上百万对同一个key的读写
热点操作是用户的行为,无法约束,但可以做一些限制,比如用户频繁刷新页面提示并阻止 热点数据的处理有三步,一是热点识别,二是热点隔离,三是热点优化

3. 超卖问题

秒杀开始时,同时会有很多用户请求秒杀接口,如果查询库存和扣减库存这两步操作如果不能保证原子操作。一定会导致超卖问题,造成资损。
如何防止商品超卖造成资损,是需要我们重点考虑的。

4. 接口防刷问题

黄牛党能够利用机器下单、人肉抢单,将秒杀商品瞬间抢到手,然后高价格售出赚取差价。大规模的批量机器下单,还会对网站的流量带来压力,产生类DDOS攻击,甚至能够造成网站瘫痪。
如何防正这类软件的重复无效请求,防止不断发起的请求也是需要我们针对性考虑的。

秒杀系统架构原则

1. 尽量将请求拦截在上游

尽量把请求拦截在上游,逐层过滤减少请求,让请求在秒杀系统可承受范围之内。

我们将请求进行分层,从用户侧到系统分别经过DNS、CDN、WEB网关、WEB服务、中间件、DB。

2. 数据要尽量少

一是用户请求的数据能少就少,包括上传给系统的数据和系统返回给用户的数据。二是要求系统依赖的数据能少就少,包括系统完成某些业务逻辑需要读取和保存的数据,这些数据一般是和后台服务以及数据库打交道的。数据库本身容易成为一个瓶颈,数据越简单、越小则越好。

3. 请求数要尽量少

当用户请求的网页内容完成基础加载后,浏览器的页面渲染流程实际上还会触发一系列次级资源请求。这些被统称为”附加请求”的资源不仅包含基础的CSS样式表和JavaScript脚本文件,还涉及页面中的各类图像素材、异步数据请求(如Ajax技术实现的动态内容加载)等关键元素。

从网页性能优化的角度考虑,应当尽可能压缩这类附加请求的总数量。其根本原因在于每个网络请求的发起都会产生多维度的性能损耗:首先在连接建立阶段需要完成完整的TCP三次握手协议,若涉及HTTPS加密通信还会增加TLS协商的额外开销;其次,受限于浏览器对同域名并发请求数的限制(通常为6-8个),以及部分资源(特别是JavaScript脚本)因可能修改DOM结构而必须采用的串行加载机制,请求队列的堆积会显著延长关键渲染路径;再者,当资源分布在多个不同域名时(如使用CDN加速或第三方服务),每个新域名都需要经历完整的DNS解析过程,包括递归查询、缓存验证等环节,这在网络状况欠佳时可能产生数百毫秒级的延迟。

因此,通过减少附加请求总量,可以有效规避上述各环节的潜在性能瓶颈,从而显著降低网络传输开销、提升资源加载效率,并最终改善终端用户的体验感知。

4. 请求路径要尽量短

请求路径特指客户端请求从发起到最终数据返回所经历的系统节点拓扑。从接入层网关到业务逻辑服务,再到数据库集群,每个节点都会引入特定处理时延。缩短调用链路不仅能提升系统可用性(减少单点故障概率),更能产生双重性能增益:其一降低序列化开销(每经过一个RPC节点至少触发两次编解码操作,常见协议如JSON序列化的CPU耗时可达毫秒级),其二压缩网络传输时间(跨机房调用通常产生5-15ms额外延迟)。

典型优化策略包括但不限于:对业务强耦合的服务实施”物理部署聚合”,通过JVM内方法调用替代跨进程通信,此举可消除协议转换开销(如Dubbo协议头128字节的无效负载)

5. 依赖要尽量少

系统依赖可划分为强依赖(服务不可降级)与弱依赖(具备柔性处理能力)两类,其中强依赖构成系统稳定性关键路径。科学的依赖治理需建立三维管控体系:首先实施”服务分级认证”,构建0-3级金字塔型架构(0级:直接影响核心交易链路;1级:重要业务支撑系统;2级:非关键业务系统;3级:辅助型系统),并遵循”同级或向下兼容”原则——0级系统仅允许依赖同级或更高级服务;其次建立”熔断防护机制”,通过Hystrix等框架实现故障服务的快速隔离,当1级依赖系统(如优惠券服务)响应成功率低于阈值时,自动触发服务降级并返回托底数据;最后采用”异步化改造”,将非必要同步依赖转换为消息队列驱动的异步处理模式。以支付系统为例,其作为0级系统对接的风控服务应保持同级,而日志采集等辅助功能需通过Kafka异步解耦,确保核心链路不受边缘服务故障影响。

系统设计

DNS处理

DNS 层可以做一些和网络相关的防攻击措施,例如CNAME-WAF,这层我们无法写业务,但是可以拦截一些攻击请求。

前端设计

前端页面动静分离

动静分离的首要目的是将动态页面改造成适合缓存的静态页面。这个如今的新系统大多数都能采用这种设计。下面我来介绍几种常用的手段。

数据拆分

  • 用户:用户身份信息包括登录状态以及登录画像等,相关要素可以单独拆分出来,通过动态请求进行获取;与之相关的广告推荐,如用户偏好、地域偏好等,同样可以通过异步方式进行加载。
  • 时间:秒杀时间是由服务端统一管控的,可以通过动态请求进行获取

数据静态缓存

三种方式:1、浏览器;2、CDN;3、服务端。这个没什么好说的,都是基础了。

数据整合

这里介绍两种方案。

  • ESI方案:Web代理服务器上请求动态数据,并将动态数据插入到静态页面中,用户看到页面时已经是一个完整的页面。这种方式对服务端性能要求高,但用户体验较好
  • CSI方案:Web代理服务器上只返回静态页面,前端单独发起一个异步JS请求动态数据。这种方式对服务端性能友好,但用户体验稍差。

接口url动态化

为了避免有程序访问经验的人通过下单页面直接访问秒杀后台接口来秒杀货品,我们需要将秒杀的url实现动态化,即使是开发整个系统的人都无法在秒杀开始前知道秒杀的url。具体的做法就是通过hash一串随机字符作为秒杀的url,然后前端访问后台获取具体的url,后台校验通过之后才可以继续秒杀。

前端页面拦截处理

在用户点击秒杀按钮之后,可以先进行用户行为判断,这样可以拦截掉很多刷接口的秒杀器。

验证码、拼图、答题方式

在发起秒杀后端请求之前,先进行验证码、拼图、答题判断,验证成功才可以继续后续流程。

限制请求频次方式

限制用户点击秒杀按钮的频率,强制用户等待。这属于前端限流,用户在秒杀按钮点击以后发起请求,那么在接下来的5秒是无法点击(通过设置按钮为disable)。这一小举措开发起来成本很小,但是很有效。

接入风控

对于秒杀器等机器秒杀行为,需要在上游做拦截处理,防止它们超高频的请求对秒杀系统造成巨大的流量压力。秒杀系统需要接入营销风控系统,对羊毛党、黄牛党等营销作弊行为进行风险监控、预警、识别、过滤、阻断。这样秒杀系统的压力就转移到风控系统了,减少了自身压力。

服务端设计

秒杀系统高可用设计,有隔离、削峰限流、缓存热点处理、熔断降级、扩容等一系列措施。

隔离设计

通过秒杀流量的隔离,我们能够把巨大瞬时流量的影响范围控制在隔离的秒杀环境里。

  • 业务隔离:把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就有了已知热点,因此可以提前做好预热。
  • 系统隔离:系统隔离更多的是运行时的隔离,可以通过分组部署的方式和另外99%分开。秒杀可以申请单独的域名,目的也是让请求落到不同的集群中。
  • 数据隔离:秒杀所调用的数据大部分都是热点数据,比如会启用单独的Cache集群或者MySQL数据库来放热点数据,目的也是不想0.01%的数据有机会影响99.99%数据。

削峰限流

限流是一种有损技术削峰。
验证码、拼图、答题以及异步化消息队列可以归为无损削峰。

限流

限流是系统自我保护的最直接手段,再厉害的系统,总有所能承载的能力上限,一旦流量突破这个上限,就会引起实例宕机,进而发生系统雪崩,带来灾难性后果。
可以使用Sentinel、Hystrix、Ratelimiter等做限流。

削峰

产品方式的削峰:验证码、拼图、答题。

技术方式的削峰:异步化消息队列。

热点处理

热点数据分为“静态热点数据”和“动态热点数据”。

静态热点数据是能够提前预测的热点数据例如,我们可以通过卖家报名的方式提前筛选出来,通过报名系统对这些热点商品进行打标。另外,我们还可以通过大数据分析来提前发现热点商品,比如我们分析历史成交记录用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是可以提前分析出来的热点。

动态热点数据不能被提前预测到的,系统在运行过程中临时产生的热点例如,卖家在抖音上做了广告,然后商品一下就火了,导致它在短时间内被大量购买。缓存热点数据

优化热点数据最有效的办法就是缓存热点数据,如果热点数据做了动静分离,那么可以长期缓存静态数据。但是,缓存热点数据更多的是“临时”缓存,即不管是静态数据还是动态数据,都用业个队列短暂地缓存数秒钟,由于队列长度有限,可以采用LRU淘汰算法替换。

限制热点数据

限制更多的是一种保护机制,限制的办法也有很多,例如对被访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源。

限购

限购之于库存,就像秒杀之于下单,前者都是后者的过滤网和保护伞。
限购的主要功能就是做商品的限制性购买,一般限制的维度主要包括两方面。

商品维度限购

最基本的限制就是商品活动库存的限制,即每次参加秒杀活动的商品投放量。如果再细分,还可以支持针对不同地区做投放的场景,比如我只想在北京、上海、广州、深圳这些一线城市投放,那么就只有收货地址是这些城市的用户才能参与抢购,而且各地区库存量是隔离的,互不影响。

个人维度限购

以个人维度来做限制,这里不单单指同一用户ID,还会从同一手机号、同一收货地址、同一设备IP等维度来做限制。比如限制同一手机号每天只能下1单,每单只能购买1件,并且一个月内只能购买2件等。个人维度的限购,体现了秒杀的公平性。

减库存

减库存一般有三种方式:下单减库存,付款减库存,预扣库存。

而秒杀这个场景,应该采用哪种方案比较好呢?

名称 下单减库存 付款减库存 预扣库存
定义 即当买家下单后,在商品的总库存中减去买家购买数量 即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家 买家下单后,库存为其保留一定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。
问题 竞争对手通过恶意下单的方式将该卖家的商品全部下单,让这款商品的库存减为零,但是不付款,从而影响卖家正常的商品销售。 “付款减库存”因为下单时不会减库存,所以也就可能出现下单成功数远远超过真正库存数的情况,会导致很多买家下单成功但是付不了款,买家的购物体验自然比较差 针对恶意下单这种情况,虽然把有效的付款时间设置为10分钟,但是恶意买家完全可以在10分钟后再次下单,或者采用一次下单很多件的方式把库存减完。

由于参加秒杀的商品,一般都是“抢到就是赚到”,所以成功下单后却不付款的情况比较少,再加上卖家对秒杀商品的库存有严格限制,所以秒杀商品采用“下单减库存”更加合理。
库存超卖的问题主要是由两个原因引起的,一个是查询和扣减不是原子操作,另一个是并发请求无序。

库存不能扣减为负数

要保证大并发请求时库存数据不能扣减为负数,有以下几个方案:

  • 在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚
  • 直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行SQL语句来报错
  • 使用CASE WHEN判断语句,例如这样的SQL语句:
    UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

缓存中减库存

秒杀商品的减库存逻辑非常单一,比如没有复杂的SKU库存和总库存这种联动关系的,可以把秒杀商品减库存直接放到缓存系统中实现,也就是直接在缓存中减库存。

数据库中减库存

如果有比较复杂的减库存逻辑,或者需要使用事务,还是必须在数据库中完成减库存。由于 MySQL存储数据的特点,同一数据在数据库里肯定是一行存储(MySQL),因此会有大量线程来竞争InnoDB行锁,而并发度越高时等待线程会越多,TPS会下降,响应时间(RT)会上升,数据库的吞吐量就会严重受影响。

解决并发锁的问题,有两种办法:

  • 应用层做排队: 按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库同一行记录进行操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用太多的数据库连接。
  • 数据库层做排队: 应用层只能做到单机的排队,但是应用机器数本身很多,这种排队方式控制并发的能力仍然有限,所以如果能在数据库层做全局排队是最理想的。阿里的数据库团队开发了针对这种MySQL的InnoDB层上的补丁程序(patch),可以在数据库层上对单行记录做到并发排队。

总结

秒杀系统的设计本质是一场高并发场景下的“攻防战”,既要保障极致的性能与稳定性,又要兼顾业务公平性与数据一致性。本文从秒杀的业务与技术特点出发,系统性地梳理了四大核心挑战——瞬时流量、热点问题、超卖风险与接口防刷,并提出了五大架构原则:请求拦截上游化、数据精简、请求收敛、链路缩短、依赖解耦

在系统设计层面,通过动静分离、动态URL、前端拦截与风控接入,将无效请求层层过滤;借助业务隔离、削峰限流、热点缓存与预扣库存策略,精准控制核心资源;最终结合异步队列、分布式锁与原子化操作,保障库存与交易的最终一致性。

秒杀系统的设计没有“银弹”,需根据业务规模动态权衡技术方案。但核心逻辑始终不变:化瞬时洪峰为涓涓细流,以最小代价支撑最大并发。希望本文总结的实践经验,能为初涉高并发系统设计的开发者提供清晰的解题思路,在面对类似场景时,快速构建出可靠、弹性、高效的秒杀架构。

CATALOG
  1. 1. 前言
  2. 2. 什么是秒杀
  3. 3. 秒杀的特点
  4. 4. 秒杀系统的挑战
    1. 4.1. 1. 巨大的瞬时流量
    2. 4.2. 2. 热点问题
    3. 4.3. 3. 超卖问题
    4. 4.4. 4. 接口防刷问题
  5. 5. 秒杀系统架构原则
    1. 5.1. 1. 尽量将请求拦截在上游
    2. 5.2. 2. 数据要尽量少
    3. 5.3. 3. 请求数要尽量少
    4. 5.4. 4. 请求路径要尽量短
    5. 5.5. 5. 依赖要尽量少
  6. 6. 系统设计
    1. 6.1. DNS处理
    2. 6.2. 前端设计
      1. 6.2.1. 前端页面动静分离
        1. 6.2.1.1. 数据拆分
        2. 6.2.1.2. 数据静态缓存
        3. 6.2.1.3. 数据整合
      2. 6.2.2. 接口url动态化
      3. 6.2.3. 前端页面拦截处理
        1. 6.2.3.1. 验证码、拼图、答题方式
        2. 6.2.3.2. 限制请求频次方式
    3. 6.3. 接入风控
    4. 6.4. 服务端设计
      1. 6.4.1. 隔离设计
      2. 6.4.2. 削峰限流
        1. 6.4.2.1. 限流
        2. 6.4.2.2. 削峰
      3. 6.4.3. 热点处理
        1. 6.4.3.1. 限制热点数据
      4. 6.4.4. 限购
        1. 6.4.4.1. 商品维度限购
        2. 6.4.4.2. 个人维度限购
      5. 6.4.5. 减库存
        1. 6.4.5.1. 库存不能扣减为负数
        2. 6.4.5.2. 缓存中减库存
        3. 6.4.5.3. 数据库中减库存
  7. 7. 总结