限流算法-滑动窗口算法的简单实现

1,696 阅读2分钟

前言

常见的限流算法有 滑动窗口、漏斗、令牌桶等,后者都可以有非常轻量的实现,这里我们就来聊聊滑动窗口算法。

正文

在聊滑动窗口之前,我们先来聊一下普通的计数器统计,我们经常会将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,来决定是否限流。好了今天就分享到这里。