限流
如果系统只有熔断机制,当流量激增的时候,就相当于被动的等待熔断机制的触发,此时就需要另外的手段来防止系统负载过高,限流就是一个很好的方案,要主动出击,防止服务挂掉。
为什么需要限流
- 熔断处理的方式不够优雅。熔断是等到系统过载之后才触发的,即先发生过载,等系统故障后才会介入,让系统恢复。这样的处理方式会导致系统的不必要抖动。
- 熔断机制是最后的底线。虽然熔断可以解决雪崩问题,但是它应该作为系统稳定性保障的 最后一道防线,正确使用熔断的思路应该是,在其他方法用尽 之后,如果过载问题依旧存在,这时熔断才会被动触发。
- 在快速失败的时候,需要能考虑调用方的重要程度。熔断是调用方依据响应结果自适应来触发的,在被调用方出现过载的时候,所有的调用方都将受到影响。但是不同接口的重要程度不一样,需要保证有些接口优先处理。
- 在多租户的情况下,不能让一个租户的问题影响到其他的租户,我们需要对每一个租户分配一定的配额,谁超过了就对谁进行限流,保证租户之间的隔离性。
如何实现限流
限流一般有固定的限流算法,有以下几种:
固定窗口和滑动窗口
固定窗口就是定义一个“固定”的统计周期,比如 10 秒、30 秒或者 1 分钟,然后在每个周 期里,统计当前周期中被接收到的请求数量,经过计数器累加后,如果超过设定的阈值就触发 限流,直到进入下一个周期后,计数器清零,流量接收再恢复正常状态,如下图所示:

存在的问题:
抗抖动性差。由于流量突增使请求超过预期,导致流量可能在一个统计周期的前 10 ms 内就达到了 100 次,给服务的处理能力造成一定压力,同时后面的 1990 ms 将会触发限流。
这个问题虽然可以通过减小统计周期来改善,但是因为统计周期变小,每个周期的阈值也会变 小,一个小的流量抖动就会导致限流的发生,所以系统的抗抖动能力就变得更差了。
如果上一个统计周期的流量集中在最后 10 ms ,而现在这个统计周期的流量集中在前 10 ms ,那么这 20 ms 的时间内会出现 200 次调用,这就超过了我们预期的 2 秒内不能超 过 100 次请求的目的了。这时候,我们就需要使用“滑动窗口”算法来改善这个问题了。
滑动窗口就是固定窗口的优化,它对固定窗口做了进一步切分,将统计周期的粒度切分 得更细,比如 1 分钟的固定窗口,切分为 60 个 1 秒的滑动窗口,然后统计的时间范围随着时 间的推移同步后移,如下图所示。

但是这里要注意一个问题,如果滑动窗口的统计窗口切分得过细,会增加系统性能和资源损耗 的压力。同时,滑动窗口和固定窗口一样面临抗抖动性差的问题。
漏桶
如下图所示,“漏桶”就像一个漏斗,进来的水量就像访问流量一样,而出去的水量 就像是我们的系统处理请求一样。当访问流量过大时,这个漏斗中就会积水,如果水太多了就会溢出。
该算法相对于滑动窗口和固定窗口做了两个改进点,第一,增加了一个桶来缓存请求,在流量突增的时候,可以先缓存起来,直到超过桶的容量才触发限流;第二,对出口的流量上限做了限制,使上游流量的抖动不会扩散到下游服务。
漏桶提供流量整形能力有一定的代价,超过漏桶流出速率的请求,需要先在漏桶中排队等待,其中流出速率是漏桶限流的防线,一般会设置得相对保守,可是这样就无法完全利用系 统的性能,就增加了请求的排队时间。
令牌桶
令牌桶算法的核心是固定“进口”速率,限流器在一个一定容量的桶内,按照一定的速率放入 Token ,然后在处理程序去处理请求的时候,需要拿到 Token 才能处理;如果拿不到,就进行限流。
因此,当大量的流量进入时,只要令牌的生成速度大于等于请求被处理的速度,那么此时系统处理能力就是极限的。
令牌桶算法相对于漏桶,虽然提高了系统的资源利用率,但是却放弃了一定的流量整形能力,也就是当请求流量突增的时候,上游流量的抖动可能会扩散到下游服务。
单节点限流
单节点限流比较简单,可以基于内存来做,需要注意两点:
限流机制作用的位置是客户端还是服务端,即选择客户端限流还是服务端限流。一般来说,熔断机制作用的位置是客户端,限流机制作用的位置更多是服务端,因为熔断更强调自适应,让作用点分散在客户端是没有问题的,而限流机制则更强调控制,它的作用点在服务端的控制能力会更强。
将作用点放置在服务端,会给服务端带来性能压力。如果将作用点放置在客户端,这就是一个天然的分布式模式,每一个调用方的客户端执行自己的限流逻辑,而将作用点放置在服务端时,服务端要执行所有请求的限流逻辑, 就需要更多的内存来缓存请求,以及更多的 CPU 来执行限流逻辑。
如果触发限流后,我们应该直接抛弃请求还是阻塞等待,即否决式限流和阻塞式限流。一般来说,如果我们可以控制流量产生的速率,那么阻塞式限流就是一个更好的选择,因为它既可以实现限流的目的,又不会抛弃请求;
如果我们不能控制流量产生的速率,那么阻塞式限流将会因为请求积压,出现大量系统资源占用的情况,很容易引发雪崩,这时否决式限流将是 更好的选择。
分布式限流
为了系统的高可用,一般每个服务都会有多个实例,所以在进行限流时,需要协调该服务的多个实例,进行统一限流。主要方案有以下几点。
1、进行集中限流。该方案可以借助一个外部存储,比如Redis,然后采用令牌桶算法。但是会带来问题,每次请求都需要先去Redis获取令牌,会导致Redis成为性能瓶颈,并且限流器故障会导致所有请求都被拒绝,而且每次请求都多了一次网络调用,增加时延。
2、将分布式限流进行本地化处理。限流器在获得一个服务限额的总阈值后, 将这个总阈值按一定的策略分配给服务的实例,每一个实例依据分配的阈值进行单节点限流。这里要考虑如果每个服务器的配置不一样,那么分配的流量就需要不同.
一个折中的方案:该方案建立在集中式限流的基础上,为了解决每次请求 都需要,通过网络访问限流器获取令牌的问题,客户端只有在令牌数不足时,才会通过限流器 获取令牌,并且一次获取一批令牌。即令牌由集中式限流器生成,但是具体的限流策略是在每个客户端本地处理。
参考
《深入浅出分布式技术原理》