秒杀系统是一个比较典型的短时高流量的应用场景,且大部分人争抢的部分有限资源
1. 架构原则
秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统
1.1 数据尽量少
因为数据在网络传输需要时间,其次请求数据和返回数据都需要服务端做处理(写网络的压缩和字符编码,rpc调用的序列化与反序列化)
依赖的系统和组件越少越好
1.2 请求数越少越好
1.3 路径尽量短
1.4 请求依赖越短越好
1.5 不要有单点
越追求极致性能,系统定制开发就会越多,同时系统的通用性也就会越差。
2. 架构案例
以淘宝早期案例为例:
2.1 早期简化方案
如果你想快速搭建一个简单的秒杀系统,只需要把你的商品购买页面增加一个"定时上架"功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完了也就结束了。
2.2 优化一:支撑10w/s
- 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化,例如这个独立出来的系统就减少了店铺装修的功能,减少了页面的复杂度;
- 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品购买集群的机器负载;
- 将热点数据(如库存数据)单独放到一个缓存系统中,以提高"读性能";
- 增加秒杀答题,防止有秒杀器抢单。

2.3 优化二:超过100W/s
- 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击抢宝按钮,借此把页面刷新的数据降到最少;
- 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够避免压垮公共缓存集群。
- 增加系统限流保护,防止最坏情况发生。

3. 关键设计点
3.1 动静分离
"动态数据"和"静态数据"的主要区别就是看页面中输出的数据是否和URL、浏览者、时间、地域相关,以及是否含有Cookie等私密数据。
3.1.1 如何做动静分离
- URL唯一化
- 分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。
- 分离时间因素。服务端输出的时间也通过动态请求获取。
- 异步化地域因素。详情页面上与地域相关的因素做成异步方式获取
- 去掉Cookie。服务端输出的页面包含的Cookie可以通过代码软件来删除
3.1.2 动态数据处理方案
ESI(Edge Side Includes)方案和CSI(Client Side Include)方案。
- ESI方案(或者SSI) :即在Web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。
- CSI方案。即单独发起一个异步JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。
3.1.3 集中架构方案
- 实体机单机部署
- 统一Cache层
- 上CDN

3.2 处理热点数据
3.2.1 什么是热点数据
热点请求会大量占用服务器处理资源,虽然这个热点可能只占请求总量的亿分之一,然而却可能抢占90%的服务器资源,如果这个热点请求还是没有价值的无效请求,那么对系统资源来说完全是浪费。
热点分为热点操作 和热点数据:
- 热点操作:例如大量的刷新页面、大量的添加购物车、双十一零点大量的下单等都属于此类操作。优化的思路就是根据CAP理论做平衡
- 热点数据:用户的热点请求对应的数据
- 静态热点数据:能够提前预测的热点数据。例如,我们可以通过卖家报名 的方式提前筛选出来,通过报名系统对这些热点商品进行打标。另外,我们还可以通过大数据分析来提前发现热点商品,比如我们分析历史成交记录、用户的购物车记录,来发现哪些商品可能更热门、更好卖,这些都是可以提前分析出来的热点。
- 动态热点数据:系统在运行过程中临时产生的热点。例如,卖家在抖音上做了广告,然后商品一下就火了,导致它在短时间内被大量购买。
3.2.2 如何发现热点数据
- 发现静态热点数据
- 提前报名筛选
- 技术手段提前预测:对买家每天访问的商品进行大数据计算,然后统计出TOP N的商品
- 发现动态热点数据 构建一个动态发现热点数据系统:
- 构建一个异步的系统,它可以收集交易链路上各个环节中的中间件产品的热点Key,如Nginx、缓存、RPC服务框架等这些中间件(一些中间件产品本身已经有热点统计模块)。
- 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(包括详情、购物车、交易、优惠、库存、物流等)访问的时间差,把上游已经发现的热点透传给下游系统,提前做好保护。比如,对于大促高峰期,详情系统是最早知道的,在统一接入层上Nginx模块统计的热点URL。
- 将上游系统收集的热点数据发送到热点服务台,然后下游系统(如交易系统)就会知道哪些商品会被频繁调用,然后做热点保护。

3.2.3 处理热点数据
- 优化:缓存热点数据
- 限制:被访问商品的ID做一致性Hash,然后根据Hash做分桶,每个分桶设置一个处理队列,这样可以把热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源
- 隔离:不要让1%的请求影响到另外的99%,隔离出来后也更方便对这1%的请求做针对性的优化
- 业务隔离:做成单独营销活动,提前识别热点数据
- 系统隔离:运行时的隔离,可以通过分组部署的方式和另外99%分开
- 数据隔离:启用单独的cache集群和mysql数据库
3.3 流量削峰
服务器处理资源是恒定的,出现峰值的时候很容易忙导致处理不过来
3.3.1 排队
最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。

3.3.2 答题
止部分买家使用秒杀器在参加秒杀时作弊
延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰

3.3.3 分层过滤
对请求进行分层过滤,从而过滤掉一些无效的请求。分层过滤其实就是采用"漏斗"式设计来处理请求的
分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,让"漏斗"最末端的才是有效请求。而要达到这种效果,我们就必须对数据做分层的校验。
分层校验的基本原则是:
- 将动态请求的读数据缓存(Cache)在Web端,过滤掉无效的数据读;
- 对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题;
- 对写数据进行基于时间的合理分片,过滤掉过期的失效请求;
- 对写请求做限流保护,将超出系统承载能力的请求过滤掉;
- 对写数据进行强一致性校验,只保留最后有效的数据。
3.4 提高系统性能
3.4.1 影响性能因素
系统服务端性能:QPS(Query Per Second,每秒请求数)和响应时间(Response Time,RT)
响应时间和QPS 的关系:响应时间一般都是由CPU执行时间和线程等待时间(比如RPC、IO等待、Sleep、Wait等)组成,即服务器在处理一个请求时,一部分是CPU本身在做运算,还有一部分是在各种等待。
降低RT需要减少CPU执行时间
线程数对QPS的影响:要提升性能我们就要减少CPU的执行时间,另外就是要设置一个合理的并发线程数,通过这两方面来显著提升服务器的性能。
线程数 = [(线程等待时间 + 线程CPU时间) / 线程CPU时间] × CPU数量
3.4.2 如何发现瓶颈
可使用arthes火焰图统计CPU耗时
APM监控工具查看
3.4.3 优化系统
- 减少编码
- 减少序列化
- Java极致优化
- 并发读优化:本地缓存
性能优化的过程首先要从发现短板开始,还可以在减少数据、数据分级(动静分离),以及减少中间环节、增加预处理等这些环节上做优化。
要做好优化,你还需要做好应用基线,比如性能基线(何时性能突然下降)、成本基线(去年双11用了多少台机器)、链路基线(我们的系统发生了哪些变化),你可以通过这些基线持续关注系统的性能,做到在代码上提升编码质量,在业务上改掉不合理的调用,在架构和调用链路上不断的改进。
3.5 扣库存设计逻辑
3.5.1 扣库存方式
- 下单扣库存:导致下单不付款
- 付款扣库存:导致下单没法付款或者付款后没库存取消订单
- 预扣库存:下单后保留一段时间库存
3.5.2 优化
"库存"是个关键数据,也是个热点数据,因为交易的各个环节中都可能涉及对库存的查询。但是,我在前面介绍分层过滤时提到过,秒杀中并不需要对库存有精确的一致性读,把库存数据放到缓存(Cache)中,可以大大提升读性能。
并发写优化:可以将扣库存操作放到缓存系统(REDIS)中,异步记录到MYSQL数据库 同时基于秒杀商品做排队机制,避免占用过多资源
3. 兜底方案设计
3.1 高可用服务

- 架构阶段:架构阶段主要考虑系统的可扩展性和容错性,要避免系统出现单点问题。例如多机房单元化部署,即使某个城市的某个机房出现整体故障,仍然不会影响整体网站的运转。
- 编码阶段:编码最重要的是保证代码的健壮性,例如涉及远程调用问题时,要设置合理的超时退出机制,防止被其他系统拖垮,也要对调用的返回结果集有预期,防止返回的结果超出程序处理范围,最常见的做法就是对错误异常进行捕获,对无法预料的错误要有默认处理结果。
- 测试阶段:测试主要是保证测试用例的覆盖度,保证最坏情况发生时,我们也有相应的处理流程。
- 发布阶段:发布时也有一些地方需要注意,因为发布时最容易出现错误,因此要有紧急的回滚机制。
- 运行阶段:运行时是系统的常态,系统大部分时间都会处于运行态,运行态最重要的是对系统的监控要准确及时,发现问题能够准确报警并且报警数据要准确详细,以便于排查问题。
- 故障发生:故障发生时首先最重要的就是及时止损,例如由于程序问题导致商品价格错误,那就要及时下架商品或者关闭购买链接,防止造成重大资产损失。然后就是要能够及时恢复服务,并定位原因解决问题。
3.2 兜底方案
3.2.1 降级
所谓"降级",就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。
3.2.2 限流
如果说降级是牺牲了一部分次要的功能和用户的体验效果,那么限流就是更极端的一种保护措施了。限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。 分为客户端限流、服务端限流
3.2.3 拒绝服务
当系统负载达到一定阈值时,例如CPU使用率达到90%或者系统load值达到2*CPU核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。
参考:
秒杀系统架构设计都有哪些关键点?