限流算法之----滑动窗口

关于限流算法有许多种,有简单的计数限流阀,固定窗口限流法,滑动窗口限流法,漏桶算法,令牌桶算法。今天我们就来聊聊滑动窗口版的限流算法。
说起滑动窗口之前,我们先来说说固定窗口限流。固定窗口限流,我们可以这样子来理解,我们假设规定1s可以放入3个请求,那么窗口的大小就是1s,然后在1s内对请求计数,如果超过3那么就限流,过了这一秒后计数清空。
可以用下图来理解
限流算法之----滑动窗口_第1张图片

这边放一下我写的固定窗口的限流算法线程安全版给大家参考一下
主要是利用juc包下的原子类和cas的操作以及一些状态的判断

/**
 * @Author: chencc
 */
public class FixedWindowRateLimit {
    /**
     * 窗口开始的时间 -1代表还没被初始化
     */
    AtomicLong startTime = new AtomicLong(-1);
    /**
     * 计数
     */
    AtomicInteger count = new AtomicInteger(0);
    /**
     * 最大请求数
     */
    private final int MAX_REQUEST_NUM = 100;

    /**
     * 判断当前是否能够继续往下进行
     * @return
     */
    public boolean entry() {
        long time = System.currentTimeMillis();
        for (;;) {
            // 如果第一次没有初始化
            if (startTime.get() == -1) {
                if (lockStartTime(-1)) {
                    count.set(1);
                    unlockStartTime(time);
                    return true;
                }
            }
            // 如果是-2 证明窗口处于锁定状态 让出cpu
            if (startTime.get() == -2) {
                Thread.yield();
                continue;
            }
            // 如果这个请求的时间已经小于当前这个窗口的时间 更新时间
            if (time < startTime.get()) {
                time = System.currentTimeMillis();
            }
            // 判断当前时间和窗口已经相差了一秒
            long expect = startTime.get();
            if (time - expect >= 1000) {
                if (lockStartTime(expect)) {
                    count.set(1);
                    unlockStartTime(time);
                    return true;
                }
            }
            // 如果已经超过的最大请求数
            if (count.get() >= MAX_REQUEST_NUM && startTime.get() > 0) {
                return false;
            }
            int num;
            for (;(num = count.get()) < MAX_REQUEST_NUM && startTime.get() > 0 && time >= startTime.get();) {
                if (count.compareAndSet(num, num + 1)) {
                    return true;
                }
            }
        }
    }

    /**
     * -2代表当前被另外一个线程再使用
     * @param expect
     * @return
     */
    private boolean lockStartTime(long expect) {
        return startTime.compareAndSet(expect, -2);
    }

    /**
     * 解除锁定
     * @param time
     */
    private void unlockStartTime(long time) {
        startTime.set(time);
    }
}

在spingboot的环境下可以通过拦截器这样子来测试

/**
 * @author chencc
 * @date 2022/8/2 21:42
 */
@Configuration
@Component
public class RateLimitFilter implements Filter {

    @Autowired
    private FixedWindowRateLimit fixedWindowRateLimit;

    @Bean
    public FixedWindowRateLimit getFixedWindowRateLimit() {
        return new FixedWindowRateLimit();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!fixedWindowRateLimit.entry()) {
            response.getWriter().write("rate limit");
        } else {
            chain.doFilter(request, response);
        }
    }
}

通过以上的了解了固定窗口版的限流算法,是不是看上去没什么毛病?其实不然,我们想一想,如果一秒钟允许500个请求,如果这500个请求都在这1s钟的最后100ms过来那么是可以通过的,然后下一秒的前100ms又过来了500个请求,那么这个算法也是允许通过的,那么问题就来了,再这短短的200ms就已经过去了1000个请求,这也是这个算法的一个缺陷所在,面对突增的流量以及刚好又在上一秒和下一秒的切换的时候,可能没有起到很好的防护
所以下面就引出我们的滑动窗口版的限流算法
滑动窗口版的算法我们可以这么来理解
我们可以把1s 以 200ms 分割成了5个小窗口,然后每个小窗口会记录这段时间内的请求数量,如果总和已经超过了最大请求量那么就拒绝请求,然后每隔200ms 我们往右边滑动,顺便减去左边被淘汰出去的窗口的请求数,我们可以用下图来理解(1s三个请求)
限流算法之----滑动窗口_第2张图片
每个大窗口会记载当前这1s内有几个请求了,然后每隔200ms往左滑动,然后减去最左边被淘汰的小窗口的请求数量,可以发现,如果小窗口的越小那么越平滑,如果小窗口的大小也是1s,那么就退化成固定窗口了。。如果使用滑动窗口的话,小窗口大小为200ms,那么就不会发生上面固定窗口的那种情况了
下一篇文章我们再来实现这样子的一个限流算法

你可能感兴趣的:(算法,java,限流算法)