限流用在接口调用:
1、计数器限流算法:
大概思路:限制1S内的请求数量最大为100,否则限流。
timeStamp:请求开始时间
interval:时间间隔,1S
算法中 now< timeStamp + interval的意思是当前时间是不是在1S的时间间隔内。else的意思是当前时间已经不在上一次的1S内了,则将起始时间定为为now,请求次数置为1。
计数器算法的缺点:
有可能相邻的两个1S内,第一个1S后半段的(0.5秒)突发了60个访问量,第二个1S的前半段突发60个访问量,那就不符合了1S内的100的限流。
2、滑动窗口限流算法:
由于计数器算法的精度太低,为了解决计数器算法突发1S内访问超出限制数量的问题,所以推出了滑动时间窗口算法。
思路:将1S内的时间分成10个100毫秒,最近的1S内,往前数10个格子的数量。
public class SlidingTimeWindowLimiter {
//服务在最近1秒内的访问次数,可以放在Redis中,实现分布式系统的访问计数
private int reqCount;
//使用LinkedList来记录滑动窗口的10个格子
private LinkedList<Integer> slots = new LinkedList<>();
//每秒限流的最大请求数
private int limitNum = 100;
//滑动时间窗口里的每个格子的时间长度,单位ms
private long windowlength = 100L;
//滑动时间窗口里的格子数量
private int windowNum = 10;
/**
* 构造函数
*/
public SlidingTimeWindowLimiter() {
slots.addLast(0);
new Thread(() -> {
while (true) {
try {
Thread.sleep(windowlength);
} catch (InterruptedException e) {
e.printStackTrace();
}
//100ms后加一个窗口
slots.addLast(0);
//格子数量超出10个,超出1S了。
if (slots.size() > windowNum) {
//第11的格子处,请求的总数 = 请求总数 - 第一个格子的请求次数
reqCount = reqCount - slots.peekFirst();
slots.removeFirst();
System.out.println("");
}
}
}).start();
}
public synchronized Boolean limit() {
if (reqCount + 1 > limitNum) {
return true;
}
slots.set(slots.size() - 1, slots.peekLast() + 1);
reqCount++;
return false;
}
public static void main(String[] args) {
SlidingTimeWindowLimiter windowLimiter = new SlidingTimeWindowLimiter();
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 160; i++) {
final int n = i;
if (n == 105) {
try {
Thread.sleep(1000);
System.out.println("等了1S");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executorService.submit(() -> {
System.out.println("当前i是多少:" + n + " 限流吗:" + windowLimiter.limit() + " 当前请求次数:" + windowLimiter.reqCount);
});
}
}
}
3、漏桶算法:
思路:由于处理请求的速度一样。用可接受的请求数量来判断是否要限流。
如果水未满,则可以继续加水(继续接受请求),否则拒绝加水(拒绝请求)。
4、令牌桶算法:
以恒定的速率产生令牌,桶满则丢弃多余的令牌。请求进来的时候获取到令牌则放行,否则拒绝请求。
产生令牌的速度一样,但是消耗令牌的速度可以不一样。
令牌的数量为0则被限流,否则通行。