您的当前位置:首页>新品 > 正文

高并发秒杀全网顶级教程:秒杀系统架构设计有哪些关键点?

来源:CSDN 时间:2023-03-03 11:04:12

高并发秒杀全网顶级教程

一、秒杀系统架构设计都有哪些关键点?

说实话,作为一名程序员,我的技术能力也在公司业务的快速增长过程中得到了 历练,并积累了一些大流量高并发网站架构设计和优化的经验,尤其是针对“秒 杀”这个场景。因为我确信,那个时候我们肯定是对系统做了足够多的极致优化, 才能扛住当时洪峰般的流量请求。


(相关资料图)

记得早期的时候,淘宝商品详情系统的 PV 还差不多是 1 亿的样子,但是到 2016 年差不多已经升至 50 亿了。尤其是 2012 年到 2014 年那个时间段,“秒 杀”活动特别流行,用户的参与热情一浪高过一浪,系统要面对的流量也是成倍 增长。

而每一次的秒杀活动对技术团队来说都是一次考验。现在想起来,那个时候我们 整个团队,无所畏惧,逐步迭代创新,然后解决一个个难题的过程,也是极具挑 战性和成就感的事情。

记得有一年,为了应对“双十一”,我们整个商品详情团队对系统做了很多优化, 我们自认为已经是整个公司最牛的系统了,性能也已经是“业界之巅”。

但是那年“双十一”的晚上,我们的系统还是遇到了瓶颈。当时老大就跑过来盯 着我们,问我们什么时候能够恢复,我们整个团队都承担着巨大的心理压力。

事后我们复盘宕机的原因,发现当时的秒杀流量远远超过了我们的预想,我们根 本没想到大家的参与热情能有那么高。于是我们按照这个增长率去预估下一年的 流量和服务器,粗算下来,我记得差不多要增加 2000 台服务器,简直不可思议。

怎么可能真正增加这么多机器,所以这也就倒逼我们必须找出一些特殊的手段来 优化系统。后面,经过一段时间的调研和分析,我们想到了把整个系统进行动静 分离改造的解决方案。

秒杀系统也差不多那个时候才从商品详情系统独立出来成为一个独立产品的。因 为我见证了秒杀系统的建设过程,所以也有颇多感慨。秒杀系统的迭代又是一个 升级打怪的过程,我们也都是遇到问题解决问题,逐一优化。

那么,如何才能更好地理解秒杀系统呢?我觉得作为一个程序员,你首先需要从 高维度出发,从整体上思考问题。在我看来,秒杀其实主要解决两个问题,一个 是并发读,一个是并发写。并发读的核心优化理念是尽量减少用户到服务端来 “读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。另外,我们还要针对秒杀系统做 一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况发生。

而从一个架构师的角度来看,要想打造并维护一个超大流量并发读写、高性能、 高可用的系统,在整个用户请求路径上从浏览器到服务端我们要遵循几个原则, 就是要保证用户请求的数据尽量少、请求数尽量少、路径尽量短、依赖尽量少, 并且不要有单点。这些关键点我会在后面的文章里重点讲解。

其实,秒杀的整体架构可以概括为“稳、准、快”几个关键字。

所谓“稳”,就是整个系统架构要满足高可用,流量符合预期时肯定要稳定,就 是超出预期时也同样不能掉链子,你要保证秒杀活动顺利完成,即秒杀商品顺利 地卖出去,这个是最基本的前提。

然后就是“准”,就是秒杀 10 台 iPhone,那就只能成交 10 台,多一台少一 台都不行。一旦库存不对,那平台就要承担损失,所以“准”就是要求保证数据 的一致性。

最后再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,否则你 怎么支撑这么大的流量呢?不光是服务端要做极致的性能优化,而且在整个请求 链路上都要做协同的优化,每个地方快一点,整个系统就完美了。

所以从技术角度上看“稳、准、快”,就对应了我们架构上的高可用、一致性和 高性能的要求,我们的专栏也将主要围绕这几个方面来展开,具体如下。

高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非 常关键。本专栏将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰 与分层过滤、服务端的极致优化这 4 个方面重点介绍。

一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量 的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存” “付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性, 其难度可想而知。因此,我将用一篇文章来专门讲解如何设计秒杀减库存方案。

高可用。 虽然我介绍了很多极致的优化思路,但现实中总难免出现一些 我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。专栏的最后,我将带 你思考可以从哪些环节来设计兜底方案。

最后,很幸运能在极客时间遇到你,希望这堂课能让你彻底理解大并发、高性能、 高可用秒杀系统的设计之道,并能够在思考解决类似问题时有更准确的思考和判 断。

二、设计秒杀系统时应该注意的 5 个架构原 则

说起秒杀,我想你肯定不陌生,这两年,从双十一购物到春节抢红包,再到 12306 抢火车票,“秒杀”的场景处处可见。简单来说,秒杀就是在同一个时刻有大量 的请求争抢购买同一个商品并完成交易的过程,用技术的行话来说就是大量的并 发读和并发写。

不管是哪一门语言,并发都是程序员们最为头疼的部分。同样,对于一个软件而 言也是这样,你可以很快增删改查做出一个秒杀系统,但是要让它支持高并发访 问就没那么容易了。比如说,如何让系统面对百万级的请求流量不出故障?如何 保证高并发情况下数据的一致性写?完全靠堆服务器来解决吗?这显然不是最 好的解决方案。

在我看来,秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系 统。今天,我们就来聊聊,如何在满足一个良好架构的分布式系统基础上,针对 秒杀这种业务做到极致的性能改进。

2.1 架构原则:“4 要 1 不要”

如果你是一个架构师,你首先要勾勒出一个轮廓,想一想如何构建一个超大流量 并发读写、高性能,以及高可用的系统,这其中有哪些要素需要考虑。我把这些 要素总结为“4 要 1 不要”。

2.1.1. 数据要尽量少

所谓“数据要尽量少”,首先是指用户请求的数据能少就少。请求的数据包括上 传给系统的数据和系统返回给用户的数据(通常就是网页)。

为啥“数据要尽量少”呢?因为首先这些数据在网络上传输需要时间,其次不管 是请求数据还是返回数据都需要服务器做处理,而服务器在写网络时通常都要做 压缩和字符编码,这些都非常消耗 CPU,所以减少传输的数据量可以显著减少 CPU 的使用。例如,我们可以简化秒杀页面的大小,去掉不必要的页面装修效果, 等等。

其次,“数据要尽量少”还要求系统依赖的数据能少就少,包括系统完成某些业 务逻辑需要读取和保存的数据,这些数据一般是和后台服务以及数据库打交道 的。调用其他服务会涉及数据的序列化和反序列化,而这也是 CPU 的一大杀手,同样也会增加延时。而且,数据库本身也容易成为一个瓶颈,所以和数据库打交 道越少越好,数据越简单、越小则越好。

2.1.2. 请求数要尽量少

用户请求的页面返回后,浏览器渲染这个页面还要包含其他的额外请求,比如说, 这个页面依赖的 CSS/JavaScript、图片,以及 Ajax 请求等等都定义为“额外 请求”,这些额外请求应该尽量少。因为浏览器每发出一个请求都多少会有一些 消耗,例如建立连接要做三次握手,有的时候有页面依赖或者连接数限制,一些 请求(例如 JavaScript)还需要串行加载等。另外,如果不同请求的域名不一 样的话,还涉及这些域名的 DNS 解析,可能会耗时更久。所以你要记住的是, 减少请求数可以显著减少以上这些因素导致的资源消耗。

例如,减少请求数最常用的一个实践就是合并 CSS 和 JavaScript 文件,把多 个 JavaScript 文件合并成一个文件,在 URL 中用逗号隔开 。这种方式在服 务端仍然是单个文件各自存放,只是服务端会有一个组件解析这个 URL,然后动 态把这些文件合并起来一起返回。

2.1.3. 路径要尽量短

所谓“路径”,就是用户发出请求到返回数据这个过程中,需求经过的中间的节 点数。

通常,这些节点可以表示为一个系统或者一个新的 Socket 连接(比如代理服务 器只是创建一个新的 Socket 连接来转发请求)。每经过一个节点,一般都会产 生一个新的 Socket 连接。

然而,每增加一个连接都会增加新的不确定性。从概率统计上来说,假如一次请 求经过 5 个节点,每个节点的可用性是 99.9% 的话,那么整个请求的可用性是: 99.9% 的 5 次方,约等于 99.5%。

所以缩短请求路径不仅可以增加可用性,同样可以有效提升性能(减少中间节点 可以减少数据的序列化与反序列化),并减少延时(可以减少网络传输耗时)。

要缩短访问路径有一种办法,就是多个相互强依赖的应用合并部署在一起,把远 程过程调用(RPC)变成 JVM 内部之间的方法调用。在《大型网站技术架构演进 与性能优化》一书中,我也有一章介绍了这种技术的详细实现。

2.1.4. 依赖要尽量少

所谓依赖,指的是要完成一次用户请求必须依赖的系统或者服务,这里的依赖指 的是强依赖。

举个例子,比如说你要展示秒杀页面,而这个页面必须强依赖商品信息、用户信 息,还有其他如优惠券、成交列表等这些对秒杀不是非要不可的信息(弱依赖), 这些弱依赖在紧急情况下就可以去掉。

要减少依赖,我们可以给系统进行分级,比如 0 级系统、1 级系统、2 级系统、 3 级系统,0 级系统如果是最重要的系统,那么 0 级系统强依赖的系统也同样 是最重要的系统,以此类推。

注意,0 级系统要尽量减少对 1 级系统的强依赖,防止重要的系统被不重要的 系统拖垮。例如支付系统是 0 级系统,而优惠券是 1 级系统的话,在极端情况 下可以把优惠券给降级,防止支付系统被优惠券这个 1 级系统给拖垮。

2.1.5. 不要有单点

系统中的单点可以说是系统架构上的一个大忌,因为单点意味着没有备份,风险 不可控,我们设计分布式系统最重要的原则就是“消除单点”。

那如何避免单点呢?我认为关键点是避免将服务的状态和机器绑定,即把服务无 状态化,这样服务就可以在机器中随意移动。

如何那把服务的状态和机器解耦呢?这里也有很多实现方式。例如把和机器相关 的配置动态化,这些参数可以通过配置中心来动态推送,在服务启动时动态拉取 下来,我们在这些配置中心设置一些规则来方便地改变这些映射关系。

应用无状态化是有效避免单点的一种方式,但是像存储服务本身很难无状态化, 因为数据要存储在磁盘上,本身就要和机器绑定,那么这种场景一般要通过冗余 多个备份的方式来解决单点问题。

前面介绍了这些设计上的一些原则,但是你有没有发现,我一直说的是“尽量” 而不是“绝对”?

我想你肯定会问是不是请求最少就一定最好,我的答案是“不一定”。我们曾经 把有些 CSS 内联进页面里,这样做可以减少依赖一个 CSS 的请求从而加快首页 的渲染,但是同样也增大了页面的大小,又不符合“数据要尽量少”的原则,这种情况下我们为了提升首屏的渲染速度,只把首屏的 HTML 依赖的 CSS 内联进 来,其他 CSS 仍然放到文件中作为依赖加载,尽量实现首屏的打开速度与整个 页面加载性能的平衡。

所以说,架构是一种平衡的艺术,而最好的架构一旦脱离了它所适应的场景, 一切都将是空谈。我希望你记住的是,这里所说的几点都只是一个个方向,你应 该尽量往这些方向上去努力,但也要考虑平衡其他因素。

2.2 不同场景下的不同架构案例

前面我说了一些架构上的原则,那么针对“秒杀”这个场景,怎样才是一个好的 架构呢?下面我以淘宝早期秒杀系统架构的演进为主线,来帮你梳理不同的请求 体量下,我认为的最佳秒杀系统架构。

如果你想快速搭建一个简单的秒杀系统,只需要把你的商品购买页面增加一个 “定时上架”功能,仅在秒杀开始时才让用户看到购买按钮,当商品的库存卖完 了也就结束了。这就是当时第一个版本的秒杀系统实现方式。

但随着请求量的加大(比如从 1w/s 到了 10w/s 的量级),这个简单的架构很 快就遇到了瓶颈,因此需要做架构改造来提升系统性能。这些架构改造包括:

1. 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化,例 如这个独立出来的系统就减少了店铺装修的功能,减少了页面的复杂度;

2. 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正 常的商品购买集群的机器负载;

3. 将热点数据(如库存数据)单独放到一个缓存系统中,以提高“读性能”;

4. 增加秒杀答题,防止有秒杀器抢单。 此时的系统架构变成了下图这个样子。最重要的就是,秒杀详情成为了一个独立 的新系统,另外核心的一些数据放到了缓存(Cache)中,其他的关联系统也都 以独立集群的方式进行部署。

然而这个架构仍然支持不了超过 100w/s 的请求量,所以为了进一步提升秒杀系 统的性能,我们又对架构做进一步升级,比如:

1. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只 需要点击抢宝按钮,借此把页面刷新的数据降到最少;

2. 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获 取数据,甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统 调用,而且能够避免压垮公共缓存集群。

3. 增加系统限流保护,防止最坏情况发生。 经过这些优化,系统架构变成了下图中的样子。在这里,我们对页面进行了进一 步的静态化,秒杀过程中不需要刷新整个页面,而只需要向服务端请求很少的动 态数据。而且,最关键的详情和交易系统都增加了本地缓存,来提前缓存秒杀商 品的信息,热点数据库也做了独立部署,等等。

从前面的几次升级来看,其实越到后面需要定制的地方越多,也就是越“不通用”。 例如,把秒杀商品缓存在每台机器的内存中,这种方式显然不适合太多的商品同 时进行秒杀的情况,因为单机的内存始终有限。所以要取得极致的性能,就要在 其他地方(比如,通用性、易用性、成本等方面)有所牺牲。

2.3.总结

回顾下前面的内容,首先介绍了构建大并发、高性能、高可用系统中几种通用的 优化思路,并抽象总结为“4 要 1 不要”原则,也就是:数据要尽量少、请求 数要尽量少、路径要尽量短、依赖要尽量少,以及不要有单点。当然,这几点是 你要努力的方向,具体操作时还是要密切结合实际的场景和具体条件来进行。

然后,给出了实际构建秒杀系统时,根据不同级别的流量,由简单到复杂打造的 几种系统架构,希望能供你参考。当然,这里面没有说具体的解决方案,比如缓 存用什么、页面静态化用什么,因为这些对于架构来说并不重要,作为架构师, 你应该时刻提醒自己主线是什么。

说了这么多,总体上我希望给你一个方向,就是想构建大并发、高性能、高可用 的系统应该从哪几个方向上去努力,然后在不同性能要求的情况下系统架构应该从哪几个方面去做取舍。同时你也要明白,越追求极致性能,系统定制开发就会 越多,同时系统的通用性也就会越差。

三、如何才能做好动静分离?有哪些方案可选?

上一章,介绍了秒杀系统在架构上要考虑的几个原则,也许有人就想说:“知易 行难,这些原则应该怎么应用到系统中呢?”别急,从这篇文章开始,我就会逐 一介绍秒杀系统的各个关键环节中涉及的关键技术。

这一章就先来讨论第一个关键点:数据的动静分离。不知道你之前听过这个解决 方案吗?不管你有没有听过,我都建议你先停下来思考动静分离的价值。如果你 的系统还没有开始应用动静分离的方案,那你也可以想想为什么没有,是之前没 有想到,还是说业务体量根本用不着?

不过我可以确信地说,如果你在一个业务飞速发展的公司里,并且你在深度参与 公司内类秒杀类系统的架构或者开发工作,那么你迟早会想到动静分离的方案。 为什么?很简单,秒杀的场景中,对于系统的要求其实就三个字:快、准、稳。

那怎么才能“快”起来呢?我觉得抽象起来讲,就只有两点,一点是提高单次请 求的效率,一点是减少没必要的请求。今天我们聊到的“动静分离”其实就是瞄 着这个大方向去的。

不知道你是否还记得,最早的秒杀系统其实是要刷新整体页面的,但后来秒杀的 时候,你只要点击“刷新抢宝”按钮就够了,这种变化的本质就是动静分离,分 离之后,客户端大幅度减少了请求的数据量。这不自然就“快”了吗?

3.1 何为动静数据

那到底什么才是动静分离呢?所谓“动静分离”,其实就是把用户请求的数据(如 HTML 页面)划分为“动态数据”和“静态数据”。 简单来说,“动态数据”和“静态数据”的主要区别就是看页面中输出的数据 是否和 URL、浏览者、时间、地域相关,以及是否含有 Cookie 等私密数据。 比如说:

1. 很多媒体类的网站,某一篇文章的内容不管是你访问还是我访问,它都是 一样的。所以它就是一个典型的静态数据,但是它是个动态页面。

2. 我们如果现在访问淘宝的首页,每个人看到的页面可能都是不一样的,淘 宝首页中包含了很多根据访问者特征推荐的信息,而这些个性化的数据就可 以理解为动态数据了。

这里再强调一下,我们所说的静态数据,不能仅仅理解为传统意义上完全存在磁 盘上的 HTML 页面,它也可能是经过 Java 系统产生的页面,但是它输出的页面 本身不包含上面所说的那些因素。也就是所谓“动态”还是“静态”,并不是说 数据本身是否动静,而是数据中是否含有和访问者相关的个性化数据。

还有一点要注意,就是页面中“不包含”,指的是“页面的 HTML 源码中不含有”, 这一点务必要清楚。

理解了静态数据和动态数据,我估计你很容易就能想明白“动静分离”这个方案 的来龙去脉了。分离了动静数据,我们就可以对分离出来的静态数据做缓存,有 了缓存之后,静态数据的“访问效率”自然就提高了。

那么,怎样对静态数据做缓存呢?我在这里总结了几个重点。

第一,你应该把静态数据缓存到离用户最近的地方。静态数据就是那些相对不会 变化的数据,因此我们可以把它们缓存起来。缓存到哪里呢?常见的就三种,用 户浏览器里、CDN 上或者在服务端的 Cache 中。你应该根据情况,把它们尽量 缓存到离用户最近的地方。

第二,静态化改造就是要直接缓存 HTTP 连接。相较于普通的数据缓存而言,你 肯定还听过系统的静态化改造。静态化改造是直接缓存 HTTP 连接而不是仅仅缓 存数据,如下图所示,Web 代理服务器根据请求 URL,直接取出对应的 HTTP 响 应头和响应体然后直接返回,这个响应过程简单得连 HTTP 协议都不用重新组 装,甚至连 HTTP 请求头也不需要解析。

第三,让谁来缓存静态数据也很重要。不同语言写的 Cache 软件处理缓存数据 的效率也各不相同。以 Java 为例,因为 Java 系统本身也有其弱点(比如不擅 长处理大量连接请求,每个连接消耗的内存较多,Servlet 容器解析 HTTP 协议 较慢),所以你可以不在 Java 层做缓存,而是直接在 Web 服务器层上做,这 样你就可以屏蔽 Java 语言层面的一些弱点;而相比起来,Web 服务器(如 Nginx、Apache、Varnish)也更擅长处理大并发的静态文件请求。

3.2 如何做动静分离的改造

理解了动静态数据的“why”和“what”,接下来我们就要看“how”了。我们如 何把动态页面改造成适合缓存的静态页面呢?其实也很简单,就是去除前面所说 的那几个影响因素,把它们单独分离出来,做动静分离。

下面,我以典型的商品详情系统为例来详细介绍。这里,你可以先打开京东或者 淘宝的商品详情页,看看这个页面里都有哪些动静数据。我们从以下 5 个方面 来分离出动态内容。

1. URL 唯一化。商品详情系统天然地就可以做到 URL 唯一化,比如每个商 品都由 ID 来标识,那么 http://item.xxx.com/item.htm?id=xxxx 就可以 作为唯一的 URL 标识。为啥要 URL 唯一呢?前面说了我们是要缓存整个 HTTP 连接,那么以什么作为 Key 呢?就以 URL 作为缓存的 Key,例如以 id=xxx 这个格式进行区分。

2. 分离浏览者相关的因素。浏览者相关的因素包括是否已登录,以及登录身 份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。

3. 分离时间因素。服务端输出的时间也通过动态请求获取。

4. 异步化地域因素。详情页面上与地域相关的因素做成异步方式获取,当然 你也可以通过动态请求方式获取,只是这里通过异步获取更合适。

5. 去掉 Cookie。服务端输出的页面包含的 Cookie 可以通过代码软件来删 除,如 Web 服务器 Varnish 可以通过 unset req.http.cookie 命令去掉 Cookie。注意,这里说的去掉 Cookie 并不是用户端收到的页面就不含 Cookie 了,而是说,在缓存的静态数据中不含有 Cookie。

分离出动态内容之后,如何组织这些内容页就变得非常关键了。这里我要提醒你 一点,因为这其中很多动态内容都会被页面中的其他模块用到,如判断该用户是 否已登录、用户 ID 是否匹配等,所以这个时候我们应该将这些信息 JSON 化(用 JSON 格式组织这些数据),以方便前端获取。

前面我们介绍里用缓存的方式来处理静态数据。而动态内容的处理通常有两种方 案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。

1. ESI 方案(或者 SSI):即在 Web 代理服务器上做动态内容请求,并将 请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种 方式对服务端性能有些影响,但是用户体验较好。

2. CSI 方案。即单独发起一个异步 JavaScript 请求,以向服务端获取动态 内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。

3.3 动静分离的几种架构方案

前面我们通过改造把静态数据和动态数据做了分离,那么如何在系统架构上进一 步对这些动态和静态数据重新组合,再完整地输出给用户呢? 这就涉及对用户请求路径进行合理的架构了。根据架构上的复杂度,有 3 种方 案可选:

1. 实体机单机部署;

2. 统一 Cache 层;

3. 上 CDN。

方案 1:实体机单机部署

这种方案是将虚拟机改为实体机,以增大 Cache 的容量,并且采用了一致性 Hash 分组的方式来提升命中率。这里将 Cache 分成若干组,是希望能达到命中 率和访问热点的平衡。Hash 分组越少,缓存的命中率肯定就会越高,但短板是 也会使单个商品集中在一个分组中,容易导致 Cache 被击穿,所以我们应该适 当增加多个相同的分组,来平衡访问热点和命中率的问题。

这里我给出了实体机单机部署方案的结构图,如下:

实体机单机部署有以下几个优点:

1. 没有网络瓶颈,而且能使用大内存;

2. 既能提升命中率,又能减少 Gzip 压缩;

3. 减少 Cache 失效压力,因为采用定时失效方式,例如只缓存 3 秒钟,过 期即自动失效。

这个方案中,虽然把通常只需要虚拟机或者容器运行的 Java 应用换成实体机, 优势很明显,它会增加单机的内存容量,但是一定程度上也造成了 CPU 的浪费, 因为单个的 Java 进程很难用完整个实体机的 CPU。

另外就是,一个实体机上部署了 Java 应用又作为 Cache 来使用,这造成了运 维上的高复杂度,所以这是一个折中的方案。如果你的公司里,没有更多的系统 有类似需求,那么这样做也比较合适,如果你们有多个业务系统都有静态化改造 的需求,那还是建议把 Cache 层单独抽出来公用比较合理,如下面的方案 2 所 示。

方案 2:统一 Cache 层

所谓统一 Cache 层,就是将单机的 Cache 统一分离出来,形成一个单独的 Cache 集群。统一 Cache 层是个更理想的可推广方案,该方案的结构图如下:

将 Cache 层单独拿出来统一管理可以减少运维成本,同时也方便接入其他静态 化系统。此外,它还有一些优点。

1. 单独一个 Cache 层,可以减少多个应用接入时使用 Cache 的成本。这样 接入的应用只要维护自己的 Java 系统就好,不需要单独维护 Cache,而只 关心如何使用即可。

2. 统一 Cache 的方案更易于维护,如后面加强监控、配置的自动化,只需 要一套解决方案就行,统一起来维护升级也比较方便。

3. 可以共享内存,最大化利用内存,不同系统之间的内存可以动态切换,从 而能够有效应对各种攻击。 这种方案虽然维护上更方便了,但是也带来了其他一些问题,比如缓存更加集中, 导致:

1. Cache 层内部交换网络成为瓶颈;

2. 缓存服务器的网卡也会是瓶颈;

3. 机器少风险较大,挂掉一台就会影响很大一部分缓存数据。

要解决上面这些问题,可以再对 Cache 做 Hash 分组,即一组 Cache 缓存的内 容相同,这样能够避免热点数据过度集中导致新的瓶颈产生。

方案 3:上 CDN

在将整个系统做动静分离后,我们自然会想到更进一步的方案,就是将 Cache 进 一步前移到 CDN 上,因为 CDN 离用户最近,效果会更好。

但是要想这么做,有以下几个问题需要解决。

1. 失效问题。前面我们也有提到过缓存时效的问题,不知道你有没有理解, 我再来解释一下。谈到静态数据时,我说过一个关键词叫“相对不变”,它 的言外之意是“可能会变化”。比如一篇文章,现在不变,但如果你发现个 错别字,是不是就会变化了?如果你的缓存时效很长,那用户端在很长一段 时间内看到的都是错的。所以,这个方案中也是,我们需要保证 CDN 可以在 秒级时间内,让分布在全国各地的 Cache 同时失效,这对 CDN 的失效系统 要求很高。

2. 命中率问题。Cache 最重要的一个衡量指标就是“高命中率”,不然 Cache 的存在就失去了意义。同样,如果将数据全部放到全国的 CDN 上,必然导致 Cache 分散,而 Cache 分散又会导致访问请求命中同一个 Cache 的可能性 降低,那么命中率就成为一个问题。

3. 发布更新问题。如果一个业务系统每周都有日常业务需要发布,那么发布 系统必须足够简洁高效,而且你还要考虑有问题时快速回滚和排查问题的简 便性。 从前面的分析来看,将商品详情系统放到全国的所有 CDN 节点上是不太现实的, 因为存在失效问题、命中率问题以及系统的发布更新问题。那么是否可以选择若 干个节点来尝试实施呢?答案是“可以”,但是这样的节点需要满足几个条件:

1. 靠近访问量比较集中的地区;

2. 离主站相对较远;

3. 节点到主站间的网络比较好,而且稳定;

4. 节点容量比较大,不会占用其他 CDN 太多的资源。

最后,还有一点也很重要,那就是:节点不要太多。

基于上面几个因素,选择 CDN 的二级 Cache 比较合适,因为二级 Cache 数量 偏少,容量也更大,让用户的请求先回源的 CDN 的二级 Cache 中,如果没命中 再回源站获取数据,部署方式如下图所示:

使用 CDN 的二级 Cache 作为缓存,可以达到和当前服务端静态化 Cache 类似 的命中率,因为节点数不多,Cache 不是很分散,访问量也比较集中,这样也就 解决了命中率问题,同时能够给用户最好的访问体验,是当前比较理想的一种 CDN 化方案。

除此之外,CDN 化部署方案还有以下几个特点:

1. 把整个页面缓存在用户浏览器中;

2. 如果强制刷新整个页面,也会请求 CDN;

3. 实际有效请求,只是用户对“刷新抢宝”按钮的点击。 这样就把 90% 的静态数据缓存在了用户端或者 CDN 上,当真正秒杀时,用户只 需要点击特殊的“刷新抢宝”按钮,而不需要刷新整个页面。这样一来,系统只 是向服务端请求很少的有效数据,而不需要重复请求大量的静态数据。

秒杀的动态数据和普通详情页面的动态数据相比更少,性能也提升了 3 倍以上。 所以“抢宝”这种设计思路,让我们不用刷新页面就能够很好地请求到服务端最 新的动态数据。

3.4 总结

这一章主要介绍了实现动静分离的几种思路,并由易到难给出了几种架构方案, 以及它们各自的优缺点。可以看到,不同的架构方案会引入不同的问题,比如我 们把缓存数据从 CDN 上移到用户的浏览器里,针对秒杀这个场景是没问题的, 但针对一般的商品可否也这样做呢?

你可能会问,存储在浏览器或 CDN 上,有多大区别?我的回答是:区别很大! 因为在 CDN 上,我们可以做主动失效,而在用户的浏览器里就更不可控,如果 用户不主动刷新的话,你很难主动地把消息推送给用户的浏览器。

另外,在什么地方把静态数据和动态数据合并并渲染出一个完整的页面也很关 键,假如在用户的浏览器里合并,那么服务端可以减少渲染整个页面的 CPU 消 耗。如果在服务端合并的话,就要考虑缓存的数据是否进行 Gzip 压缩了:如果 缓存 Gzip 压缩后的静态数据可以减少缓存的数据量,但是进行页面合并渲染时 就要先解压,然后再压缩完整的页面数据输出给用户;如果缓存未压缩的静态数 据,这样不用解压静态数据,但是会增加缓存容量。虽然这些都是细节问题,但 你在设计架构方案时都需要考虑清楚。

四、二八原则:有针对性地处理好系统的“热点数据”

4.1 为什么要关注热点

4.2 什么是“热点”

4.3 发现热点数据

4.4 处理热点数据

4.5 总结

五、流量削峰这事应该怎么做?

5.1 为什么要削峰

5.2 排队

5.3 答题

5.4 分层过滤

5.5 总结

六、影响性能的因素有哪些?又该如何提高系统的性能?

6.1 影响性能的因素

6.2 如何发现瓶颈

6.3 如何优化系统

6.3.1. 减少编码

6.3.2. 减少序列化

6.3.3. Java 极致优化

6.3.4. 并发读优化

6.4 总结

七、秒杀系统“减库存”设计的核心逻辑

7.1 减库存有哪几种方式

7.2 减库存可能存在的问题

7.3 大型秒杀中如何减库存?

7.4 秒杀减库存的极致优化

7.5 总结

八、如何设计兜底方案?

8.1 高可用建设应该从哪里着手

8.2 降级

8.3 限流

8.4 拒绝服务

8.5 总结

最后给大家分享Spring系列的学习笔记和面试题,包含spring面试题、spring cloud面试题、spring boot面试题、spring教程笔记、spring boot教程笔记、最新阿里巴巴开发手册(63页PDF总结)、2022年Java面试手册。一共整理了1184页PDF文档。私信博主(777)领取,祝大家更上一层楼!!!

标签:

最新新闻:

新闻放送
Top