如果你看过秒杀系统的流量监控图的话,你会发现它是一条直线,就在秒杀开始那一秒是一条很直很直的线,这是因为秒杀请求在时间上高度集中于某一特定的时间点。这样一来,就会导致一个特别高的流量峰值,它对资源的消耗是瞬时的。
但是对秒杀这个场景来说,最终能够抢到商品的人数是固定的,也就是说100人和10000人发起请求的结果都是一样的,并发度越高,无效请求也越多。
但是从业务上来说,秒杀活动是希望更多的人来参与的,也就是开始之前希望有更多的人来刷页面,但是真正开始下单时,秒杀请求并不是越多越好。因此我们可以设计一些规则,让并发的请求更多地延缓,而且我们甚至可以过滤掉一些无效请求。
为什么要削峰
为什么要削峰呢?或者说峰值会带来哪些坏处?
我们知道服务器的处理资源是恒定的,你用或者不用它的处理能力都是一样的,所以出现峰值的话,很容易导致忙到处理不过来,闲的时候却又没有什么要处理。但是由于要保证服务质量,我们的很多处理资源只能按照忙的时候来预估,而这会导致资源的一个浪费。
这就好比因为存在早高峰和晚高峰的问题,所以有了错峰限行的解决方案。
削峰的存在,一是可以让服务端处理变得更加平稳,二是可以节省服务器的资源成本。
针对秒杀这一场景,削峰从本质上来说就是更多地延缓用户请求的发出,以便减少和过滤掉一些无效请求,它遵从“请求数要尽量少”的原则。
今天,我就来介绍一下流量削峰的一些操作思路:排队、答题、分层过滤。
这几种方式都是无损(即不会损失用户的发出请求)的实现方案,当然还有些有损的实现方案,包括我们后面要介绍的关于稳定性的一些办法,比如限流和机器负载保护等一些强制措施也能达到削峰保护的目的,当然这都是不得已的一些措施,因此就不归类到这里了。
排队
要对流量进行削峰,最容易想到的解决方案就是用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。在这里,消息队列就像“水库”一样,拦蓄上游的洪水,削减进入下游河道的洪峰流量,从而达到减免洪水灾害的目的。
用消息队列来缓冲瞬时流量的方案,如下图所示:
图1 用消息队列来缓冲瞬时流量
但是,如果流量峰值持续一段时间达到了消息队列的处理上限,例如本机的消息积压达到了存储空间的上限,消息队列同样也会被压垮,这样虽然保护了下游的系统,但是和直接把请求丢弃也没多大的区别。就像遇到洪水爆发时,即使是有水库恐怕也无济于事。
除了消息队列,类似的排队方式还有很多,例如:
- 利用线程池加锁等待也是一种常用的排队方式;
- 先进先出、先进后出等常用的内存排队算法的实现方式;
- 把请求序列化到文件中,然后再顺序地读文件(例如基于MySQL binlog的同步机制)来恢复请求等方式。
可以看到,这些方式都有一个共同特征,就是把“一步的操作”变成“两步的操作”,其中增加的一步操作用来起到缓冲的作用。
说到这里你可能会说,这样一来增加了访问请求的路径啊,并不符合我们介绍的“4要1不要”原则。没错,的确看起来不太合理,但是如果不增加一个缓冲步骤,那么在一些场景下系统很可能会直接崩溃,所以最终还是需要你做出妥协和平衡。