对于一个分布式系统而言,如何保证系统的稳定可靠,永远都是头等大事。缓存、限流和降级是最有效也是我们最常用的手段。
今天我们就一起来看看分布式系统是如何进行限流的。
为什么要限流
原因很简单,资源是有限的,我们的系统的接待能力也是有限的,对于那些已经超出系统接待能力的请求我们应该尽可能早的识别出来并让其等待或拒绝这些请求。
当大流量进入系统而我们又不进行限流,那么处理请求能力最差的一个子系统将会最先宕机,进而导致依赖这个子系统的其它系统也跟着宕机,最终导致整个系统全面瘫痪,这就是系统雪崩效应。
限流算法
限流的前提是我们能够准确的计算出过去一段时间的请求数量,然后根据系统负载能力来判断接下来的请求是否放行。
常用的限流算法可以分为两大类:计数法和桶算法。
其中计数法又可以分为计数器和滑动窗口计数法,桶算法则分为漏桶法和令牌桶法。
接下来我们就深入的分析一下这些限流算法的特点。
计数器
这种限流算法是最为简单直接的了。直接记录一下当前周期内的请求个数,如果请求个数超出了阈值,那么就限制请求,如果没有超出,就放行。
如下图所示:
计数器法虽然实现上非常简单,也很容易理解,但是它的缺点也是非常明显的。我们假设一种情况:
系统线路的 QPS 为 100,第一秒有 90 个请求,并且所有的请求都在最后 100ms 进入,这个时候请求没有达到阈值,是不会限流的。
紧接着第二秒也有 90 个请求,不过全部集中在前 100ms 进入,这个时候也没有达到阈值,也不会限流。
然而如果我们全局分析,会发现在短短的 200ms 内进入了 180 个请求,这显然是远远超过了限流阈值 100 的。
具体情况如下图所示:
显然计数器法在要求比较高的场景下是不适用的。
滑动窗口法
滑动窗口法是在计数器法的基础上演进而来的,也是采用计数的方式来统计过去一段时间的请求数。
与计数器法不一样的地方是:滑动窗口计数会把计数窗口进行分割,比如分割成两份、10 份等,分割的越小,精度越高。
如下图所示:
上图展示了把统计窗口均分为 10 等分的情况,假设统计窗口为 1秒,那么每一小格代表的就是 100ms 的请求计数,最近 1 秒中的请求总数就等于最近 10 小格的统计数之和。
每过去 100ms,统计窗口就向右滑动一小格(这就是滑动窗口法的由来),最新的数据记录在最右边位置,最左一格的数据将会被丢弃(具体实现上会有所差异)。
其实滑动窗口法并没有完全消除计数器法中遇到的问题,它只是减小了影响。
假设限流 QPS 大小为 X,窗口均分为 N 份,那么理论上可以达到的峰值 QPS为X * (N + 1) / N,它显然是大于 X 的。
不过我们多均分几份以后,影响就会大大减少。
漏桶法
漏桶法非常的简单,也非常的形象。我们可以把整个系统看成一个水桶,进来的请求理解为往桶里注入水,处理请求就是桶中的的流出。
漏桶法就是不管注入水(请求进入)的快慢如何,我只按照恒定的流水出水(处理请求)。
固定线程个数的线程池就是我们平时接触的比较多的漏桶法限流的例子,这种情况中不管需要处理的任务有多少,线程池最多只会运行固定个数的任务,其余的任务要么被拒绝要么等待。
令牌桶算法
令牌桶算法就是系统会安装固定的速率往桶中添加令牌,请求的时候先到桶里拿一个令牌,如果能够拿到令牌就表示可以进行请求处理,如果桶里没有令牌了,就表明需要限流了。
常用限流库
1、Google 的 Guava 工具集中有一个 RateLimiter 限流器,在令牌桶的算法基础上进行改良实现的。
2、阿里开源的 Sentinel 也具有限流的功能,采用的是滑动窗口算法进行限流。