我们常见的限流算法有四种:计数器(固定窗口)算法、滑动窗口算法、漏桶算法、令牌桶算法。

为什么要限流

资源是有限的,我们的系统的处理能力也是有限的,对于那些已经超出系统处理能力的请求我们应该尽可能早的识别出来并让其等待或拒绝这些请求。

如果当大流量进入系统的时候不进行限流,那么将超出系统的负载,这种情况会导致服务异常、宕机等情况的出现。

常见的四种限流算法

1.计数器算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。

比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多。

如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:

通过上图我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。

我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的系统。

导致这个问题出现的原因是我们统计的精度太低。

所以就有了下面的算法滑动窗口算法。

2.滑动窗口

滑动窗口,又称rolling window。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。

下面这张图,很好地解释了滑动窗口算法:

在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决之前的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触发了限流。

我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

所以,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

 

3.漏桶算法

漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。

 

在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

 

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

 

漏桶算法有以下特点:

  • 漏桶具有固定容量,出水速率是固定常量(流出请求)

  • 如果桶是空的,则不需流出水滴

  • 可以以任意速率流入水滴到漏桶(流入请求)

  • 如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)

 

在某些情况下,漏桶算法不能够有效地使用网络资源。因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使某一个单独的流突发到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。

 

4.令牌桶算法

令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。

典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。

大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。

 

传送到令牌桶的数据包需要消耗令牌。不同大小的数据包,消耗的令牌数量不一样。

 

令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量。而如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。

令牌桶算法的基本过程如下:
 

  • 假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中

  • 假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃

  • 当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络

  • 如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外

  • 算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r

漏桶算法和令牌桶算法的区别

  限流策略 限流策略
漏桶算法 漏桶是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是 1 的速率流出,而不能一次是 1 ,下次又是 2 ),从而平滑突发流入速率
令牌桶算法 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿 3 个令牌, 4 个令牌),并允许一定程度突发流量

 

漏桶算法与令牌桶算法在表面看起来类似,很容易将两者混淆。但事实上,这两者具有截然不同的特性,且为不同的目的而使用。漏桶算法与令牌桶算法的区别在于:

  • 漏桶算法能够强行限制数据的传输速率。

  • 令牌桶算法能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。

 

    在某些情况下,漏桶算法不能够有效地使用网络资源。因为漏桶的漏出速率是固定的,所以即使网络中没有发生拥塞,漏桶算法也不能使某一个单独的数据流达到端口速率。因此,漏桶算法对于存在突发特性的流量来说缺乏效率。而令牌桶算法则能够满足这些具有突发特性的流量。通常,漏桶算法与令牌桶算法结合起来为网络流量提供更高效的控制。