四种常见限流算法
参考视频:【IT老齐157】从计数器到令牌桶,四种经典限流算法到底是如何实现的?_哔哩哔哩_bilibili
限流算法,顾名思义,就是在系统面临高并发或大流量请求的情况下,限制新的流量对系统的访问,从而保证系统服务的安全性和稳定性的一种手段。在日常生活中,例如一些热门的旅游景区,一般会对每日的旅游参观人数有限制,这也是限流算法思想的一种体现。
什么情况下需要用到限流?
- 突发流量。比如秒杀活动,当服务器同一时间处理不了过多的请求,就可能造成服务器被打垮;因此需要对请求做限流,防止自身的系统被突发的流量打垮
- 恶意流量。比如爬虫,也是会造成服务器的压力,影响服务器的正常运行
- 业务本身需要。涉及到系统的珍贵资源的接口,有时同样需要限流,比如有某个接口调用了第三方的服务(例如chatgpt的api),而这个第三方服务处理请求的速度远远跟不上服务器处理请求速度时,可能会造成第三方服务的奔溃;
如何准确限流?
基于计数的限流器
这个思想是在一个每一个固定的时间段内统计调用次数,如果这个时间段内超过指定次数就进行限流;超过这个时间段计数器清零,重新计数
优点:实现简单
缺点:有临界问题,没办法精确地控制流量的平滑性,比如我在0:00 - 0:58内都没有请求,但是我在0:59的时候请求了100次,又在1:00请求100次,那么还是没有解决突发流量问题
基于滑动窗口的限流
这个思想是遍历过去一分钟内每一个独立区间,也就是每10s内的计数器的计数总和,这个总和就是过去一分钟内的全部请求数量。每经过10s就把滑动窗口往右移动一格。
想要进一步提高流量的平滑性,就需要不断增加窗口的精度,也就是缩小每个区间的大小
优点:提高了流量的平滑性
缺点:增加了系统的内存负担,因为需要存储每一个区间内的请求计数,区间越小,占用内存越多,计算间隔越小
漏桶算法
"漏桶"联想到实际生活中,无论有多少"水"灌入漏桶中,但是它漏水的速度是不变了;这种现象十分适用在限流算法中,限流不就是想我们的流量是平滑地进入系统吗?
一般情况下我们会适用队列来实现漏桶算法,因为队列有先进先出的特性,有请求进来就从一端存储到队列中,另一端使用一个稳定的线程拉取处理请求,如果系统处理请求的速度跟不上流出的速率,那么超出部分的请求就会被丢弃,类似于桶里的水超过桶的容量就会溢出
优点:稳定,流量平滑
缺点:稳定也就意味着放弃了应对突发流量的能力,比如系统想要对短时间的突发流量进行支持,漏桶算法无法实现
令牌桶算法
系统会以一个恒定的速度往桶里放入令牌,如果在一段时间内,请求的速度都高于令牌放入的速度,令牌桶中很快就会没用令牌可以使用了,服务就会拒绝一部分请求,并保证系统处理的流量在我们控制范围内
令牌桶算法和漏桶算法的区别
令牌桶算法对突发流量更友好,比如某一时间段内,系统的处理的请求较少,令牌数较多,那么突发流量进来自然可以进行快速处理(前提是有足够的令牌数)
代码实现
可以使用Guava提供的限流器
import com.google.common.util.concurrent.RateLimiter;
public class TokenBucketExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个令牌桶,每秒产生1个令牌,最大容量为5个令牌
RateLimiter rateLimiter = RateLimiter.create(1.0); // 每秒产生一个令牌
// 模拟请求处理
for (int i = 0; i < 10; i++) {
// 如果令牌桶中有令牌,则处理请求并消耗一个令牌
if (rateLimiter.tryAcquire()) {
System.out.println("处理请求 " + (i + 1));
} else {
System.out.println("请求 " + (i + 1) + " 被拒绝");
}
Thread.sleep(100); // 模拟请求间隔
}
}
}