秒杀架构:设计秒杀架构必知必会的那些事
08 讲我们开始讲解秒杀架构的相关内容,秒杀架构可以说是综合性非常强的一个场景,而且面试官特别喜欢基于这个场景进行提问,因此你有必要好好学习下哦。
在正式内容讲解之前,我们先来回顾下前面几个课时的内容。
05 讲我们讲了读缓存,我们先把数据存放在缓存中,每次请求通过缓存读取数据,大大减少了数据库读请求压力。06 讲我们讲了写缓存,流量洪峰时,我们先将数据写入缓存中,再逐步搬运数据到数据库,大大减少了数据库写请求压力。07 讲我们讲了数据收集,利用消息队列我们可以把缓存中的数据搬运到数据库中。而这 3 节课中涉及的架构设计思路,本文都会使用到。
业务场景(架构经历七)
我们先来看一个实际的业务场景。
之前,我们公司策划了一场秒杀活动,该活动提供了 100 件特价商品(商品价格非常低)供用户于 10 月 10 日 22 点 10 分 0 秒正式参与秒杀。
当时,平台已经积累了几千万的用户量,预计数十万的用户对此特价商品感兴趣。按照秒杀活动的调性,特价商品一般会在 1-2 秒内被一抢而光,剩余时间涌进来的流量只能看到秒杀结束界面,因此我们预测秒杀开启那一瞬间会出现一个瞬间流量峰值。
这也是一场短暂性的活动,领导也是要求我们别加太多服务器,也别花太多时间重构架构,说白了就是需要我们以最小的技术代价搞定这次秒杀活动。
因此,我们这次秒杀架构的设计目标是以较小的改动保证秒杀时间的流量洪流不会冲垮服务器。
对于秒杀架构设计而言,其难点在于僧多粥少,因此设计秒杀架构时,我们一般需要遵循东西不能超卖、下单成功的订单数据不能丢失、服务器和数据库不能挂、尽量别让机器人抢走商品这 4 个原则。
那我们如何做到这 4 个原则呢?我们先从整体思路入手吧。
整体思路
其实,秒杀架构的设计方案就是一个不断过滤请求的过程。 从系统架构层面来说,秒杀系统的分层设计思路如下图所示:

在上图中我们发现,秒杀系统的架构设计目标是尽量在上层把用户的请求处理掉,不让其往下层游动,那具体如何实现呢?
由于整个秒杀系统涉及多个用户操作步骤,因此解决如何将请求拦截在系统上层这个问题时,我们需要结合实际业务流程,将用户的每个操作步骤考虑在内。
为了方便你理解,我们通过一张图来描述秒杀系统的具体业务流程,如下图所示:

接下来,我们按照秒杀架构系统的业务流程一步步讲解如何将请求拦截在系统上游。
(一)浏览页面如何将请求拦截在上游?
在我以往的秒杀系统架构经历中,曾出现过这么一个幺蛾子:当时我把系统的方方面面都考虑到了,检查完自我感觉棒棒哒,可是活动一上线,系统立马显示异常,一通检查后,我们发现所有服务器的性能指标都没问题,唯独出口带宽出问题了。(它被占满了。)
结果你们也知道,参与活动时页面出现严重卡顿,用户吐槽不断。
通过这次惨痛经历,我将设计思路进行了相关调整,后期对于静态资源能上 CDN 就上 CDN。如果涉及 PC 网站,首先必须前后端分离,然后静态资源能上 CDN 就上 CDN。
学到这,我们有必要先解释一下什么是 CDN。
比如我们平时访问的请求是 https://static.weimu.xx/1.jpg,这个地址指向自己的服务器,经过改造后,我们将 static.weimu.xx 这个域名解析交给 CDN 服务商。因 CDN 服务商在全国各地都有服务器,服务器中存放着我们想要的静态资源的缓存。CDN 收到这个域名后,首先会寻找一台响应最快的服务器,并指向这个服务器的 IP。(大体意思是这样,如果你对细节有兴趣,自己深研哈。)
因此,使用 CDN 的好处是不浪费自己的服务器资源和带宽,且响应速度快。通过这种方式,我们可以把静态资源的压力拦截在系统分层的外面。
那如果是动态的请求该怎么办?有如下 3 种实现方式。
比如评论、商品属性详情、购买数等请求,平时我们都是通过 JS 后台动态调用。在这个场景中,我们可以把动态的数据与页面进行整合,比如把每个秒杀商品的详情页面变成静态页面,然后再放入 CDN。如果嫌改造太大,我们也可以把它放在 Redis 缓存中,不过我更倾向于 CDN。
判断服务器时间开启秒杀的标识:一般页面中都有一个 JS,它通过访问服务器获取服务器时间,然后根据时间开启秒杀下单的按钮,即判断秒杀开始时,我们会将下单按钮设置为可以购买。针对获取服务器时间的这个请求,我们把它放在静态资源或负载均衡那层即可,这样用户请求就不会进入系统下游。
判断秒杀结束:我们的做法是将秒杀结束的标识放在 cookie 中,如果 cookie 中没有结束标识,请求就会进入后台服务器,后台服务器判断本地内存没有结束标识,就会进入缓存中,要是缓存中也没有结束标识,那就说明秒杀没结束。
总的来说,对于浏览页面的用户行为,我们需要把用户请求尽量拦截在 CDN、静态资源或负载均衡侧,实在不行可以拦截在缓存中。
(二)下单页面如何将请求拦截在上游?
用户进入下单页时,主要分为 2 个操作动作:进入下单页、订单提交。下面我们讲解如何在这两个环节中将请求拦截在系统上游。
(1)进入下单页
为了防止别人通过爬虫抓取下单页页面信息,从而给服务器增加压力,因此我们需要在下单页中做以下 2 层防护,以此防止(恶意)请求重复提交。
页面 URL 后台动态获取:按照正常的活动设计流程,用户只有在秒杀活动开启后才可进入下单页,但难免有同行在活动开启前直接获取下单页的 URL 并不断刷新,这样恶意请求就跑到了后台服务器中。虽然后台服务器也可以拦截恶意请求,但是徒增了不少压力。此时我们主要使用一个诡异的 URL 进行处理。(我们不把它放在静态页面中,而是通过后台动态获取。)前面我们介绍了 JS 可以用来判断秒杀开始时间 ,秒杀时间一到,它便可以通过另一个请求获取这个 URL。
用户点击下单页的购买按钮直接 disable,因此我们还需要防止用户不断点击购买按钮。
(2)订单提交
秒杀系统架构方案的重心是订单提交,因为订单提交这个步骤的逻辑最复杂,其他步骤仅是页面展示的逻辑,针对高并发问题使用缓存或者 CDN 进行处理难度不大。
因此,在订单提交环节,我们要想尽一切办法在系统各个分层中把一些不必要的请求过滤掉。
网关层面过滤请求
对系统而言,如果我们可以在网关层面拦截掉用户请求,可以说这个方案的性价比很高。要是能在这层过滤 95% 以上的请求,整个系统也就很稳定。
那在网关层面如何实现请求过滤呢?这里我分享三种方式。
限定每个用户访问频率:比如每 5 秒下单 1 次。
限定每个 IP 访问频率:这种方式我们担心有些人通过机器人自动下单,因此错杀真实用户。
把一个时间段内的请求拦截掉一个百分比,或者只允许特定数量的请求进入后台服务器。(这里我们可以使用限流的漏桶或令牌桶算法,在 12 讲中再详细展开。)
后台服务器过滤请求
请求进入后台服务器后,我们的目标已经不是如何过滤请求了,而是如何保证特价商品不超卖,以及如何保证特价商品订单数据的准确性。
那具体如何实现呢?我们可以通过以下三种方式。
商品库存放入缓存 Redis 中: 如果每个请求都前往数据库查询商品库存,数据库肯定扛不住,因此我们需要把库存存放在缓存中,这样每次用户下单前,先使用 decr 扣减库存,判断返回值。如果 Redis 的库存扣减后 <0,说明秒杀失败,库存 incr 回去;如果 Redis 的库存扣减后 >=0,说明秒杀成功,开始创建订单。
订单写入缓存中: 在 06 讲介绍写缓存时,我们提及过 1 个方案,即订单数据先不放入数据库,而是先放到缓存中,然后每隔一段时间(比如 100ms )批量插入订单。我们知道用户下单后,首先进入一个等待页面,然后这个页面向后台定时轮询订单数据。轮询订单数据的过程中,后台先在 Redis 中查询订单数据,查不到说明已经落库,再去数据库查询订单数据,查到后直接返回给用户,用户收到消息通知后可以直接进入付款页面支付了。在数据库查询订单数据时,查不到说明秒杀失败。(理论上不会查不到,你要是一直查不到记得抛个异常后续跟踪处理下。)
订单批量落库: 我们需要定期将订单批量落库,且在订单落库的时扣减数据库中的库存。
以上就是订单提交操作的架构设计,不难看出我们主要是在网关层和后台服务器进行相关设计。
(三)付款页如何将请求拦截在上游?
在付款页面,我们基本不需要再过滤用户请求了。在这个环节,我们除了保障数据的一致性,还需要注意一个要点:如果业务逻辑上出现一个订单未及时付款而被取消,记得把数据库及 Redis 的库存加回去。
整体服务器架构
我们再来回顾下之前的秒杀系统架构的分层设计思路,这也是秒杀系统的整体服务器架构方案。

为了保障秒杀系统的高可用性,在整体服务器架构中,我们需要保证上图中所有的层级都是高可用。因此,静态资源服务器、网关、后台服务器均需要配置负载均衡,而缓存 Redis 和数据库均需要配置集群模式。
整体服务器架构中还有一个重要组成部分——MQ,因这次的秒杀架构方案中不涉及它的设计逻辑,所以我们并未在上面的分层中提及它。不过,服务间触发通知时,我们就需要使用它了,因此我们也需要保证它是高可用的。(这里我们要把主从、分片、failover机制都考虑进去。)
总结
讲到这,秒杀架构的注意事项就说完了。因很多注意点在 05、06、07 讲一一介绍过,所以这次讲解的内容就比较简练。
下表中我整理了一份秒杀架构 check list,在你设计秒杀系统时,这部分知识对你大有裨益哦。

08 讲中,还有 2 个要点我们没有讲解。
网关层的限流。(这点会在 12 讲中详细展开。)
假设后台某服务因秒杀宕机了,如何避免其他服务雪崩。(这点会在 11 讲中详细展开。)
这节课中讲解的方案,肯定还存在一些遗漏的问题没有考虑,如果你有更好的方案 另外,喜欢如果你喜欢这些内容,欢迎与更多好友分享哦。
下一模块,我们将从最简单的服务管理说起,由简至深,逐层深入学习微服务的相关知识,敬请期待哦~