这是我参与8月更文挑战的第28天,活动详情查看: 8月更文挑战
限流在我们生活中也听到的比较多,比如去景区旅游时,需要提前预约景区的门票,其实这也是种限流措施,在软件中如果访问量比较大的时候,系统无法承受住一下比较大的访问流量,如果我们不对出现这样的问题做一些限流操作的话,那我们的系统可能就会造成整个系统不可用,限流是对系统的一种保护措施,当流量请求访问超过一定数量的时候,就需要限制流量的请求,将多余的请求抛弃掉,来保障系统的可用性,限流在网关中用的比较多,网关需要限制过多的流量打到后端请求,造成后端的应用程序无法支持那么大的流量请求可宕掉,常用的限流算法有:计数器、漏桶算法、令牌桶、滑动窗口这几种限流算法。
计数器
- 概述
对指定时间段内访问的次数做一个计数,当访问量达到限制的次数之后,在该时间段内后面的访问就不允许访问,只能等到下一个时间段才可以访问。这种算法实现起来简单,但是当在一个时间段内,有大量的请求来的时候,会在瞬间丢弃大量的请求,例如1秒限制访问5次,如果前5次在0.1秒内都处理完成后,后面的0.9秒都没法请求访问,服务也处理空闲状态,这样就造成资源白白的浪费。 采用计数器的场景,在发送验证码的时候用的比较多,防止恶意刷验证码,造成验证码资源浪费,这个时候一般就会限制1分钟内只能够发送一次,当输入次数达到一次的次数后,就会冻结账号的登录,隔一段时间后才可以减冻,重新登录。
- 算法实现介绍
在这里实现的时候借助java并发包里的Semaphore类来实现,在代码中开启一个线程,每1秒钟重置Semaphore的次数,然后采用while循环来模拟数据不停的请求,当获取到次数后,在执行后续的请求,没有拿到次数就阻塞等待下一个周期
- 简易代码
final Semaphore semaphore = new Semaphore(5);
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
semaphore.release(5);
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
//模拟无数个请求从天而降
while (true) {
//判断计数器
semaphore.acquire();
}
漏桶算法
- 概述
漏桶算法就像我们平时接水的水桶一样,在下面有一个小洞,可以将水匀速的漏出,当我们将这个桶放到水龙头下接水的时候,当水龙头开的比较大时,很快就将水桶接满,这个时候水就会溢出,当水龙头水比较小或者没有时,下面的洞会慢慢的把水桶里的水漏干。这里借用网上的一张图
这种算法可以有效的挡住外部的请求,当请求超过我桶的容量时,就将多余的请求扔掉,但是因为内部是匀速请求,当出现业务高峰期时,会将多余的请求扔掉,无法应对流量高峰期,当业务高峰期时会扔掉很多请求,对业务不太友好。
漏桶算法在Nginx中比较常用,可以对相同的客户端访问次数限制,防止客户端恶意攻击网站。
http {
#$binary_remote_addr 表示通过remote_addr这个标识来做key,也就是限制同一客户端ip地址。
#zone=one:10m 表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。
#rate=1r/s 表示允许相同标识的客户端每秒1次访问
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /limited/ {
#zone=one 与上面limit_req_zone 里的name对应。 #burst=5 缓冲区,超过了访问频次限制的请求可以先放到这个缓冲区内,类似代码中的队列长度。
#nodelay 如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求 会等待排队,类似代码中的put还是offer。
limit_req zone=one burst=5 nodelay;
}
}
- 算法实现介绍
才用LinkedBlockingQueue来模拟桶的容量,然后采用一个线程来每隔一秒钟将LinkedBlockingQueue队列中的元素取出代表请求被执行,然后采用while循环来不停的往LinkedBlockingQueue队列中添加元素,如果LinkedBlockingQueue队列没有满就可以添加成功,当队列满了就无法继续添加数据,就将该元素扔掉。
- 简易代码
//桶,用阻塞队列实现,容量为5
final LinkedBlockingQueue<Integer> que = new LinkedBlockingQueue(5);
//定时器,相当于服务的窗口,1s处理一个
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate((Runnable) () -> {
int v = que.poll();
System.out.println("处理:" + v);
}, 1000, 1000, TimeUnit.MILLISECONDS);
int i = 0;
while (true) {
i++;
System.out.println("put:" + i);
que.offer(i, 1000, TimeUnit.MILLISECONDS);
}
令牌桶
- 概述
令牌桶是漏桶算法的一种升级,漏桶是限制桶漏出的大小,而令牌就是限制水龙头每次产出的水的流速,然后将水龙头的水流入到桶中,桶可以根据水的多少来扩容,然后后面的请求不停的从桶中舀出水来处理。这里选用网上的一张图片来描述该算法的实现逻辑
在实际应用中SpringCloud的网关可以配置令牌桶算法来实现限流控制
cloud:
gateway: routes:
‐ id: limit_route
uri: http://localhost:8080/test
filters:
‐ name: RequestRateLimiter
args:
#限流的key,ipKeyResolver为spring中托管的Bean,需要扩展KeyResolver接口
key‐resolver: '#{@ipResolver}'
#令牌桶每秒填充平均速率,相当于代码中的发放频率
redis‐rate‐limiter.replenishRate: 1
#令牌桶总容量,相当于代码中,信号量的容量
redis‐rate‐limiter.burstCapacity: 3
- 算法实现介绍
实现的时候也采用Semaphore来做一个桶,设置桶的最大容量,然后开启一个线程匀速的增加Semaphore桶的容量,当小于指定容量的时候才往里面存放令牌,然后模拟请求,从Semaphore获取信号,当获取到后才进行后续的执行逻辑,没有获取到就一直等待,直到有令牌产生。
- 简易代码
final Semaphore semaphore = new Semaphore(3);
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(() -> {
if (semaphore.availablePermits() < 3) {
semaphore.release();
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
滑动窗口
- 概述
滑动窗口可以理解为将一个周期内的限制次数拆分为多个片段,每个片段的处理请求都要小于该段的请求上限,对于请求的控制更加精细,采用网上的一张图,大家应该一看就比较明白,每次窗口不停的往前滑动
滑动窗口算法在TCP协议中发包读包过程中,用的比较多