前言
常见的限流算法有 滑动窗口、漏斗、令牌桶等,后者都可以有非常轻量的实现,这里我们就来聊聊滑动窗口算法。
正文
在聊滑动窗口之前,我们先来聊一下普通的计数器统计,我们经常会将Key设置为时间,然后value就是请求的数量,这样基于redis就能实现一个简单的限流,但是这样存在一个什么问题呢?就是一个尖刺问题,比如我一秒现在的QPS是100,那么我第一秒的请求都是集中在后半秒有80,对于我第一秒来说不用限流的,然后第二秒的请求都在前半段有40,那么对于我的第二秒来说也是不需要限流的,但是我们全局来看的时候就会发现问题了,因为中间时刻QPS超过了100达到了120,但是我们并没有进行限流,这里就引出了滑动窗口算法,首先滑动窗口并没有完全解决这个尖刺的问题,只是做了优化,只要窗口的精度越小,曲线也就越平缓。接下来就上代码吧
代码
/**
* 滑动时间窗口限流工具 单机demo
*
* @author ljf
* @date 2022-08-29 10:53
*/
public class SlideWindowV2 {
/**
* 服务访问次数
*/
private AtomicLong counter = new AtomicLong(0L);
/**
* 格子
*/
private LinkedList<Long> slots = new LinkedList<>();
/**
* 限流标志
*/
private volatile boolean limit = false;
/**
* 滑动窗口状态
*/
private volatile boolean status = true;
private SlideWindowV2(int type, int qps) {
if (type == 1) {
startSlideWindowSecondsV2(qps);
} else {
throw new UnsupportedOperationException();
}
}
/**
* 验证
*
* @return
*/
public boolean check() {
counter.incrementAndGet();
return !limit;
}
/**
* 停止
*/
public void stop() {
status = false;
}
private void startSlideWindowSecondsV2(int qps) {
new Thread(() -> {
while (status) {
slots.addLast(counter.get());
// 一秒分成100个格子
if (slots.size() > 100) {
slots.removeFirst();
}
if (slots.getLast() - slots.getFirst() + 1 > qps) {
limit = true;
} else {
limit = false;
}
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
public static void main(String[] args) throws InterruptedException {
SlideWindowV2 v2 = new SlideWindowV2(1, 7);
Thread.sleep(100L);
for (int i = 0; i < 10; i++) {
if (v2.check()) {
System.out.println(System.currentTimeMillis() + ":正常运行");
} else {
System.out.println("被限流了");
}
Thread.sleep(50L);
}
v2.stop();
}
}
我将一秒分为了100个格子,那么一个格子也就是10ms,每个格子记录的就是当时统计的QPS总数,我每隔10ms进行校验,首先判断格子的数量是不是超过了100,超过了就代表时间窗口需要滑动了,那么将最开始的格子移除,同时判断第一个和最后一个格子的差值是不是超过了限定的QPS,来决定是否限流。好了今天就分享到这里。