提到秒杀,很多人都会觉得这是一件技术要求很高的事情,因为这涉及到超大访问量(可能瞬间千万倍的用户访问商品)、维护数据一致性(不能超卖)。
前者对性能有极高的要求,而后者又正好拉低了性能,本文谈谈秒杀的设计思路,并在最后给出秒杀设计的简单模型图。
秒杀的情景
生活中有很多秒杀的情景,例如商家促销,像一元抢茅台,五毛钱抢宝马(这儿只是一个例子)。
假如一百万人来抢十瓶茅台,这时候肯定不能多卖出,也就是不能被大于 10 的人数抢到,通常最后时间会有一个倒计时按钮,30,29,28,3,2,1,GO!之后跃跃欲试的人们开始抢。
这时候有以下问题需要被考虑:
- 第一是多个客户端的时间如何保持同步,也就是让大家看到时间是一致的,不能你显示 3,而我这还显示 30。
- 第二是如何保证有没有黄牛用机器人抢。
- 第三是如何确保后端服务器可以支撑住这巨大的流量。
- ......
秒杀解决思路
有了上面的情景以及引出来的问题,来看看秒杀方案的设计思路,我们服务器如何应对这一百万的TPS呢?
首先想到的是扩容,但这是不现实的,因为扩容需要很多很多机器,TPS 增加一万倍对物理服务器的性能要求远远不止一万倍。
另外对于一个商家来说,为了这一次促销活动购置服务器是不划算的,平时势必有众多的机器处于闲置状态。
没法扩容,那么也就意味着要使用其他方法,如果所有请求访问一台物理机器肯定不行,一百万的数据访问无论如何分库分表都无济于事,因为面对的每一条都是热点数据,所以要用到分布式架构的思路。
秒杀的技术方案
分布式,可以降低服务器的压力,下面开始讲述秒杀的设计思路。
方案一
很明显,要让一百万用户能够同时打开抢货的网页,势必要用要到 CDN。
CDN 主要作用有两个:
一方面是将一些不会改变的静态资源放到离客户端较近的边缘服务器上。
这样客户端请求数据的时候可以直接从边缘服务器获取,降低中心服务器的压力。
另外一方面可以把小服务部署到 CDN 结点上去,这样,当前端页面来问开没开始时,这个小服务除了告诉前端开没开始外,它还可以统计下有多少人在线。
每个小服务会把当前在线等待秒杀的人数每隔一段时间就回传给我们的数据中心,于是我们就知道全网总共在线的人数有多少。
假设,我们知道有大约 100 万的人在线等着抢,那么,在我们快要开始的时候,由数据中心向各个部署在 CDN 结点上的小服务上传递一个概率值,这个概率值为 CDN 节点人数权重乘以获奖概率,比如说是 e。
于是,当秒杀开始的时候,这 100 万用户都在点下单按钮,首先他们请求到的是 CDN 上的这些服务。
这些小服务按照 e 的量把用户放到后面的数据中心,也就是放过去人数∗e,剩下的都直接返回秒杀已结束。
方案二
利用我们分布式中限流、网关等知识,将请求层层筛选,降低最后连接到数据库的请求。
首先也是利用 CDN 将静态资源分发在边缘服务器上,当进行服务请求时,先进行鉴权,鉴权主要是筛选机器人等非人工抢购,根据实际经验,鉴权可以筛选很大一部分用户,例如是否登录。
当鉴权确定是真实有效的用户之后,通过负载均衡,也就是 LVS Keepalived 将请求分配到不同的 Nginx 上。
一般会建立 Nginx 集群,然后再通过网关集群,即使这样还是要增加一些限流措施。
如果到这一步还是有很多请求压到数据库势必撑不住,那么可以采取服务限流、服务降级等措施,进行削峰处理。
到这儿理论上流量就不高了,如果还是很高,后面就将热点数据放进缓存集群中进行预热,同时设置定时任务。
一方面关注数据库与缓存的一致性,另一方面关闭超时未支付的订单,当订单提交之后交给任务队列,生成订单、修改数据库、做好持久化工作。
架构图如下(可点击查看大图):
这就是整个“秒杀”的技术细节,是不是有点不敢相信?
与这种秒杀业务类似的还有 12306 抢票,这个也是瞬间高流量,但是上面提到的架构就不适合了,因为 12306 完全不知道用户来是要买哪张火车票的。
不知道这个信息,很不好过滤用户,而且用户在买票前需要有很多查询操作,然后在查询中选择自己的车票。也就意味着没法在开始就过滤用户。
12306 最好的应对方式,除了不要一次把所有的票放出来,而是分批在不同的时间段把票放出来。
这样可以让人们不要集中在一个时间点来抢票,做到人肉分流,可以降低一些并发度。
另外,12306 最好是用预售的方式,让大家把自己的购票先输入到系统中。
系统并不真正放票,而是把大家的需求都收集好,然后做整体统筹安排,该增加车次的增加车次,该加车厢的加车厢,这样可以确保大家都能走。实在不行,就抽签了。
总结
我们可以看到,解决秒杀这种特定业务场景,可以使用 CDN 的边缘结点来扛流量,然后过滤用户请求(限流用户请求),来保护数据中心的系统,这样才让整个秒杀得以顺利进行。
也可以像方案二那样逐层过滤请求,这种业务场景和双十一相同吗?如果像双 11 那样,想尽可能多地卖出商品,那么就不像秒杀了。
这是要尽可能多地收订单,但又不能超过库存,其中还有大量的银行支付,各大仓库的库存查询和分配,这些都是非常慢的操作。
为了保证一致性,还要能够扛得住像双 11 这样的大规模并发访问,那么,应该怎么做呢?
使用秒杀这样的解决方案基本上不太科学了。这个时候就需要认认真真地做高并发的架构和测试了。
需要各个系统把自己的性能调整上去,还要小心地做性能规划,更要把分布式的弹力设计做好。
最后是要不停地做性能测试,找到整个架构的系统瓶颈,然后不断地做水平扩展,以解决大规模的并发。
有些时候,我们总是在想数据中心的解决方案。其实,我们有时候也需要换一换思路,也许,在数据中心解决并不一定是最好的方式,放在边缘来解决可能会更好一些。
尤其是针对一些有地域特征的业务,比如像外卖、共享单车、打车这样的业务。
其实,把一些简单的业务逻辑放在边缘,比放在数据中心不但能够有更好的性能,还有更便宜的成本。
我觉得,随着请求量越来越大,数据也越来越多,数据中心是有点到瓶颈了,而需要边缘结点来帮忙了。而且,这个边缘化解决方案的趋势也会越来越有优势。