限流之滑动窗口算法实战

x33g5p2x  于2022-01-11 转载在 其他  
字(2.1k)|赞(0)|评价(0)|浏览(386)

一 算法

滑动窗口算法弥补了计数器算法的不足。滑动窗口算法把间隔时间划分成更小的粒度,当更小粒度的时间间隔过去后,把过去的间隔请求数减掉,再补充一个空的时间间隔。

如下图所示,把1分钟划分为10个更小的时间间隔,每6s为一个间隔。

1 一个时间窗口为1分钟,滑动窗口分成10个格子,每个格子6秒。

2 每过6秒,滑动窗口向右移动1个格子。

3 每个格子都有独立的计数器。

4 如果时间窗口内所有的计数器之和超过了限流阀值,则触发限流操作。

如下图所示,滑动窗口算法比计数器算法控制得更精细。

用户在0:59 时刻发送了100个请求,第10个格子的计数器增加100,下一秒的时候时间窗口向右移动1格,这时再来100个请求就超过了阈值,不会处理这100个请求,这样就避免了计数器场景出现的问题。

滑动窗口设置得越精细,限流的效果越好,但滑动窗口的时间间隔(小格子)多了,存储的空间也会增加。

二 需求

1 设计一个滑动窗口,窗口有10个格子,每个格子10秒,每隔10秒移动一格。

2 装满所有格子的时间为 10 * 10 = 100 秒。也就是说时间窗口是 100 秒。

3 从100秒开始,开始滑动,新请求数开始覆盖老请求数。

三 代码

package currentLimit;

import java.util.Date;
import java.util.Random;

/**
* @className: CounterLimit
* @description: 滑动窗口算法
* @date: 2022/1/8
* @author: cakin
*/
public class SlideWindowLimit {
    // 滑动窗口大小
    static final int size = 10;
    // 滑动窗口数组,每移动一个格子,更新对应数据项的值
    static int window[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    // 理解为移动窗口中正在计数的格子
    static int curId = 0;
    // 记录上次统计时间
    static Date lastDate = new Date();
    // 当前窗口计数总和
    static int counter = 0;

    /**
     * 功能描述:模拟一次请求是否限流
     *
     * @return true:限流 false;不限流
     * @author 贝医
     * @date 2022/1/8
     * @description:
     */
    static boolean slideWindowLimit() {
        // 获取当前时间
        Date now = new Date();
        // 当前时间同上次记录时间的间隔,单位为秒
        long time = (now.getTime() - lastDate.getTime()) / 1000;
        // 按照新的移动窗口进行计数
        if (time >= 10) {
            // 当前计数格子的下一个格子将被清掉重写
            curId++;
            curId = curId % size;
            int newCurId = curId;
            // 下一个格子将被清掉,总数据减掉
            counter = counter - window[newCurId];
            // 新格子设置为1
            window[newCurId] = 1;
            // 记录滑动的时间
            lastDate = now;
        } else {
            // 当前计数的格子
            ++window[curId];
        }
        ++counter;
        return counter >= 1000;
    }

    // 测试方法
    public static void main(String[] args) {
        for (; ; ) {
            // 模拟一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Random random = new Random();
            int i = random.nextInt(3);
            // 模拟1秒内请求8次
            if (i == 1) {
                for (int j = 0; j < 8; j++) {
                    if (slideWindowLimit()) {
                        System.out.println("限流了" + counter);
                    } else {
                        System.out.println("没限流" + counter);
                    }
                }
            } else if (i == 2) { // 模拟1秒内请求9次
                for (int j = 0; j < 9; j++) {
                    if (slideWindowLimit()) {
                        System.out.println("限流了" + counter);
                    } else {
                        System.out.println("没限流" + counter);
                    }
                }
            } else { // 模拟1秒内请求10次
                for (int j = 0; j < 10; j++) {
                    if (slideWindowLimit()) {
                        System.out.println("限流了" + counter);
                    } else {
                        System.out.println("没限流" + counter);
                    }
                }
            }
        }
    }
}

四 测试

五 说明

记录滑动窗口中的请求数。滑动窗口中的请求数控制在 1000以内。滑动窗口能记录100秒的请求,所以如果每秒请求不超过10,不会限流。测试用例也是这样设计的,每秒模拟发送的请求为8次,9次,10次。从测试结果来看,符合预期。

相关文章