限流是指在系统面临高并发、大流量请求的情况下,限制新的流量对系统的访问,从而保证系统服务的安全性。常用的限流算法有计数器固定窗口算法、滑动窗口算法、漏斗算法和令牌桶算法,下面将对这几种算法进行分别介绍,并给出具体的实现。本文目录如下,略长,读者可以全文阅读,同样也可以只看感兴趣的部分。
文章目录
计数器固定窗口算法
原理
计数器固定窗口算法是最基础也是最简单的一种限流算法。原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。
代码实现及测试
实现起来也比较简单,如下:
package project.limiter;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Project: AllForJava
* Title:
* Description:
* Date: 2020-09-07 15:56
* Copyright: Copyright (c) 2020
*
* @公众号: 超悦编程
* @微信号:exzlco
* @author: 超悦人生
* @email: exzlc@139.com
* @version 1.0
**/
public class CounterLimiter {
private int windowSize; //窗口大小,毫秒为单位
private int limit;//窗口内限流大小
private AtomicInteger count;//当前窗口的计数器
private CounterLimiter(){
}
public CounterLimiter(int windowSize,int limit){
this.limit = limit;
this.windowSize = windowSize;
count = new AtomicInteger(0);
//开启一个线程,达到窗口结束时清空count
new Thread(new Runnable() {
@Override
public void run() {
while(true){
count.set(0);
try {
Thread.sleep(windowSize);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
//请求到达后先调用本方法,若返回true,则请求通过,否则限流
public boolean tryAcquire(){
int newCount = count.addAndGet(1);
if(newCount > limit){
return false;
}else{
return true;
}
}
//测试
public static void main(String[] args) throws InterruptedException {
//每秒20个请求
CounterLimiter counterLimiter = new CounterLimiter(1000,20);
int count = 0;
//模拟50次请求,看多少能通过
for(int i = 0;i < 50;i ++){
if(counterLimiter.tryAcquire()){
count ++;
}
}
System.out.println("第一拨50次请求中通过:" + count + ",限流:" + (50 - count));
//过一秒再请求
Thread.sleep(1000);
//模拟50次请求,看多少能通过
count = 0;
for(int i = 0;i < 50;i ++){
if(counterLimiter.tryAcquire()){
count ++;
}
}
System.out.println("第二拨50次请求中通过:" + count + ",限流:" + (50 - count));
}
}
测试结果如下:
可以看到50个请求只有20个通过了,30个被限流,达到了预期的限流效果。
特点分析
优点:实现简单,容易理解。
缺点:流量曲线可能不够平滑,有“突刺现象”,如下图所示。这样会有两个问题:
-
一段时间内(不超过时间窗口)系统服务不可用。比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第1ms来了100个请求,然后第2ms-999ms的请求就都会被拒绝,这段时间用户会感觉系统服务不可用。
-
窗口切换时可能会产生两倍于阈值流量的请求。比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第999ms来了100个请求,窗口前期没有请求,所以这100个请求都会通过。再恰好,下一个窗口的第1ms有来了100个请求,也全部通过了,那也就是在2ms之内通过了200个请求,而我们设定的阈值是100,通过的请求达到了阈值的两倍。
计数器滑动窗口算法
原理
计数器滑动窗口算法是计数器固定窗口算法的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点。
滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
从图中不难看出,滑动窗口算法就是固定窗口的升级版。将计时窗口划分成一个小窗口,滑动窗口算法就退化成了固定窗口算法。而滑动窗口算法其实就是对请求数进行了更细粒度的限流,窗口划分的越多,则限流越精准。
代码实现及测试
package project.limiter;
/**
* Project: AllForJava
* Title:
* Description:
* Date: 2020-09-07 18:38
* Copyright: Copyright (c) 2020
*
* @公众号: 超悦编程
* @微信号:exzlco
* @author: 超悦人生
* @email: exzlc@139.com
* @version 1.0
**/
public class CounterSildeWindowLimiter {
private int windowSize; //窗口大小,毫秒为单位
private int limit;//窗口内限流大小
private int splitNum;//切分小窗口的数目大小
private int[] counters;//每个小窗口的计数数组
private int index;//当前小窗口计数器的索引
private long startTime;//窗口开始时间
private CounterSildeWindowLimi