滑动窗口限流算法的实现-----------单线程版

上文我们说了滑动窗口算法的原理,本篇文章就来实现这样子的一个算法,为什么说是单线程版呢,因为多线程的情况下是线程不安全的,为了方便大家的理解,这次就只放单线程版的,多线程版的我们下篇文章再来放,由于代码中有注释我就不多说明了,大家参考的同时也可以帮我找找Bug,哈哈,entry方法是入口

package com.ccc;

/**
 * @author chencc
 * @date 2022/7/30 22:07
 */
public class SlidingWindowRateLimit {
    /**
     * 窗口大小
     */
    private int windowSize;
    /**
     * 最大请求数
     */
    private int maxRequestNum;
    /**
     * 以1000毫秒为一个分割(qps)
     */
    private final int LIMIT_UNIT = 1000;
    /**
     * 窗口数组 模拟队列
     */
    private Window[] windowArr;
    /**
     * 窗口数组的长度
     */
    private int arrLen;
    /**
     * 尾下标
     */
    private int tailIndex = 0;
    /**
     * 头下标
     */
    private int headIndex = 0;
    /**
     * 当前请求数
     */
    private int currentRequestNum = 0;

    class Window {
        /**
         * 窗口的起点
         */
        private long start;
        /**
         * 在这个区间内的请求数
         */
        private int requestNum;

        public Window() {
        }

        public Window(int start, int requestNum) {
            this.start = start;
            this.requestNum = requestNum;
        }
    }

    public SlidingWindowRateLimit(int maxRequestNum) {
        this(100, maxRequestNum);
    }

    public SlidingWindowRateLimit(int windowSize, int maxRequestNum) {
        if (LIMIT_UNIT % windowSize != 0) {
            throw new IllegalArgumentException(LIMIT_UNIT + " % windowSize must is 0");
        }
        this.windowSize = windowSize;
        this.maxRequestNum = maxRequestNum;
        this.arrLen = getLen(LIMIT_UNIT / windowSize);
        this.windowArr = new Window[this.arrLen];
        // 预先初始化window对象
        initWindowArr(this.arrLen);
    }

    private void initWindowArr(int len) {
        for (int n = 0; n < len; n++) {
            windowArr[n] = new Window();
        }
    }

    /**
     * 判断当前这个请求是否能够通过
     * @return
     */
    public boolean entry() {
        // 获得当前时间
        long currentTime = System.currentTimeMillis();
        // 判断是否有窗口可以移出去
        while (enableRemoveWindow(currentTime)) {
            // 删除窗口
            removeTailWindow();
        }
        // 如果当前1s内的所有窗口的请求总和数量大于最大请求数量
        if (currentRequestNum >= maxRequestNum) {
            return false;
        }
        return doEntry(currentTime);
    }

    private boolean doEntry(long time) {
        if (headIndex == tailIndex) {
            setHeadWindowStartTime(time);
            return true;
        }
        // 获得当前最新的window对象
        Window currentHeadWindow = getCurrentHeadWindow();
        // 判断是否需要新生成窗口对象
        if (!check(currentHeadWindow, time)) {
            currentHeadWindow.requestNum++;
            currentRequestNum++;
            return true;
        }
        // 获得新窗口的开始时间
        long startTime = getStartTime(currentHeadWindow, time);
        setHeadWindowStartTime(startTime);
        return true;
    }

    /**
     * 当这个窗口和这个时间不在一个窗口内,获得这个时间应该在的窗口的startTime 又可能中间相差了好几个窗口 所以要计算startTime
     * @param window
     * @param time
     * @return
     */
    public long getStartTime(Window window, long time) {
        long start = window.start;
        long interval = time - start;
        // 获得相差窗口的个数
        long num = interval / windowSize;
        return num * windowSize + start;
    }


    private boolean check(Window currentHeadWindow, long time) {
        return currentHeadWindow.start + windowSize < time;
    }

    /**
     * 获得当前最新的window对象
     * @return
     */
    private Window getCurrentHeadWindow() {
        if (headIndex == 0) {
            return windowArr[arrLen - 1];
        }
        return windowArr[headIndex - 1];
    }

    private void incrementHeadIndex() {
        headIndex = (headIndex + 1) & (arrLen - 1);
    }

    private void incrementTailIndex() {
        tailIndex = (tailIndex + 1) & (arrLen - 1);
    }

    /**
     * 设置头下标对应窗口的值
     * @param startTime
     */
    private void setHeadWindowStartTime(long startTime) {
        Window window = windowArr[headIndex];
        window.start = startTime;
        window.requestNum = 1;
        // 总请求数量加一
        currentRequestNum++;
        // 移动头下标
        incrementHeadIndex();
    }

    /**
     * 移动尾下标 来表示移除掉窗口
     */
    private void removeTailWindow() {
        // 减去这个窗口的请求数
        currentRequestNum -= windowArr[tailIndex].requestNum;
        // 移动尾下标
        incrementTailIndex();
    }

    /**
     * 判断是否能够移除掉窗口
     * @param time
     * @return
     */
    private boolean enableRemoveWindow(long time) {
        if (tailIndex == headIndex) {
            return false;
        }
        return time - windowArr[tailIndex].start >= LIMIT_UNIT;
    }

    /**
     * 获得大于len的最小2的N次方的数
     * 参考 ArrayDeque 里面的 源码 基本拷贝的 哈哈
     * 为了 % 可以使用位运算
     * @param len
     * @return
     */
    private int getLen(int len) {
        len |= (len >>>  1);
        len |= (len >>>  2);
        len |= (len >>>  4);
        len |= (len >>>  8);
        len |= (len >>> 16);
        len++;
        if (len < 0) {
            len >>>= 1;
        }
        return len;
    }

}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值