4种经典的限流算法

0、基础知识

1000毫秒内,允许2个请求,其他请求全部拒绝。

不拒绝就可能往db打请求,把db干爆~

interval = 1000 

rate = 2;

一、固定窗口限流

固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一种最简单的限流算法,其原理是在固定时间窗口(单位时间)内限制请求的数量。通俗点说主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。

/**
 * 固定窗口限流算法
 * 比如1000ms 允许通过10个请求
 * @author jeb_lin
 * 14:14 2023/11/19
 */
public class FixWindowRateLimiter {

    private long interval; // 窗口的时间间隔
    private long rate; // 限制的调用次数
    private long lastTimeStamp; // 上次请求来的时间戳
    private AtomicLong counter; // 计数器

    public FixWindowRateLimiter(long interval,long rate){
        this.interval = interval;
        this.rate = rate;
        this.lastTimeStamp = System.currentTimeMillis();
        this.counter = new AtomicLong(0);
    }

    public static void main(String[] args) {
        // 比如1000ms 允许通过10个请求
        FixWindowRateLimiter limiter = new FixWindowRateLimiter(1000,2);

        for (int i = 0; i < 4; i++) {
            if(limiter.allow()){
                System.out.println(i + " -> ok ");
            } else {
                System.out.println(i + " -> no");
            }
        }


    }

    private boolean allow() {
        long now = System.currentTimeMillis();
        if(now - lastTimeStamp > interval){
            counter.set(0L);
            lastTimeStamp = now;
        }

        if(counter.get() >= rate){
            return false;
        } else {
            counter.incrementAndGet();
            return true;
        }
    }
}

输出:

0 -> ok 
1 -> ok 
2 -> no
3 -> no

优点

  1. 简单易懂

缺陷

  1. 存在临界问题

本来就允许1秒内进来2个请求,这时候进来了4个

 /**
     * 测试不正常的情况
     */
    private static void testUnNormal() throws Exception{
        // 比如1000ms 允许通过10个请求
        FixWindowRateLimiter limiter = new FixWindowRateLimiter(1000,2);

        Thread.sleep(500);
        for (int i = 0; i < 4; i++) {
            if(limiter.allow()){
                System.out.println(i + " -> ok ");
            } else {
                System.out.println(i + " -> no");
            }
            Thread.sleep(250);
        }
    }

输出:

0 -> ok 
1 -> ok 
2 -> ok 
3 -> ok 

二、滑动窗口限流

滑动窗口限流算法是一种常用的限流算法,它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期。比如上图的示例中,每 500ms 滑动一次窗口就可以避免这种临界问题,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。

类似于微积分:假如我切成1000个窗口,每1ms一个计数器

/**
 * 滑动窗口限流算法
 * 比如1000ms 允许通过 2 个请求
 *
 * @author jeb_lin
 * 14:14 2023/11/19
 */
public class SlidingWindowRateLimiter {

    private long interval; // 窗口的时间间隔
    private long maxRequest; // 限制的调用次数
    private Map<Long, AtomicLong> millToCount = null; // 假如1秒切成1000个窗口,也就是1毫秒一个计数器
    private LinkedList<Long> timeStampList = null; // 出现请求的那个时间戳,需要记录下来,用于后期的删除
    private AtomicLong counter; // 计数器

    public SlidingWindowRateLimiter(long interval, long maxRequest) {
        this.interval = interval;
        this.maxRequest = maxRequest;
        this.millToCount = new HashMap<>();
        this.timeStampList = new LinkedList<>();
        this.counter = new AtomicLong(0);
    }

    public static void main(String[] args) throws Exception {
        testNormal();
    }


    /**
     * 测试正常的情况
     */
    private static void testNormal() {
        // 比如1000ms 允许通过10个请求
        SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(1000, 2);

        for (int i = 0; i < 10; i++) {
            if (limiter.allow()) {
                System.out.println(i + " -> ok ");
            } else {
                System.out.println(i + " -> no");
            }
        }
    }

    private boolean allow() {
        long now = System.currentTimeMillis();

        // 剔除掉过期的窗口,比如现在是1001ms,那么1ms的窗口就需要剔除掉
        while (!timeStampList.isEmpty() && now - interval > timeStampList.getFirst()) {
            long timeStamp = timeStampList.poll();
            for (int i = 0; i < millToCount.getOrDefault(timeStamp,new AtomicLong(0)).get(); i++) {
                counter.decrementAndGet();
            }
            millToCount.remove(timeStamp);
        }

        if (counter.get() >= maxRequest) {
            return false;
        } else {
            timeStampList.add(now); // 当前出现成功请求,那么串上list

            AtomicLong timeStampCounter = millToCount.getOrDefault(now, new AtomicLong(0L));
            timeStampCounter.incrementAndGet();
            millToCount.put(now, timeStampCounter);
            counter.incrementAndGet();
            return true;
        }
    }
}

优点

  1. 简单易懂
  2. 精度高(通过调整时间窗口的大小来实现不同的限流效果)

缺陷

  1. 依旧存在临界问题,不可能无限小
  2. 占用更多内存空间

三、漏桶算法(固定消费速率)

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

类似于Kafka的消费者,在不调整配置的情况下,消费速度是固定的,多余的请求会积压,如果超过你kafka配置的topic最大磁盘容量,那么会丢消息。(topic是一个目录,topic下N个partition目录,假如你每个partition配置了1G的容量,那么超过这个这个容量,就会删除partition下的segement文件 xxx.index,xxx.log)

/**
 * 漏桶算法
 * 比如 1秒只能消费2个请求
 *
 * @author jeb_lin
 * 15:55 2023/11/19
 */
public class LeakyBucketRateLimiter {

    private int consumerRate; // 消费速度
    private Long interval; // 时间间隔,比如1000ms
    private int bucketCapacity; // 桶的容量
    private AtomicLong water; // 桶里面水滴数量

    public LeakyBucketRateLimiter(int consumerRate, Long interval, int bucketCapacity) {
        this.consumerRate = consumerRate;
        this.interval = interval;
        this.bucketCapacity = bucketCapacity;
        this.water = new AtomicLong(0);
        scheduledTask();
    }

    // 周期任务,比如每1000ms消费2个请求
    private void scheduledTask() {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate((Runnable) () -> {
            for (int i = 0; i < consumerRate && water.get() > 0; i++) {
                this.water.decrementAndGet();
            }
            System.out.println("water -> " + this.water.get());
        }, 0, interval, TimeUnit.MILLISECONDS);
    }

    public static void main(String[] args) {
        // 1000毫秒消费2个请求
        LeakyBucketRateLimiter limiter = new LeakyBucketRateLimiter(2, 1000L, 10);

        for (int i = 0; i < 10; i++) {
            if (limiter.allow()) {
                System.out.println(i + "-> ok");
            } else {
                System.out.println(i + "-> no");
            }
        }

    }

    private boolean allow() {
        if (bucketCapacity < water.get() + 1) {
            return false;
        } else {
            water.incrementAndGet();
            return true;
        }
    }
}

输出:

0 -> ok 
1 -> ok 
2 -> no
3 -> no
4 -> no
5 -> no
6 -> no
7 -> no
8 -> no
9 -> no

优点

  1. 可以控制请求的处理速度,避免过载或者过度闲置,避免瞬间请求过多导致系统崩溃或者雪崩。
  2. 可以通过调整桶的大小和漏出速率来满足不同的限流需求(这个基本靠手动)

缺陷

  1. 需要对请求进行缓存,会增加服务器的内存消耗。
  2. 但是面对突发流量的时候,漏桶算法还是循规蹈矩地按照固定的速率处理请求。

四、令牌桶算法

令牌桶算法是一种常用的限流算法,相对于漏桶算法,它可以更好地应对突发流量和解决内存消耗的问题。该算法维护一个固定容量的令牌桶,每秒钟会向令牌桶中放入一定数量的令牌。当有请求到来时,如果令牌桶中有足够的令牌,则请求被允许通过并从令牌桶中消耗一个令牌,否则请求被拒绝。

4.2 令牌桶限流代码实现


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 令牌桶限流算法
 * 每 1000ms 生成2个令牌
 *
 * @author jeb_lin
 * 14:14 2023/11/19
 */
public class TokenBucketRateLimiter {

    private long interval; // 窗口的时间间隔
    private long rate; // 速率
    private AtomicLong tokenCounter; // 令牌的数量
    private final long bucketCapacity; // 桶的大小

    public TokenBucketRateLimiter(long interval, long rate ,long bucketCapacity) {
        this.interval = interval;
        this.rate = rate;
        this.tokenCounter = new AtomicLong(0);
        this.bucketCapacity = bucketCapacity;
        scheduledProduceToken();
    }

    private void scheduledProduceToken() {
        ExecutorService executorService = Executors.newScheduledThreadPool(1);
        ((ScheduledExecutorService) executorService).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 桶里面放不下了
                if(tokenCounter.get() + rate > bucketCapacity){
                    System.out.println("bucket is full");
                    return;
                }
                long token = tokenCounter.addAndGet(rate);
                System.out.println("token -> " + token);
            }
        }, 0, interval, TimeUnit.MILLISECONDS);
    }

    public static void main(String[] args) throws Exception {
        testNormal();
    }

    /**
     * 测试正常的情况
     */
    private static void testNormal() throws Exception{
        // 比如1000ms 允许通过10个请求
        TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1000, 2,10);
        // 模拟瞬时流量,5秒前都没请求,5秒后瞬时流量上来了
        Thread.sleep(5000);
        for (int i = 0; i < 12; i++) {
            if (limiter.allow()) {
                System.out.println(i + " -> ok ");
            } else {
                System.out.println(i + " -> no");
            }
        }
    }

    private boolean allow() {
        if(tokenCounter.get() > 0){
            tokenCounter.getAndDecrement();
            return true;
        }

        return false;
    }
}

输出:

token -> 2
token -> 4
token -> 6
token -> 8
token -> 10
bucket is full
0 -> ok 
1 -> ok 
2 -> ok 
3 -> ok 
4 -> ok 
5 -> ok 
6 -> ok 
7 -> ok 
8 -> ok 
9 -> ok 
10 -> no
11 -> no

优点

Guava的RateLimiter限流组件,就是基于令牌桶算法实现的。

  1. 精度高:令牌桶算法可以根据实际情况动态调整生成令牌的速率,可以实现较高精度的限流。
  2. 弹性好:令牌桶算法可以处理突发流量,可以在短时间内提供更多的处理能力,以处理突发流量。

缺陷

  1. 实现复杂:在短时间内有大量请求到来时,可能会导致令牌桶中的令牌被快速消耗完,从而限流。这种情况下,可以考虑使用漏桶算法。(需要削峰填谷,学习kafka就用漏桶算法)
  2. 时间精度要求高:令牌桶算法需要在固定的时间间隔内生成令牌,因此要求时间精度较高,如果系统时间不准确,可能会导致限流效果不理想。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三相异步电机动态数学模型及矢量控制仿真

文章目录 三相异步电机动态数学模型及矢量控制仿真1、异步电机三相方程2、坐标变换3、磁链3/2变换推导4、两相静止坐标系下的方程5、两相旋转坐标系下的方程6、以 ω-is-Ψr 为状态变量的状态方程7、矢量控制及 matlab 仿真 原文链接需要仿真的同学请关注【Qin的学习营地】 三相…

Node.js之fs文件系统模块

什么是fs文件系统模块&#xff1f;又如何使用呢&#xff1f;让我为大家介绍一下&#xff01; fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性&#xff0c;用来满足用户对文件的操作需求 注意&#xff1a;如果要在JavaScript代码中&#xff0c…

OS 进程同步

基本概念 定义&#xff1a;把异步环境下的一组并发进程因直接制约而相互发送消息、相互合作、相互等待&#xff0c;使得各进程按一定的速度执行的过程&#xff0c;称为进程同步 协作进程&#xff1a;具有同步关系的一组并发进程 进程同步机制的主要任务&#xff1a;在执行次…

pm2在Windows环境中的使用

pm2 进程管理工具可以Windows操作系统上运行&#xff0c;当一台Windows电脑上需要运行多个进程时&#xff0c;或者运维时需要运行多个进程以提供服务时。可以使用pm2&#xff0c;而不再是使用脚本。 1. 使用PM2管理进程 1.1. 启动PM2项目 1.1.1. 直接启动项目 参数说明&…

Transformer ZOO

Natural Language Processing Transformer:Attention is all you need URL(46589)2017.6 提出Attention机制可以替代卷积框架。引入Position Encoding&#xff0c;用来为序列添加前后文关系。注意力机制中包含了全局信息自注意力机制在建模序列数据中的长期依赖关系方面表现出…

iOS_折叠展开 FoldTextView

1. 显示效果 Test1&#xff1a;直接使用&#xff1a; Test2&#xff1a;在 cell 里使用&#xff1a; 2. 使用 2.1 直接使用 // 1.1 init view private lazy var mooFoldTextView: MOOFoldTextView {let view MOOFoldTextView(frame: .zero)view.backgroundColor .cyanvie…

QGroundControl源码编译的三种方法

1.使用QtCreator编译: 下载qgroundcontrol源码 https://github.com/mavlink/qgroundcontrol.git 克隆 同步子模块 使用打开qgroundcontrol.pro 打开前要求先安装qt 5.15.2

嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B

嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B 简介 FM25CL64B是赛普拉斯cypress出品的一款铁电存储器&#xff0c;这种存储器最大的优势是可以像RAM一样随机存储&#xff0c;和按字节写入&#xff0c;也可以像ROM一样掉电仍然可以保存数据&#xff0c;是一种相当优秀的…

重命名com1.{d3e34b21-9d75-101a-8c3d-00aa001a1652}文件夹

今天在win10系统上&#xff0c;发现一个名称为: com1.{d3e34b21-9d75-101a-8c3d-00aa001a1652} 的文件夹&#xff0c;该文件夹很奇怪&#xff0c;既不能手动删除&#xff0c;也不能手动给文件夹重命名&#xff0c;如图(1)所示&#xff1a; E:\EncodeOne\hello\Thumbs.ms\com1.…

【神印王座】月夜大尺度诱惑,皓晨潜入月魔宫,枫秀降临男扮女装

Hello,小伙伴们&#xff0c;我是拾荒君。 为了能安全回到联盟&#xff0c;龙皓晨决定让月夜商队护送他们&#xff0c;这也是他们目前处境更快更安全回到人类境地的方法。于是&#xff0c;龙皓晨只身一人去寻找月夜&#xff0c;此次执行的任务完全超出龙皓晨的掌握之外&#xf…

中国电影票房排行数据爬取及分析可视化

大家好&#xff0c;我是带我去滑雪&#xff01; 对中国电影票房排行数据的爬取和分析可视化具有多方面的用处&#xff1a;例如了解电影市场的历史趋势&#xff0c;包括不同类型电影的受欢迎程度、票房的季节性波动。识别观众对于不同类型电影的偏好&#xff0c;为电影制片方提供…

Nodejs中net模块多次Socket.setTimeout无法覆盖之前函数,导致叠加执行问题解决

Hi, I’m Shendi Nodejs中net模块多次Socket.setTimeout无法覆盖之前函数&#xff0c;导致叠加执行问题解决 问题描述 在 Nodejs 中&#xff0c;net 模块的 Socket 的 setTimeout 函数是设置超时时间&#xff0c;如果多次设置&#xff0c;超时时间会是最后一次的时间&#xff…

4、FFmpeg命令行操作10

音视频处理流程 先看两条命令 ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx264 -s 1280x720 test_1280x720.flv ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx265 -s 1280x720 test_1280x720.mkv ffmpeg音视频处理流程

038、语义分割

之——介绍与数据集 杂谈 语义分割&#xff0c;语义分割(Semantic Segmentation)方法-CSDN博客&#xff1a; 语义分割是计算机视觉领域的一项重要任务&#xff0c;旨在将图像中的每个像素分配到其对应的语义类别中。与物体检测或图像分类不同&#xff0c;语义分割不仅要识别图像…

Zookeeper实战案例(1)

前置知识&#xff1a; Zookeeper学习笔记&#xff08;1&#xff09;—— 基础知识-CSDN博客 Zookeeper学习笔记&#xff08;2&#xff09;—— Zookeeper API简单操作-CSDN博客 Zookeeper 服务器动态上下线监听案例 需求分析 某分布式系统中&#xff0c;主节点可以有多台&am…

CV计算机视觉每日开源代码Paper with code速览-2023.11.15

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构&#xff1a;CNN】PadChannel: Improving CNN Performance through Explicit Padding Encoding 论文地址&#xff1a;https:/…

AUTODL云服务器使用大致步骤(适合本人版)

(一)在官网上创建一个服务器 (二)远程连接指令&#xff1a; 改为&#xff1a; (三)连接后&#xff0c;可在中进行代码运行 输入一些指令 python ......

基于go标准分层架构项目设计实现

基于go标准分层架构项目设计实现 缘起 个人博客网址 最近主要看了两方面知识&#xff0c;一方面是技术相关的&#xff0c;如何设计一个比较好的后端架构项目代码&#xff1b;一方面是非技术相关的&#xff0c;如何写一篇好的技术文章&#xff0c;能够让他人读懂并有收获。因…

基于STM32CubeMX和keil采用RTC时钟周期唤醒和闹钟实现LED与BEEP周期开关

文章目录 前言1. RTC概念1.1 RTC的时钟信号源1.2 预分频器1.3 实时时钟与日历数据1.4 周期性自动唤醒1.5 可编程闹钟 2. RTC相关中断3. STM32CubeMX配置3.1 时钟配置3.2 引脚配置3.3 RTC配置3.3.1 模式选择3.3.2 RTC基本参数配置3.3 中断配置 4. 代码编写总结 前言 RTC的功能有…

2023最新最全【内网渗透工具】零基础安装教程

1.1 简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协议&#xff08;访问内网网站、本地支付接口调试、ssh访问、远程桌面&#xff0c;内网dns解析等等……&#xff09;&#xff0c;此外还支持内网…