计数器
计数器就是对请求进行计数,不断的与阀值进行比较判断是否限流,一旦到达了临界点,将计数器清零。就是一个时间窗口,这个时间窗口允许的请求数为一定数值。
程序的执行逻辑是:
- 在程序设置一个变量 count,每次过来一个请求就+1,同时计算请求的时间。
- 当下一次请求来的时候,判断 count 的计数值是否超过,以及当前请求的时间和前一次请求的时间是否在 1 分钟之内。
- 如果一分钟之内请求的次数超过设定的阀值,证明请求过多,后面的请求就拒绝。 -但是如果请求的时间间隔大于计数周期。而且 count 也在限流的范围内,重置 count.
但是存在的问题是,我可以在重置时间的前后间隔的时间段发送 2 倍的请求。重置的前后很短很短的时间内。
滑动窗口
关键是在于:多个时间格子使得可以记录两个时间格子的『临界时间』前后的请求总量,而不是丢弃临界前的请求量。
滑动窗口的本质是把固定的时间片进行划分,并且随着时间的流逝,进行一定移动固定数量和格子,进行计数并判断阀值。
一个时间窗口(一分钟)每个时间窗口有六个格子,每个格子都是 10 秒钟。每过 10 秒钟时间窗口向右移动一个格。每个格子都有一个独立的计数器。一个请求在 0:45 访问了我们第五个格子,那么第 5 个格子的计数器+1,在判断限流的时候只需要把所有的格子的计数加起来和设定的频次进行比较即可。
当用户在 0:59 秒钟发送了 200 个请求就会被第六个格子的计数器记录 +200,当下一秒的时候时间窗口向右移动了一个,此时计数器已经记录了该用户发送的 200 个请求,所以再发送的话就会触发限流,则拒绝新的请求。
计数器就是一个滑动窗口,只不过只有一个格子而以,所以为了让限流做的更加精确就只需要划分更多的格子,格子的数量影响滑动窗口的算法的精度,仍然有时间片的概念。
漏桶算法(Leaky Bucket)
原理就是一个固定容量的漏桶,有水流进来,也有水流出去。但是对于流进来的水无法预计以共有多少,但是对于流出去的水,这个桶可以以固定的水流速度,从而达到流量控制的效果。
实现步骤:
- 调用方发出请求,如果桶没有满,那么返回调用方,请求已经被接收,被接收的请求进入下一步。如果已经满了,那么丢弃请求,请求调用失败,直接返回,调用方继续发出请求调用。
- 被接收的请求同步执行:调用方请求的时候,如果请求可以被放入桶,那么调用方所在的线程被阻塞,水滴从漏桶流出的时候,唤醒调用方线程,执行具体的业务。
- 被接收的请求异步执行:调用方请求的时候,如果请求可以被放入桶,那么调用方所在的线程收到响应,方法将异步执行,水滴从漏桶流出的时候,水滴代理执行具体的业务。
只有通过水滴露出的时候,去执行业务方法,才能保证方法被“匀速的”执行。
漏桶算法的特点:
- 漏桶具有固定的容量,出水速率是固定的常量
- 桶是空的就不需要流出水滴。
- 可以以任意的速率流入请求到漏桶
- 如果流入的请求超出了桶的容量,则流入的请求被拒绝。
- 漏桶限制的是常量流出的速率,所以最大的速率就是出水速率,不能出现突发流量。
令牌桶算法(Token Bucket)
令牌桶算法用来控制发送到网络上的数据的数目。允许突发数据的发送。
实现步骤:
- 调用方发出请求,判断,如果桶中没有令牌就丢弃该请求,由调用方重新发送请求。如果桶中有令牌,那么就从桶中取出一个令牌,只有拿到令牌,请求才能被执行。
往令牌桶添加令牌的算法:
- 定时生成:(消耗资源)专门的线程匀速往桶添加 1 个令牌。如何令牌桶已经满了就不再添加。
- 获取令牌时重新计算“应该有的”令牌数然后进行令牌补充。(上次刷新时间-当前时间)*生成速率
令牌桶有如下特点:
- 令牌按照固定的速率被放入令牌桶
- 如果令牌桶满了,新添加的令牌将被丢弃或者拒绝
- 令牌桶限制的流入速率(允许突发情况,只要有令牌就能被处理。因为一次可以拿 3 个令牌,四个令牌)
计数器/滑动窗口/令牌桶/漏桶
都是针对服务器的限流。我们也可以对容器进行限流,比如对 Tomcat, Nginx 等限流手段。
Tomcat 可以限制最大线程数,当并发超过最大的线程的数的时候就会排队。 Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数