一文秒解四大经典限流算法

阅读前提:没有最好的算法,只有最适合的算法!

限流算法:

  • 固定窗口限流算法

  • 滑动窗口限流算法

  • 漏桶限流算法

  • 令牌桶限流算法

固定窗口限流算法

介绍

固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一种最简单的限流算法,其原理是在固定时间窗口(单位时间)内限制请求的数量。该算法将时间分成固定的窗口,并在每个窗口内限制请求的数量。具体来说,算法将请求按照时间顺序放入时间窗口中,并计算该时间窗口内的请求数量,如果请求数量超出了限制,则拒绝该请求。

这个算法就是说:在一个固定时间段内,可以接收固定数量的请求。拒绝多余的请求。

实现

下面是用Java的实现:

我们可以根据自己需要来调整 maxRequestswindowTimeMillis 参数来设置限流策略。

import java.util.concurrent.atomic.AtomicInteger;
​
public class FixedWindowRateLimiter {
    private final int maxRequests; // 最大请求数量
    private final long windowTimeMillis; // 时间窗口大小(毫秒)
    private final long[] timestamps; // 存储时间戳的数组
    private final AtomicInteger count; // 记录当前请求数量的原子整数
​
    public FixedWindowRateLimiter(int maxRequests, long windowTimeMillis) {
        this.maxRequests = maxRequests;
        this.windowTimeMillis = windowTimeMillis;
        this.timestamps = new long[maxRequests];
        this.count = new AtomicInteger(0);
    }
​
    public synchronized boolean allowRequest() {
        long now = System.currentTimeMillis();
        int currentCount = count.get();
        
        if (currentCount < maxRequests) {
            // 如果当前请求数量未达到最大限制,允许该请求
            count.incrementAndGet();
            timestamps[currentCount] = now;
            return true;
        } else {
            // 检查窗口中最早的请求是否已经过期
            long oldestTimestamp = timestamps[0];
            if (now - oldestTimestamp > windowTimeMillis) {
                // 如果已过期,重置时间窗口并允许该请求
                resetWindow(now);
                count.incrementAndGet();
                timestamps[0] = now;
                return true;
            } else {
                // 如果未过期,则拒绝该请求
                return false;
            }
        }
    }
​
    private void resetWindow(long now) {
        // 重置时间窗口,即将时间戳向前移动并重置请求数量
        int currentCount = count.get();
        for (int i = 1; i < currentCount; i++) {
            timestamps[i - 1] = timestamps[i];
        }
        count.set(0);
    }
​
    public static void main(String[] args) {
        // 示例用法
        FixedWindowRateLimiter rateLimiter = new FixedWindowRateLimiter(5, 60000); // 每分钟限制5个请求
        for (int i = 0; i < 10; i++) {
            if (rateLimiter.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 拒绝");
            }
            try {
                Thread.sleep(1000); // 模拟请求之间的一些延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​

优缺点分析

  • 优点:算法简单,易于实现和理解。

  • 缺点:存在明显的临界问题。比如说:我们设置的是1分钟内,可以接收10个请求。如果在59s的时候,突然来了10个请求,然后在下个一分钟的时候,又来了10个请求。相当于2s内来了20个请求,这使得并发量极高,这不明显的没有起到限流的作用吗?

滑动窗口限流算法

滑动窗口限流算法是一种常用的限流算法,用于控制系统对外提供服务的速率,防止系统被过多的请求压垮。它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期。它可以解决固定窗口临界值的问题

个人理解:滑动窗口限流就是将一个时间段划分为多个小区间。比如说在一分钟内我们可以限流30个请求,然后我们将每2秒划分为一个小区间,来进行滑动窗口限流

实现

windowSize表示窗口大小(单位:秒),maxRequests表示每个窗口内允许的最大请求数量。使用Deque来存储请求时间戳队列。allowRequest方法用于判断是否允许请求通过,它会根据当前时间戳和窗口大小来判断请求是否在窗口内,并根据窗口内的请求数量来决定是否允许请求通过。

package com.pxl.test.sf;
​
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Deque;
​
/**
 * 滑动窗口限流算法实现
 */
public class SlidingWindowRateLimiter {
    private final int windowSize; // 窗口大小(单位:秒)
    private final int maxRequests; // 每个窗口内允许的最大请求数量
    private final Deque<Instant> requestTimes; // 请求时间戳队列
​
    /**
     * 构造函数
     * @param windowSize 窗口大小(单位:秒)
     * @param maxRequests 每个窗口内允许的最大请求数量
     */
    public SlidingWindowRateLimiter(int windowSize, int maxRequests) {
        this.windowSize = windowSize;
        this.maxRequests = maxRequests;
        this.requestTimes = new ArrayDeque<>();
    }
​
    /**
     * 判断是否允许请求通过
     * @return 如果允许请求通过返回true,否则返回false
     */
    public synchronized boolean allowRequest() {
        Instant now = Instant.now();
        Instant windowStart = now.minusSeconds(windowSize);
​
        // 移除窗口外的请求时间戳
        while (!requestTimes.isEmpty() && requestTimes.peek().isBefore(windowStart)) {
            requestTimes.poll();
        }
​
        // 判断窗口内请求数量是否超过阈值
        if (requestTimes.size() < maxRequests) {
            requestTimes.offer(now);
            return true; // 允许请求通过
        } else {
            return false; // 拒绝请求
        }
    }
​
    /**
     * 主函数,用于测试滑动窗口限流算法
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(5, 1); // 窗口大小为5秒,每个窗口内最多处理1个请求
        for (int i = 0; i < 20; i++) {
            if (rateLimiter.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 拒绝");
            }
            try {
                Thread.sleep(1000); // 模拟请求之间的延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

优缺点分析

  • 优点

    • 简单易懂

    • 精度高(可调整时间窗口大小来实现不同的限流效果)

    • 可扩展性强(可以容易的与其他限流算法结合使用)

  • 缺点

    • 突发流量无法处理(无法应对短时间内的大量请求,但是一旦到达限流后,请求都会直接暴力被拒绝。这样会导致我们损失一部分请求,这其实对于产品来说,并不太友好),需要合理调整时间窗口大小。

漏桶限流算法

介绍

漏桶限流算法(Leaky Bucket Algorithm)是一种流量控制算法,用于控制流入网络的数据速率,以防止网络拥塞。它的思想是将数据包看作是水滴,漏桶看作是一个固定容量的水桶,数据包像水滴一样从桶的顶部流入桶中,并通过桶底的一个小孔以一定的速度流出,从而限制了数据包的流量。

漏桶限流算法的基本工作原理是:对于每个到来的数据包,都将其加入到漏桶中,并检查漏桶中当前的水量是否超过了漏桶的容量。如果超过了容量,就将多余的数据包丢弃。如果漏桶中还有水,就以一定的速率从桶底输出数据包,保证输出的速率不超过预设的速率,从而达到限流的目的。

实现

我们可以根据需要调整 capacity(漏桶容量)和 ratePerSecond(漏桶速率)来设置限流策略。

import java.util.concurrent.atomic.AtomicLong;
​
public class LeakyBucketRateLimiter {
    private final long capacity; // 漏桶容量
    private final long ratePerSecond; // 漏桶速率(每秒处理请求数量)
    private long water; // 当前漏桶中的水量
    private long lastLeakTime; // 上一次漏水的时间
​
    public LeakyBucketRateLimiter(long capacity, long ratePerSecond) {
        this.capacity = capacity;
        this.ratePerSecond = ratePerSecond;
        this.water = 0;
        this.lastLeakTime = System.currentTimeMillis();
    }
​
    public synchronized boolean allowRequest() {
        long now = System.currentTimeMillis();
        // 计算当前时间距离上一次漏水的时间间隔
        long elapsedTime = now - lastLeakTime;
        // 计算漏桶在此时间间隔内漏掉的水量
        long leakedWater = elapsedTime * ratePerSecond / 1000;
        // 更新漏桶中的水量
        water = Math.max(0, water - leakedWater);
        // 更新上一次漏水的时间
        lastLeakTime = now;
        // 判断漏桶中的水量是否小于漏桶容量,如果小于则允许请求通过
        if (water < capacity) {
            water++;
            return true;
        } else {
            // 漏桶已满,拒绝请求
            return false;
        }
    }
​
    public static void main(String[] args) {
        // 示例用法
        LeakyBucketRateLimiter rateLimiter = new LeakyBucketRateLimiter(10, 2); // 漏桶容量为10,速率为每秒2个请求
        for (int i = 0; i < 20; i++) {
            if (rateLimiter.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 拒绝");
            }
            try {
                Thread.sleep(500); // 模拟请求之间的一些延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​

优缺点分析

  • 优点

    • 可以平滑限制请求处理速度,避免瞬间请求增多导致系统崩溃。

    • 可以控制请求处理速度,使系统可以适应不同的流量需求,避免过载或过度闲置。

    • 可以调整桶的大小和漏出速率来满足不同限流需求,灵活地适应不同场景。

  • 缺点

    • 需要对请求进行缓存,增加服务器的内存消耗

    • 固定速率的处理请求

    • 不适应突发流量情况

适用于平滑流量、限制速率以及防止突发流量等场景。然而,在处理突发流量和动态调整处理速率等方面,漏桶算法存在一些局限性,需要根据具体的业务需求和系统特点进行选择和调整。

令牌桶限流算法

令牌桶算法是一种常用的限流算法,可以用于限制单位时间内请求的数量。该算法维护一个固定容量的令牌桶,每秒钟会向令牌桶中放入一定数量的令牌。当有请求到来时,如果令牌桶中有足够的令牌,则请求被允许通过并从令牌桶中消耗一个令牌,否则请求被拒绝。

image-20240402141033407

实现

可以根据需要调整 capacity(令牌桶容量)和 ratePerSecond(令牌生成速率)来设置限流策略。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
​
public class TokenBucketRateLimiter {
    private final int capacity; // 令牌桶容量
    private final int ratePerSecond; // 令牌生成速率(每秒生成令牌数量)
    private BlockingQueue<Object> tokens; // 令牌桶,使用阻塞队列实现
​
    public TokenBucketRateLimiter(int capacity, int ratePerSecond) {
        this.capacity = capacity;
        this.ratePerSecond = ratePerSecond;
        this.tokens = new LinkedBlockingQueue<>(capacity);
        // 初始化令牌桶,将令牌添加到队列中
        for (int i = 0; i < capacity; i++) {
            tokens.offer(new Object());
        }
        // 启动令牌生成线程,每秒生成指定数量的令牌
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000 / ratePerSecond);
                    tokens.offer(new Object());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
​
    public boolean allowRequest() {
        return tokens.poll() != null; // 尝试从令牌桶中取出一个令牌,如果成功取出则允许请求通过
    }
​
    public static void main(String[] args) {
        // 示例用法
        TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(10, 2); // 令牌桶容量为10,速率为每秒2个令牌
        for (int i = 0; i < 20; i++) {
            if (rateLimiter.allowRequest()) {
                System.out.println("请求 " + (i + 1) + ": 允许");
            } else {
                System.out.println("请求 " + (i + 1) + ": 拒绝");
            }
            try {
                Thread.sleep(500); // 模拟请求之间的一些延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​

优缺点分析

  • 优点

    • 稳定性高:令牌桶可以控制请求处理速度,使系统负载稳定

    • 灵活性好:令牌桶算法可以通过调整令牌生成速率来动态控制请求的处理速率,从而适应不同的流量情况和系统负载。

    • 允许突发流量:在令牌桶中存在一定数量的令牌,可以应对一定程度的突发流量,避免突发流量导致请求拒绝或排队等待。

  • 缺点

    • 实现复杂:相对其他限流算法,令牌桶的实现较复杂。对短时间内大量请求到来,可能导致令牌桶中的令牌消耗完,从而限流。这种情况可以考虑漏桶算法。

    • 时间精度要求高:需要在固定时间间隔内生成令牌,因此要求时间精度高,如果系统时间不准,可能导致限流效果不佳。

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

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

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

相关文章

乐校园二手书交易管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)大学生闲置二手书在线销售

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

《深入Linux内核架构》第3章 内存管理(6)

目录 3.5.7 内核中不连续页的分配 3.5.8 内核映射 本节讲解vmalloc, vmap&#xff0c;kmap原理。 3.5.7 内核中不连续页的分配 kmalloc函数&#xff1a;分配物理地址和虚拟地址都连续的内存。 kmalloc基于slab&#xff0c;而slab基于伙伴系统。 void *vmalloc(unsigned lon…

普通人的进化方法论,成为真正精英的秘诀

一、资料前言 本套个人成长资料&#xff0c;大小37.38M&#xff0c;共有25个文件。 二、资料目录 第01期&#xff1a;塑造心灵造就强大个体.pdf 第02期&#xff1a;用认知能力打开新世界.pdf 第03期&#xff1a;如何解开“不知如何做选择”的谜题.pdf 第04期 为什么我们总…

【JavaSE】解密 继承和多态(上)

前言 本篇将会通过典型代码案例来揭开 Java中继承和多态 的神秘面纱~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 继承 继承代码举例 子类访问父类的成员变量和方法 子类访问父类的成员变量 super this和su…

vue源码解析——vue如何将template转换为render函数

Vue 将模板&#xff08;template&#xff09;转换为渲染函数&#xff08;render function&#xff09;是 Vue 编译器的核心功能&#xff0c;它是 Vue 实现响应式和虚拟 DOM 的关键步骤。在 Vue 中&#xff0c;模板&#xff08;template&#xff09;是开发者编写的类似 HTML 的代…

uni-app项目打包步骤和踩过的坑(二)

书接上回&#xff0c;上一篇文章写道我利用Android Studio打包uni-app的项目&#xff0c;不知道填写那个数据签证的问题&#xff0c;而且即使能成功打包出的apk在运行时候一直报未配置appkey或配置错误 期间尝试了多种网络上的方式都出现问题&#xff0c;而且我还切换Android S…

【数据库】锁表原因及处理

文章目录 什么是数据库锁表&#xff1f;数据库锁表可能会导致什么问题&#xff1f;死锁问题的原因分析如何避免数据库锁表&#xff1f;解决死锁问题的常用策略解决死锁问题mysql锁表处理ORACEL数据库锁表处理SQL Server数据库锁表处理 来源 什么是数据库锁表&#xff1f; 答&a…

【LeetCode热题100】124.二叉树的最大路径和(二叉树)

一.题目要求 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root …

百度行驶证C++离线SDK V1.1 C#接入

百度行驶证C离线SDK V1.1 C#接入 目录 说明 效果 项目 代码 说明 自己根据SDK封装了动态库&#xff0c;然后C#调用。 SDK包结构 效果 项目 代码 using Newtonsoft.Json; using System; using System.Drawing; using System.Runtime.InteropServices; using System.Text;…

Python基础之pandas:文件读取与数据处理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、文件读取1.以pd.read_csv()为例&#xff1a;2.数据查看 二、数据离散化、排序1.pd.cut()离散化&#xff0c;以按范围加标签为例2. pd.qcut()实现离散化3.排序4.…

走进《与凤行》感受维达棉韧联名魅力

随着《与凤行》的热播&#xff0c;维达也陆续推出了各个纸抽系列的联名&#xff0c;棉韧联名就是这样一款结合了维达品牌优质棉韧面巾纸和《与凤行》IP元素的产品。这款软抽不仅在质地上保持了维达棉韧系列的柔软舒适&#xff0c;还融入了《与凤行》的设计元素&#xff0c;为用…

L2-036 网红点打卡攻略 ( 模拟题 )

本题链接&#xff1a;PTA | 程序设计类实验辅助教学平台 题目&#xff1a; 样例&#xff1a; 输入 6 13 0 5 2 6 2 2 6 0 1 3 4 2 1 5 2 2 5 1 3 1 1 4 1 2 1 6 1 6 3 2 1 2 1 4 5 3 2 0 2 7 6 5 1 4 3 6 2 6 5 2 1 6 3 4 8 6 2 1 6 3 4 5 2 3 2 1 5 6 6 1 3 4 5 2 7 6 2 1 3…

ubuntu20.04 运行 lio-sam 流程记录

ubuntu20.04 运行 lio-sam 一、安装和编译1.1、安装 ROS11.2、安装 gtsam1.3、安装依赖1.4、下载源码1.5、修改文件1.6、编译和运行 二、官方数据集的运行2.1、casual_walk_2.bag2.2、outdoor.bag、west.bag2.3、park.bag 三、一些比较好的参考链接 记录流程&#xff0c;方便自…

管理项目有哪些好用的系统?

不论在公司是什么角色&#xff0c;不过不管是负责哪一块&#xff0c;项目型公司的管理难点都会经历过&#xff0c;特别是中小型的做建筑装饰类的业务。 一般都会存在合同进度统计难、项目成本管控难、上下游结算难等问题&#xff0c;除了资金方面的原因&#xff0c;也有数据核…

c++对象指针

对象指针在使用之前必须先进行初始化。可以让它指向一个已定义的对象&#xff0c;也可以用new运算符动态建立堆对象。 定义对象指针的格式为&#xff1a; 类名 *对象指针 &对象; //或者 类名 *对象指针 new 类名(参数); 用对象指针访问对象数据成员的格式为&#xff1a…

ubuntu16.04不能在主机和虚拟机之间拷贝文本

问题 ubuntu16.04不能在主机和虚拟机之间拷贝文本。 原因 vmware tools没安装好。 解决办法 让虚拟机加载C:\Program Files (x86)\VMware\VMware Workstation\linux.iso光盘文件&#xff0c;设置如下&#xff1a; 拷贝虚拟机光盘中的VMwareTools-10.3.22-15902021.tar.gz文…

点旋转 与 坐标系旋转

之前想明白过&#xff0c;隔了一段时间没看&#xff0c;现在又忘记了。重新复习一下。 这篇博客写的很明白 推公式的话从坐标旋转开始推&#xff0c;容易理解&#xff0c;又容易推导。 1、坐标系中点的旋转的旋转矩阵 xrcos(αβ) r(cosαcosβ-sinαsinβ) xcosβ-ysinβ…

虹科Pico汽车示波器 | 免拆诊断案例 | 2019款别克GL8豪华商务车前照灯水平调节故障

一、故障现象 一辆2019款别克GL8豪华商务车&#xff0c;搭载LTG发动机&#xff0c;累计行驶里程约为10.7万km。车主反映&#xff0c;车辆行驶过程中组合仪表提示前照灯水平调节故障。 二、故障诊断 接车后试车&#xff0c;起动发动机&#xff0c;组合仪表上提示“前照灯水平…

线上剧本杀小程序开发,剧本杀行业的发展趋势

剧本杀一时火爆全网&#xff0c;剧本杀门店也是迅速占领了大街小巷&#xff0c;成为年轻人热衷的游戏娱乐方式。 不过&#xff0c;线下剧本杀因为价格高、剧本质量不过关等问题&#xff0c;迎来了“寒冬期”&#xff0c;线下剧本杀门店的发展逐渐“降温”。 随着互联网的发展…

跨平台内容策略:Kompas.ai让你的内容在各大平台上发光发热

在数字化营销的今天&#xff0c;品牌需要在多个社交媒体平台上建立强大的在线存在。每个平台都有其独特的用户群体和内容消费习惯&#xff0c;这就要求品牌制定精准的跨平台内容策略&#xff0c;以确保在不同的社交环境中都能发光发热。本文将深入探讨不同社交媒体平台的特点及…