「面试高频」秒杀架构的设计套路,你值得拥有
![「面试高频」秒杀架构的设计套路,你值得拥有](https://static001.geekbang.org/infoq/81/81babede4c1a68cffbf5e5fc12d801ff.jpeg)
本篇文章我们开始我们来聊下秒杀架构的相关内容,秒杀架构可以说是综合性非常强的一个场景,而且面试官特别喜欢基于这个场景进行提问,因此有必要好好了解下。
在正式开始之前,我们先来回顾下前面几篇文章的内容。
在第五篇中我们聊了缓存,我们先把数据存放到缓存中,每次请求通过缓存读取数据,大大减少了数据库读请求的压力。第六篇文章里我们聊了写缓存,流量洪峰时,我们先将数据写入缓存中,再逐步搬运数据到数据库,大大较少了数据库写请求的压力。第七篇文章里我们聊了数据收集,利用消息队列我们可以把缓存中的数据搬运到数据库中。而这几篇文章中涉及的架构设计思路,本篇文章中我们都会用到。
一、业务场景七
有一个秒杀活动,该活动提供了 100 件特价商品(商品价格非常低)供用户于 10 月 10 日 22 点 10 分 0 秒正式开始秒杀。
当时,平台已经积累了几千万的用户量,预计数十万的用户对此特价商品感兴趣。按照秒杀活动的调性,特价商品一般会在 1-2 秒内被一抢而光,剩余时间涌进来的流量只能看到秒杀结束界面,因此我们预测秒杀开启那一瞬间会出现一个瞬间流量峰值。
这也是一场短暂性的活动,不能添加太多的服务器,也不能花太多的时间重构,说白了也就是需要以最小的技术代价搞定这次秒杀活动。
因此,我们这次秒杀架构的设计目标是以较小的改动保证秒杀时间的流量洪流不会冲垮服务器。
对于秒杀架构设计而言,其难点在于僧多粥少,因此在设计秒杀架构时,一般要遵循东西不能超卖、下单成功的订单数据不能丢失、服务器和数据库不能挂、尽量别让机器人抢走商品这 4 个原则。
那如何做到这 4 个原则呢?我们先从整体思路入手吧。
二、整体思路
其实,秒杀架构的设计方案就是一个不断过滤请求的过程。从系统架构层面来说,秒杀系统的分层设计思路如下图所示:
![](https://static001.geekbang.org/infoq/27/27b566e85e3388d63684f17ae6f07385.png)
在上图中可以发现,秒杀系统的架构设计目标是尽量在上层把用户的请求处理掉,不让其往下层游动,那具体如何实现呢?
由于整个秒杀系统涉及多个用户操作步骤,因此解决如何将请求拦截在系统上层这个问题时,我们需要结合实际业务流程,将用户的每个操作步骤考虑在内。
为了方便理解,我们通过一张图来描述秒杀系统的具体业务流程,如下图所示:
![](https://static001.geekbang.org/infoq/cd/cdbc00cc8b1f465dc9fa177deed879a9.png)
接下来,我们按照秒杀架构系统的业务流程来一步步说明如何将请求拦截在系统上游。
(一)浏览页面如何将请求拦截在上游?
在以往的秒杀系统架构经历中,曾出现过这么个幺蛾子:当时我们把系统的方方面面都考虑到了,检查完自我感觉棒棒哒,可是活动一上线,系统立马显示异常,一通检查后,我们发现所有服务器的性能指标都没问题,唯独出口带宽出问题了。(它被占满了)
结果大家也都知道,参与活动时页面出现严重卡顿,用户吐槽不断。
通过这次惨痛经历,我们设计思路进行了相关调整,后期对于静态资源能上 CDN 就上 CDN。如果涉及到 PC 网站,首先必须前后端分离,然后静态资源能上 CDN 就上 CDN。
看到这里,我们有必要先了解一下什么是 CDN。
比如我们平时访问的请求是https://static.resource.xx/1.jpg,这个地址指向就是自己的服务器,经过改造后,我们将 static.resouce.xx 这个域名解析交给 CDN 服务商。因 CDN 服务商在全国各地都有服务器,服务器中存放着我们想要的静态资源的缓存。CDN 收到这个域名后,首先会寻找一台响应最快的服务器,并指向这个服务器的 IP。(大体意思就是这样,如果大家对细节有兴趣,可以留言告诉我,我们用一篇文章来介绍。关注公众号:服务端技术精选留言就行)
因此,使用 CDN 的好处是不浪费自己服务器资源和带宽,且响应速度快。通过这种方式,我们可以把静态资源的压力拦截在系统分层的外面。
那如果是动态的请求该怎么办?有如下三种实现方式。
比如评论、商品属性详情、购买数等请求,平时我们都是通过 JS 后台动态调用。在这个场景中,我们可以把动态的数据与页面进行整合,比如把每个秒杀商品的详情页面变成静态页面,然后再放入 CDN。如果嫌改造太大,我们也可以把它放在 Redis 缓存中,不过我更倾向于 CDN。
判断服务器时间开启秒杀标识:一般页面中都有一个 JS,它通过访问服务器获取服务器时间,然后根据时间开启秒杀下单的按钮,即判断秒杀开始时,我们会将下单按钮设置为可以购买。针对获取服务器时间的这个请求,我们把它放在静态资源或负载均衡那层即可,这样用户请求就不会进入系统下游。
判断秒杀结果:我们的做法是将秒杀结束的标识放在 cookie 中,如果 cookie 中没有结束标识,请求就会进入后台服务器,后台服务器判断本地内存没有结束标识,就会进入缓存中,要是缓存中也没有结束标识,那就说明秒杀没结束。
总的来说,对于浏览页面的用户行为,我们需要把用户请求尽量拦截在 CDN、静态资源或负载均衡侧,实在不行可以拦截在缓存中。
(二)下单页面如何将请求拦截在上游?
用户进入下单页时,主要分为 2 个操作动作:进入下单页、订单提交。下面我们来聊下如何在这两个环节中将请求拦截在系统上游。
(1)进入下单页
为了防止别人通过爬虫抓取下单页面信息,从而给服务器增加压力,因此我们需要在下单页中做以下 2 层防护,以防止(恶意)请求重复提交。
页面 URL 后台动态获取:按照正常的活动设计流程,用户只有在秒杀活动开启后才可进入下单页,但难免有同行在活动开启前直接获取下单页的 URL 并不断刷新,这样恶意请求就跑到了后台服务器中。虽然后台服务器也可以拦截恶意请求,但是徒增了不少压力。此时我们主要使用一个诡异的 URL 进行处理。(我们不把它放在静态页面中,而是通过后台动态获取。)前面我们介绍了 JS 可以用来判断秒杀开始时间,秒杀时间一到,它便可以通过另一个请求获取这个 URL。
用户点击下单页的购买按钮直接 disable,因此我们还需要防止用户不断点击购买按钮。
(2)订单提交
秒杀系统架构方案的重心是订单提交,因为订单提交这个步骤最复杂,其他步骤仅是页面展示的逻辑,针对高并发问题使用缓存或者 CDN 进行处理难度不大。
因此,在订单提交环节,我们要想尽一切办法在系统各个分层中把一些不必要的请求过滤掉。
网关层面过滤请求
对系统而言,如果我们可以在网关层面拦截掉用户请求,可以说这个方案的性价比很高。要是能在这层过滤 95%以上的请求,整个系统也就很稳定。
那在网关层面如何实现请求过滤呢?这里我分享三种方式。
限定每个用户访问频率:比如每 5 秒下单 1 次。
限定每个 IP 访问频率:这种方式我们担心有些人通过机器人自动下单,因此错杀真实用户。
把一个时间段内的请求拦截掉一个百分比,或者只允许特定数量的请求进入后台服务器。(这里我们可以使用限流的漏桶或令牌算法,在后面的文章中再详细展开)
后台服务器过滤请求
请求进入后台服务器后,我们的目标已经不是如何过滤请求了,而是如何保证特价商品不超卖,以及如何保证特价商品订单数据的准确性。
那具体如何实现呢?我们可以通过以下三种方式。
商品库存放入缓存中:如果每个请求都前往数据库查询商品库存,数据库肯定扛不住,因此我们需要把库存存放在缓存中,这样每次用户下单前,先使用 decr 扣减库存,判断返回值。如果 Redis 的库存扣减后<0,说明秒杀失败,库存 incr 回去;如果 Redis 的库存扣减后>=0,说明秒杀成功,开始创建订单。
订单写入缓存中:在第六篇写缓存时,我们提及过 1 个方案,即订单数据先不放入数据库,而是先放到缓存中,然后每隔一段时间(100ms)批量插入订单。我们知道用户下单后,首先进入一个等待页面,然后这个页面向后台定时轮询订单数据。轮询订单数据的过程中,后台先在 Redis 中查询订单数据,查不到说明已经落库,再去数据库查询订单数据,查到后直接返回给用户,用户收到消息通知后可以直接进入付款页面支付了。在数据库查询订单数据时,查不到说明秒杀失败。(理论上不会查不到,你要是一直查不到记得抛个异常后续跟踪处理下。)
订单批量落库:我们需要定期将订单批量落库,且在订单落库时扣减数据库中的库存。
以上就是订单提交操作的架构设计,不难看出我们主要是在网关层和后台服务器进行相关设计。
(三)付款页如何将请求拦截在上游?
在付款页面,我们基本不需要再过滤用户请求了。在这个环节,我们除了保障数据的一致性,还需要注意一个要点:如果业务逻辑上出现一个订单未及时付款而被取消,记得把数据库及 Redis 的库存加回去。
三、整体服务器架构
我们再来回顾下之前的秒杀系统架构的分层设计思路,这也是秒杀系统的整体服务器架构方案。
![](https://static001.geekbang.org/infoq/6d/6dbed533878cf4a529f42fbd33e61edf.png)
为了保障秒杀系统的高可用,在整体服务器架构中,我们需要保证上图中所有的层级都是高可用。因此,静态资源服务器、网关、后台服务器均需配置负载均衡,而缓存 Redis 和数据库均需要配置集群模式。
整体服务器架构中还有一个重要组成部分——MQ,因为这次的秒杀架构方案中不涉及它的设计逻辑,所以我们并未在上面的分层中提及它。不过,服务间触发通知时,我们就需要使用它了,因此我们也需要保证它是高可用的。(这里我们要把主从、分片、failover 机制都考虑进去。)
四、总结
说到这里,秒杀架构的注意事项就说完了。因很多注意点在第五、六、七章都介绍过,所以本文中的内容就比较简练。
下表中整理了一份秒杀架构 check list,在你设计秒杀系统时,这部分知识对你可能有用。
流程事项浏览页面静态资源放 CDN 浏览页面秒杀期间一些动态数据请求放进静态页面浏览页面秒杀开始的时间获取依赖服务端浏览页面秒杀结束的标识放在各个地方下单下单 URL 动态后台获取下单购买按钮点击完置灰下单网关从三个方面过滤请求:1、用户访问频率。2、IP 访问频率。3、整体流量控制。下单库存放 Redis,每次判断缓存防止超卖。下单订单先放缓存再批量落库。付款订单取消记得加回数据库和 Redis 的库存。服务器架构静态资源服务器负载均衡。服务器架构网关负载均衡服务器架构后台服务负载均衡服务器架构 Redis 集群服务器架构 MQ 集群服务器架构数据库集群
在本篇中,还有两个点我们没有说到。
网关层的限流。
假设后台某服务因秒杀宕机了,如何避免其他服务雪崩。
下一篇文章中,我们开始从最简单的服务管理聊起,说说微服务相关知识,欢迎关注个人微信公众号:服务端技术精选!!!
评论