感谢Gavin老师的分享
1. 秒杀系统的架构设计有哪些关键点
首先要从高维度去出发来整体思考,对于秒杀系统的设计,主要解决两个问题:
-
一个是并发读
并发读的核心优化理念是尽量减少用户到服务端来读数据,或者让他们读更少的数据。
-
一个是并发写,
并发写的处理原则也是一样的。
从整个系统架构层面考虑,要打造一个超大流量的并发读写、高性能、高可用的系统,整个用户路径上从浏览器到服务端我们要遵循几个原则:要保证用户请求的数据尽量少、请求数尽量少、路径尽量短、依赖尽量少,并且不要有单点。
简单来讲,秒杀的整体架构可以概括为:稳、准、快
-
稳:高可用
就是整个系统架构要满足高可用,流量符合预期时要稳定,超过预期时也同样不能掉链子,既要保证秒杀活动的顺利完成,还要保证秒杀商品顺利卖出去,这时最基本的前提
-
准:一致性
比如秒杀10台iphone12,就只能成交10台,多一台少一台都不行,一旦库存不对,平台就要承担损失,所以准就要求保证数据的一致性
-
快:高性能
系统的性能要足够高,否则如何支撑这么大的流量
从技术角度在进行一下分析
- 高性能:动静分离、热点发现与隔离、请求的削峰与分层过滤
- 一致性:确保数据的高一致性
- 高可用:在具体的实现过程中难免会出现一些考虑不到的情况,要保证系统的高可用和正确性,还需要设计一个planB来兜底,以便在最坏情况下仍然能够从容应对
2. 设计秒杀系统应该注意的5个架构原则
秒杀就是在同一个时刻有大量的请求争抢购买同一个商品并完成交易的过程,就是大量的并发读和并发写。
秒杀系统的本质就是一个满足大并发、高性能和高可用的分布式系统
架构的原则:“4要1不要”
-
数据要尽量少
首先指用户请求的数据能少就少。请求的数据包括上传给系统的数据和系统返回给用户的数据(通常就是网页)
为什么数据要尽量少,因为这些数据在网络上传输需要时间,其次是不管是请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做压缩和字符编码,这些都非常消耗CPU,所以减少传输的数据量可以显著减少CPU的使用。例如可以简化秒杀页面的大小,去掉不必要的页面装饰效果等
其次数据要尽量少还要求系统依赖的数据能少就少,包括系统完成某业务逻辑需要读取和保存的数据,这些数据一般是和后台服务以及数据打交道的。调用其他服务会涉及数据的序列化和反序列化,而这也是CPU执行比较大的影响,也会增加延时。而且数据库本身也容易成为一个瓶颈,所以和数据库打交道越少越好,数据越简单,越小越好。
-
请求数要尽量少
用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如这个页面依赖的CSS/Javascript、图片、以及Ajax请求等等都定义为额外请求,这些额外请求应该尽量少。因为浏览器每发出一个请求都会多少有一些消耗,建立连接需要做三次握手,有的时候页面依赖或者连接数限制,一些请求还需做串行加载。另外,如果不同请求的域名不一样的话,还涉及这些域名的DNS解析,可以会消耗更久。所以减少请求数据可以显著减少这些因素导致的资源消耗。
-
路径要尽量短
所谓路径就是用户发出请求到返回数据这个过程中,请求经过的中间节点数
通常这些节点可以表示为一个系统或者一个新的Socket连接,每经过一个节点,一般都会产生一个新的socket连接。
每增加一个连接都会增加新的不确定性,从概率上讲,假如一次请求经过5个节点,每个节点的可用性99.9%的话,那么整个请求的可用性是:99.9%的5次方,约等于99.5%
所以缩短请求路径不仅可以增加可用性,同样可以有效提升性能,并减少延时
要缩短访问路径有一种办法,就是将多个相互依赖的应用合并部署在一起,把远程过程调用变成JVM内部之间的方法调用
-
依赖要尽量的少
所谓依赖就是指完成一次用户请求必须依赖的系统或者服务,这里的依赖指的是强依赖,对于系统在运行中,不必要的信息和依赖就可以去掉
-
不要有单点
系统中的单点可以说是系统架构上的一个大忌,因为单点就意味着没有备份,风险不可控,我们设计分布式系统的最重要的原则就是消除单点
如何避免单点,就是将服务的状态避免和机器绑定,即把服务无状态化,这样服务就可以在机器中随意移动。
如何将服务和机器状态解耦,可以将服务配置动态化,配置到配置中心中动态获取,在服务启动的时候动态拉取下来。
3.如何做好动静分离
3.1. 动静分离介绍
在整个秒杀场景下,对于系统的要求其实就三个字:快、准、稳。
怎么才能快起来呢?第一是提高单次请求的效率,第二是减少没有必要的请求。动静分离其实就是瞄着这两个方向去的。
简单来说,动态数据和静态数据的主要区别就是看页面中的数据是否和URL、浏览者、时间、地域相关,以及是否含有Cookie等私密数据,比如说:
-
很多媒体网站,某一篇文章,无论谁访问,都是一样的结果。这就是一个典型的静态数据,但它是一个动态页面
-
如果现在访问淘宝页面,每个人看到的页面都不一样,页面中包含了很多根据访问者特征推荐的信息,这些个性化的数据就可以理解为动态数据了。
如何对静态数据做缓存,这里总结一下
- 应该将静态数据缓存到离用户最近的地方。静态数据就是那些相对不变的数据,因此我们可以把它缓存起来,常见的几种缓存方式:用户浏览器缓存、CDN上、服务端的Cache中
- 静态化改造就是要直接缓存HTTP连接
- 让谁来缓存静态数据也很重要
3.2. 如何做动静分离的改造
- URL唯一化:如果需要缓存整个HTTP连接。目前的改造方案是:先对其URL设备及运行进行检查
- 分离浏览者相关的因素:浏览者相关的因素包括已登录、登录身份等,这些相关因素我们可以单独拆分处理,通过动态请求来获取
- 分离时间因素
- 异步化的地域因素
- 去掉Cookie
上面是通过缓存的方式来处理静态数据,而动态内容的处理通过有两种方案ESI和CSI
- ESI方案(SSI):在web代理服务器上做动态内容请求,并将请求插入到静态用户中
- CSI方案:单独发起一个异步Javacript请求,向业务获取动态内容,这种方式在服务端新能盖好,当用在客户互动,这种方式服务性能更佳,但页面端可能会延时,体验稍差。
3.3. 动静分离的几种架构方案
1、实体机进行单机部署
将虚拟机修改为实体机,以增大Cache的容量,采用一致性Hash分组的方式来提升命中率,适当的增加多个相同的分组,来平衡访问热点和命中率的问题
实体机部署有这几个优点:
1、没有网络瓶颈,能使用大内存
2、既能提升命中率,又能减少Gzip压缩
3、减少Cache失效压力,因为采用定时失效方式
2、统一Cache层
所谓统一Cache层,就是将单机的Cache统一分离出来,形成一个单独的Cache集群,将Cache层单独拿出来统一管理可以减少运维成本,同时也方法接入其他静态化系统,还有一些优点如下:
1、单独一个Cache层,可以减少多个应用接入时使用Cache的成本。接入的应用只要维护自己的Java系统就好,不需要单独维护Cache,只关心如何使用即可
2、便于维护
3、可以共享内存
这种方式也会带来一些问题
1、Cache内部的交换网络会成为瓶颈
2、缓存服务器的网卡也会是瓶颈
3、机器少风险较大,挂掉一台就会影响很大的一部分缓存数据
3、上CDN
将缓存放入CDN中,我们需要考虑的问题
1、失效问题:如果一篇文章缓存后,发现需要修改,如何让全国的CDN节点在秒级进行失效
2、命中率问题:缓存命中的情况是CDN的首要问题
3、发布更新问题:频繁发布导致CDN上的数据无法及时更新
选择CDN发布节点,需要满足以下几个条件
1、靠近访问量比较集中的地区
2、离主站相对较远
3、节点到主站间网络比较好,而且稳定
4、节点容量比较大,不会占用其他CDN太多的资源
5、节点不要太多
4. 有针对性的处理好热点数据
如果系统中有大量的数据被用户频繁的访问,这些访问的数据中就一定有一些数据是被重复访问的,这些就是“热点数据”,基于这些热点数据进行的操作就是热点操作。
4.1. 什么是热点
热点分为:热点操作和热点数据,
热点操作又可以分为读请求和写请求,而读请求,一般都是无状态的,所有优化的空间比较大,而写请求的瓶颈一般都在存储层,优化的思路是根据CAP(C:一致性,A:可用性,P:分区容错性)理论做平衡。
热点数据比较好理解,就是用户热点请求对应的数据。而热点数据又分为“静态热点数据”和“动态热点数据”。
-
所谓“静态热点数据”就是能够提前预测的热点数据。
-
所谓“动态热点数据”,就是不能被提前预测到的。
4.2. 发现热点数据
-
发现静态热点数据:通过系统提前标识,通过浏览记录进行统计
- 发现动态热点数据:
- 构建一个异步的系统,可以收集交易链路上各个环节中的中间件产品的热点key
- 建立一个热点上报和可以按需订阅的热点服务的下发规范
- 将上游系统收集的热点数据发送到热点服务台,然后现有系统就会知道哪些商品会被频繁调用,然后做热点保护
- 打造热点发现系统需要注意的点
- 热点服务后台抓取热点数据日志最好采用异步方式。异步一方面能保证通用性,另一方面又不影响业务的主流程
- 热点服务发现和中间件自身的热点保护模块并存,每个中间件和应用还需要保护自己
- 热点发现要做到接近实时(3s内完成热点数据的发现),因为只有做到接近实时,动态发现才有意义,才能实时地对下游系统提供保护
4.3. 处理热点数据
处理热点数据通常的方法是:优化、限制、隔离
-
优化热点数据最有效的办法就是缓存热点数据。如果热点数据做了动静分离,可以长期缓存静态数据。但是,缓存热点数据更多的是临时缓存,由于缓存队列长度有限,可以采用LRU淘汰算法替换。
-
限制更多的是一种保护机制,限制的办法也有很多,将热点商品限制在一个请求队列里,防止因某些热点商品占用太多的服务器资源,而使其他请求始终得不到服务器的处理资源
-
隔离:秒杀系统设计的第一原则就是将这种热点数据隔离出来,不要让1%的请求影响到另外99%,隔离出来后也更加方便对这1%的请求做针对性的优化
到具体的秒杀业务层面,可以在以下几个层次实现隔离
1、业务隔离:可以通过业务提前获知热点数据,并对数据进行预热
2、系统隔离:系统隔离更多的是运行时隔离,可以通过分组部署的方式和另外99%的分开。秒杀也可以申请单独的域名,目前就是让请求落到不同的集群中
3、数据隔离:和秒杀业务相关的内容从缓存到数据库再到具体的数据流转都隔离出来,目前就是不影响现有系统的其他业务
5. 流量削峰如何处理
5.1. 为什么要削峰
- 让服务端处理变得更加平稳
- 可以节省服务器的资源成本
- 针对秒杀这一场景,削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求,它遵从“请求数量要尽量少”的原则
5.2. 削峰的实现方式
可以通过:排队、答题、分层过滤实现,这几种方式都是无损的实现方式(不会损失用户发出的请求)
1、排队
通过消息队列的方式将瞬时爆发的流量发送到消息队列中,消息队列的下游会进行数据的有序消费,不会将下游压垮,但如果流量峰值持续一段时间达到了消息队列的处理上限,例如消息积压达到了存储空间的上限,消息队列同样也会被压垮,这样虽然保护了下游系统,但和直接把请求丢弃没有多大的区别。
2、答题
主要实现分为以下部分
- 题库生成模块:重要的防止由机器算出结果,防止秒杀器来答题
- 题库的推送模块:为了防止答题作弊,保证用户每次答题的题目是唯一的
- 题目的图片生成模块:将题目生成图片的目的是为了防止机器直接答题
3、分层过滤
通过:CDN–>前台读系统–>后台写系统–>DB
- 大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的读取
- 经过第二层(即前台系统)时数据(包括强一致性的数据)尽量走Cache,过滤一些无效的请求;
- 再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,这样数据量和请求就进一步减少
- 最后在数据层完成数据的强一致性校验
分层过滤的核心思想是:在不同的层次尽可能地过滤掉无效请求,到“漏斗”最末端的才是有效请求。要达到这种效果,我们必须对数据做分层校验。
分层校验的基本原则
- 将动态请求的读数据缓存在web端,过滤掉无效的数据读操作;
- 对读数据不做强一致性校验,减少因为一致性校验产生瓶颈的问题;
- 对写数据进行基于时间的合理分片,过滤掉过期的失效请求;
- 对写请求做限流保护,将超出系统承载能力的请求过滤掉;
- 对写数据进行强一致性校验,只保留最后有效的数据。
6. 影响性能的因素有哪些
- RT:响应时间(一定时间内的响应时间),在sentinal中有这个限流规则设置
- QPS:每秒请求数,可以用来表示并发量
- TPS:每秒处理的事务数,软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程,客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数,也叫事务处理的吞吐量
QPS与TPS是相对的,QPS表示客户端的每秒
正常情况下:RT越短,一秒的请求数自然就越多,在单线程处理的情况下这两个是线性关系
在实际情况中,响应时间总是有一个限度的,不可能无限下降,这里就会和线程数相关联了
真正对性能有影响的是CPU的执行时间,如果减少CPU一半的执行时间,可以增加一倍的QPS
我们应该致力于减少CPU的执行时间
我们设置什么样的线程数是合理的,很多系统多线程的场景都有一个默认配置
线程数=2 x CPU核数 + 1
最佳实践公式:
线程数=[(线程等待时间 + 线程CPU时间) / 线程CPU时间] x CPU数量
要提升性能我们就要减少CPU的执行时间,另外就是要设置一个合理的并发线程数,通过这两方面来显著提升服务器的性能