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


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

    这边放一下我写的固定窗口的限流算法线程安全版给大家参考一下
    主要是利用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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    在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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

  • 相关阅读:
    07 Qt自绘组件:图片预览小组件ImageViewer
    flink on k8s部署
    远程管理SSH服务
    大语言模型研究进展综述
    STM32外部Flash移植FATFS笔记
    IPTV桌面系统建设物料和费用:服务器+软件+电视盒
    美国高防服务器到底好不好用
    es笔记三之term,match,match_phrase 等查询方法介绍
    ps2022 - ps to dxf
    Verilog中关于reg [3:0] a [7:0] 和 reg [3:0] [7:0] a的区别
  • 原文地址:https://blog.csdn.net/weixin_45415649/article/details/126131310