常用的限流方案思路和实现

news2024/11/22 8:42:01

限流方案

1、计数器(固定窗口)

1.1、简介

计数器固定窗口算法是最基础也是最简单的一种限流算法。原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求;如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。
在这里插入图片描述

1.2、代码实现

import java.util.concurrent.atomic.AtomicInteger;

public class CountLimiter {
    /**
     * 窗口大小,单位为毫秒
     */
    private int window;
    /**
     * 窗口内允许的请求次数
     */
    private int limit;
    /**
     * 当前窗口内的请求次数
     */
    private AtomicInteger count;
    /**
     * 窗口是否正在运行
     */
    private volatile boolean isRunning = true;

    public CountLimiter() {
        this(60, 10);
    }

    /**
     * 构造函数
     * @param window 窗口大小,单位为毫秒
     * @param limit 窗口内允许的请求次数
     */
    public CountLimiter(int window, int limit) {
        this.window = window;
        this.limit = limit;
        count = new AtomicInteger(0);
    }

    /**
     * 尝试获取令牌
     * @return 获取成功返回true,失败返回false
     */
    public boolean tryAcquire() {
        int current = count.incrementAndGet();
        if (current > limit) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 启动窗口线程
     */
    public void start() {
        // 启动一个线程,每过window毫秒将count置为0
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    try {
                        Thread.sleep(window);
                        count.set(0);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    /**
     * 关闭窗口线程
     */
    public void stop() {
        isRunning = false;
        System.out.println("限流关闭,关闭时间" + System.currentTimeMillis());
    }

    public static void main(String[] args) {
        CountLimiter limiter = new CountLimiter(100, 3);
        limiter.start();
        int count = 0;

        for (int i = 0; i < 10; ++i) {
            if (limiter.tryAcquire()) {
                count++;
                System.out.println("请求通过, 当前时间" + System.currentTimeMillis());
            }
        }
        limiter.stop();
        System.out.println("请求通过:" + count);
    }
}

运行结果:

请求通过, 当前时间1709273340687
请求通过, 当前时间1709273340693
请求通过, 当前时间1709273340693
限流关闭,关闭时间1709273340693
请求通过:3

1.3、特点分析

  • 实现简单,容易理解
  • 窗口界限处可能会出现两倍于阈值的流量
  • 窗口时间内可能会出现服务不可用,流量不够平滑

2、计数器(滑动窗口)

2.1、简介

计数器滑动窗口算法是计数器固定窗口算法的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点。
滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之和不能超过设定的阈值。
在这里插入图片描述

滑动窗口算法其实就是对请求数进行了更细粒度的限流,窗口划分得越多,则限流越精准。

2.2、代码实现

public class CountSlideLimiter {
    // 窗口大小
    private int window;
    // 窗口内允许的请求次数
    private int limit;
    // 滑动窗口的个数
    private int splitNum;
    // 计数器
    private int[] counter;
    // 计数器索引
    private int index;
    // 开始时间
    private long startTime;

    public CountSlideLimiter() {
        this(1000, 20, 10);
    }

    public CountSlideLimiter(int window, int limit, int splitNum) {
        this.window = window;
        this.limit = limit;
        this.splitNum = splitNum;
        counter = new int[splitNum];
        index = 0;
        startTime = System.currentTimeMillis();
    }

    /**
     * 尝试获取令牌
     * @return 获取成功返回true,否则返回false
     */
    public synchronized boolean tryAcquire() {
        long curTime = System.currentTimeMillis();
        long windowNum = Math.max(curTime - window - startTime, 0) / (window / splitNum);
        slide(windowNum);
        int count = 0;
        for (int i = 0; i < splitNum; ++i) {
            count += counter[i];
        }
        if (count >= limit) {
            return false;
        } else {
            counter[index]++;
            return true;
        }
    }

    /**
     * 滑动窗口
     * @param windowNum 滑动窗口距离
     */
    private synchronized void slide(long windowNum) {
        if (windowNum == 0) {
            return;
        }
        long slideNum = Math.min(windowNum, splitNum);
        for (int i = 0; i < slideNum; ++i) {
            index = (index + 1) % splitNum;
            counter[index] = 0;
        }
        startTime = startTime + windowNum * (window / splitNum);
    }

    public static void main(String[] args) throws InterruptedException {
        int limit = 3;
        CountSlideLimiter limiter = new CountSlideLimiter(100, limit, 5);
        int count = 0;

        for (int i = 0; i < 10; ++i) {
            count = 0;
            for (int j = 0; j < 50; ++j) {
                if (limiter.tryAcquire()) {
                    count++;
                    System.out.println("请求通过, 当前时间" + System.currentTimeMillis());
                }
            }
            Thread.sleep(10);
            for (int j = 0; j < 50; ++j) {
                if (limiter.tryAcquire()) {
                    count++;
                    System.out.println("请求通过, 当前时间" + System.currentTimeMillis());
                }
            }
            if (count > limit) {
                System.out.println("限流失败,当前请求数:" + count);
            }
        }
    }
}

运行结果:

请求通过, 当前时间1709278379478
请求通过, 当前时间1709278379484
请求通过, 当前时间1709278379484

2.2、特点分析:

  • 避免了计数器固定窗口算法在固定窗口切换时可能产生两倍于阈值流量的请求问题;
  • 和漏斗算法相比,新来的请求也能够被处理到,避免了漏斗算法的饥饿问题。

3、漏桶算法

3.1、简介

漏桶算法中的漏桶是一个形象的比喻,这里可以用生产者消费者模式进行说明,请求是一个生产者,每一个请求都如一滴水,请求到来后放到一个队列(漏桶)中,而桶底有一个孔,不断地漏出水滴,就如消费者不断地在消费队列中的内容,消费的速率(漏出的速度)等于限流阈值。即假如 QPS 为 2,则每 1s / 2= 500ms 消费一次。漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。
在这里插入图片描述

3.2、代码实现

import java.util.LinkedList;
import java.util.List;

public class LeakyBucketLimiter {
    // 容量
    private int capacity;
    // 速率
    private int rate;
    // 剩余容量
    private int left;
    // 请求队列
    private List<Request> requests;
    // 是否运行
    private volatile boolean isRunning = true;

    public LeakyBucketLimiter(int capacity, int rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.left = capacity;
        this.requests = new LinkedList<>();
    }

    public void handelRequest(Request request) {
        request.setHandleTime(System.currentTimeMillis());
        left++;
        if (left > capacity) {
            left = capacity;
        }
        System.out.println(request.toString());
    }

    public synchronized boolean tryAcquire(Request request) {
        if (left <= 0) {
            return false;
        } else {
            left--;
            requests.add(request);
            return true;
        }
    }

    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(isRunning) {
                    if(!requests.isEmpty()) {
                        Request request = requests.remove(0);
                        handelRequest(request);
                    }
                    try {
                        Thread.sleep(1000 / rate);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();;
    }

    public void stop() {
        isRunning = false;
    }

    public static void main(String[] args) throws InterruptedException {
        LeakyBucketLimiter limiter = new LeakyBucketLimiter(3, 5);
        limiter.start();
        int count = 0;
        for(int i = 0; i < 10; i++) {
            Thread.sleep(50);
            Request request = new Request(i, System.currentTimeMillis(), 0L);
            if(limiter.tryAcquire(request)) {
                count++;
                System.out.println("请求被接受");
            } else {
                System.out.println("请求被拒绝");
            }
        }
        System.out.println("请求通过数:" + count);
        Thread.sleep(10000);
        limiter.stop();
    }

    public static class Request {
        private int code;
        private long lanchTime;
        private long handleTime;

        public Request() {
        }

        public Request(int code, long lanchTime, long handleTime) {
            this.code = code;
            this.lanchTime = lanchTime;
            this.handleTime = handleTime;
        }

        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }

        public long getLanchTime() {
            return lanchTime;
        }
        public void setLanchTime(long lanchTime) {
            this.lanchTime = lanchTime;
        }
        public long getHandleTime() {
            return handleTime;
        }
        public void setHandleTime(long handleTime) {
            this.handleTime = handleTime;
        }
        @Override
        public String toString() {
            return "Request{" +
                    "code=" + code +
                    ", lanchTime=" + lanchTime +
                    ", handleTime=" + handleTime +
                    '}';
        }
    }
}

运行结果:

请求被接受
请求被接受
请求被接受
请求被接受
Request{code=0, lanchTime=1709280004490, handleTime=1709280004640}
请求被拒绝
请求被拒绝
请求被拒绝
Request{code=1, lanchTime=1709280004546, handleTime=1709280004861}
请求被接受
请求被拒绝
请求被拒绝
请求通过数:5
Request{code=2, lanchTime=1709280004596, handleTime=1709280005066}
Request{code=3, lanchTime=1709280004651, handleTime=1709280005267}
Request{code=7, lanchTime=1709280004867, handleTime=1709280005471}

3.3、特点分析:

  • 漏桶的漏出速率是固定的,对下游系统起到了保护的作用
  • 不能解决流量突发情况。

4、令牌桶

4.1、简介

令牌桶算法同样是实现限流是一种常见的思路,最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系。
系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加。
请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。
在这里插入图片描述

4.2、代码实现:

public class TokenBucketLimiter {
    private int capacity;
    private int rate;
    private int tokens;
    private volatile boolean isRunning = true;
    private static final int DEFAULT_CAPACITY = 10;
    private static final int DEFAULT_RATE = 5;

    public TokenBucketLimiter() {
        this(DEFAULT_CAPACITY, DEFAULT_RATE);
    }

    public TokenBucketLimiter(int capacity, int rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.tokens = capacity;
    }

    public synchronized boolean tryAcquire(Request request) {
        if (tokens > 0) {
            tokens--;
            handelRequest(request);
            return true;
        } else {
            return false;
        }
    }

    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRunning) {
                    synchronized(this) {
                        tokens++;
                        if (tokens > capacity) {
                            tokens = capacity;
                        }
                    }
                    try {
                        Thread.sleep(1000 / rate);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();;
    }

    public void stop() {
        isRunning = false;
    }

    public void handelRequest(Request request) {
        request.setHandleTime(System.currentTimeMillis());
        System.out.println(request.toString());
    }

    public static void main(String[] args) throws InterruptedException {
        TokenBucketLimiter limiter = new TokenBucketLimiter(3, 5);
        limiter.start();
        int count = 0;
        for(int i = 0; i < 10; i++) {
            Thread.sleep(50);
            Request request = new Request(i, System.currentTimeMillis(), 0L);
            if(limiter.tryAcquire(request)) {
                count++;
                System.out.println("请求被接受,当前时间:" + System.currentTimeMillis());
            } else {
                System.out.println("请求被拒绝,当前时间:" + System.currentTimeMillis());
            }
        }
        System.out.println("请求通过数:" + count);
        limiter.stop();
    }

    public static class Request {
        private int code;
        private long lanchTime;
        private long handleTime;

        public Request() {
        }

        public Request(int code, long lanchTime, long handleTime) {
            this.code = code;
            this.lanchTime = lanchTime;
            this.handleTime = handleTime;
        }

        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }

        public long getLanchTime() {
            return lanchTime;
        }
        public void setLanchTime(long lanchTime) {
            this.lanchTime = lanchTime;
        }
        public long getHandleTime() {
            return handleTime;
        }
        public void setHandleTime(long handleTime) {
            this.handleTime = handleTime;
        }
        @Override
        public String toString() {
            return "Request{" +
                    "code=" + code +
                    ", lanchTime=" + lanchTime +
                    ", handleTime=" + handleTime +
                    '}';
        }
    }
}

运行结果:

Request{code=0, lanchTime=1709279960998, handleTime=1709279960998}
请求被接受,当前时间:1709279961008
Request{code=1, lanchTime=1709279961061, handleTime=1709279961061}
请求被接受,当前时间:1709279961061
Request{code=2, lanchTime=1709279961111, handleTime=1709279961111}
请求被接受,当前时间:1709279961111
Request{code=3, lanchTime=1709279961162, handleTime=1709279961162}
请求被接受,当前时间:1709279961162
请求被拒绝,当前时间:1709279961214
请求被拒绝,当前时间:1709279961268
请求被拒绝,当前时间:1709279961323
Request{code=7, lanchTime=1709279961378, handleTime=1709279961378}
请求被接受,当前时间:1709279961378
请求被拒绝,当前时间:1709279961433
请求被拒绝,当前时间:1709279961485
请求通过数:5

4.3、特点分析:

  • 令牌桶算法是对漏桶算法的一种改进,除了能够在限制调用的平均速率的同时还允许一定程度的流量突发

参考

分布式限流
https://blog.51cto.com/u_15905482/6237162
参考:
https://www.wdbyte.com/java/rate-limiter/
https://juejin.cn/post/6870396751178629127

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1566522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32学习笔记(10_1)- I2C通信协议

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期开…

JMeter+Grafana+influxdb 配置出现transaction无数据情况解决办法

JMeterGrafanainfluxdb 配置出现transaction无数据情况解决办法 一、问题描述二、解决方法 一、问题描述 如下图所示出现application有数据但是transaction无数据情况 二、解决方法 需要做如下设置 打开变量设置如下图打开两个选项 然后再进行后端监听器的设置 如下图所…

【VUE】ruoyi框架自带页面可正常缓存,新页面缓存无效

ruoyi框架自带页面可正常缓存&#xff0c;新页面缓存无效 背景&#xff1a; 用若依框架进行开发时&#xff0c;发现ruoyi自带的页面缓存正常&#xff0c;而新开发的页面即使设置了缓存&#xff0c;当重新进入页面时依旧刷新了接口。 原因&#xff1a;页面name与 getRouters …

3个 JavaScript 字符串截取方法

在 JavaScript 中&#xff0c;可以使用 substr()、slice() 和 substring() 方法截取字符串. substring() substring() 方法返回一个字符串在开始索引到结束索引之间的一个子集&#xff0c;或从开始索引直到字符串的末尾的一个子集。语法如下&#xff1a; str.substring(inde…

GT收发器64B66B设计(1)IP核配置和example design

文章目录 前言一、IP核配置1.1、编码方式1.2、字节对齐和逗号码 二、example design 前言 在前面我们基于GT收发器进行了PHY层设计&#xff0c;其中采用的编码方式为8B10B&#xff0c;为进一步提高传输效率&#xff0c;从本文开始&#xff0c;将采用基于GT高速收发器采用64B66…

DC/DC选型:了解电感参数的基本含义

电感是一种相对简单的元件&#xff0c;它由缠绕在线圈中的绝缘线组成。但当单个元件组合在一起&#xff0c;用来创建具有适当尺寸、重量、温度、频率和电压的电感&#xff0c;同时又能满足目标应用时&#xff0c;复杂性就会增加。 选择电感时&#xff0c;了解电感数据手册中标…

京西商城——基于viewset视图集开发评论接口

在使用GenericAPIView和Mixins开发时&#xff0c;确实可以大大提高编码的速度以及减少代码量&#xff0c;但是在一个视图里并不能实现5个基础的请求方法&#xff0c;要用两个视图类来完成。所以我们可以使用viewset&#xff08;视图集&#xff09;来将两个视图类合并 如果要使…

Docker实战教程 第2章 Docker基础

3-1 Docker介绍 什么是Docker 虚拟化&#xff0c;容器 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&…

职场聚餐:搭建沟通桥梁,促进团队凝聚力

在职场中&#xff0c;聚餐作为一种非正式的社交活动&#xff0c;不仅能够增进同事间的了解&#xff0c;还有助于提升团队凝聚力。本文将探讨职场聚餐的重要性以及如何组织一场成功的职场聚餐。 一、职场聚餐的重要性 1. 搭建沟通桥梁&#xff1a;职场聚餐为员工提供了一个轻松愉…

Java中线程详解

文章目录 相关概念多线程概念实现方式继承Thread类实现Runnable接口比较 常用方法线程安全产生的原因解决思想同步同步代码块同步方法Lock锁机制 死锁概念避免 状态线程间的通讯介绍方法 相关概念 并行&#xff1a;在同一时刻&#xff0c;有多个任务在多个CPU上同时执行并发&a…

【JAVASE】学习类与对象的创建和实例化

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G-CSDN博客 目标&#xff1a; 1. 掌握类的定义方式以及对象的实例化 2. …

QML嵌套页面的实现学习记录

StackView是一个QML组件&#xff0c;用于管理和显示多个页面。它提供了向前和向后导航的功能&#xff0c;可以在堆栈中推入新页面&#xff0c;并在不需要时将页面弹出。 ApplicationWindow {id:rootvisible: truewidth: 340height: 480title: qsTr("Stack")// 抽屉:…

语义分割交互式智能标注工具 | 澳鹏数据标注平台

随着人工智能应用的大规模落地&#xff0c;数据标注市场在高速增长的同时&#xff0c;也面临着标注成本的挑战。据IDC报告显示&#xff1a;数据标注在AI应用开发过程中所耗费的时间占到了25%&#xff0c;部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

【Django学习笔记(五)】JQuery介绍

JQuery介绍 前言正文1、JQuery 快速上手1.1 下载 JQuery1.2 应用 JQuery 2、寻找标签&#xff08;直接&#xff09;2.1 ID选择器2.2 样式选择器2.3 标签选择器2.4 层级选择器2.5 多选择器2.5 属性选择器 3、寻找标签&#xff08;间接&#xff09;3.1 找到上一个兄弟3.2 找父子 …

浪潮分布式存储AS13000G6-M36改扩配后管理界面不能识别和标记硬盘的处理方法

AS13000G6 改配出问题的场景 浪潮分布式存储AS13000G6-M36渠道备货的分布式存储通常是流量机型&#xff0c;实际出货可能会涉及改配 集群部署完以后建议在系统视图下查看一下盘是否能识别 这个是正常的情况&#xff0c;可以正确管理到盘,硬盘侧边有绿色的指示灯。 如图是管理…

【Linux】进程控制详解

目录 前言 进程创建 认识fork 写时拷贝 再谈fork 进程终止 进程退出码 用代码来终止进程 常见的进程终止的方式 exit _exit 进程等待 进程等待的必要性 进程等待的方式 wait waitpid 详解status参数 详解option参数 前言 本文适合有一点基础的人看的&#…

计算机笔记(3)续20个

41.WWW浏览器和Web服务器都遵循http协议 42.NTSC制式30帧/s 44.三种制式电视&#xff1a;NTSC&#xff0c;PAL&#xff0c;SECAM 45.IP&#xff0c;子网掩码白话文简述&#xff1a; A类地址&#xff1a;取值范围0-127&#xff08;四段数字&#xff08;127.0.0.0&#xff09…

Oil Deposits (DFS BFS)

//新生训练 #include <iostream> #include <algorithm> #include <cstring> #include <queue> using namespace std; using PII pair<int, int>; const int N 205; int n, m; int dis[N][N]; int dx[] {0, 0, -1, 1, -1, 1, -1, 1}; int dy[]…

JAVAEE之Spring, Spring Boot 和Spring MVC的关系以及区别

1.Spring, Spring Boot 和Spring MVC的关系以及区别 Spring: 简单来说, Spring 是⼀个开发应⽤框架&#xff0c;什么样的框架呢&#xff0c;有这么⼏个标签&#xff1a;轻量级、⼀ 站式、模块化&#xff0c;其⽬的是⽤于简化企业级应⽤程序开发 Spring的主要功能: 管理对象&am…

《pytorch深度学习实战》学习笔记第1章

第1章 深度学习和pytorch库简介 1.1 深度学习革命 机器学习依赖特征工程。而深度学习是从原始数据中自动找出这样的特征。 1.2 pytorhc深度学习 pytorch是一个python程序库。pytorch为深度学习提供了入门指南。pytorch的核心数据结构——张量&#xff0c;为一个多维数组&…