限流算法浅析

news2024/11/20 9:24:07

前言

在前文接口请求安全措施中,简单提到过接口限流,那里是通过Guava工具类的RateLimiter实现的,它实际上是令牌桶限流的具体实现,那么下面分别介绍几种限流算法,做一个更详细的了解。

固定窗口限流

1、核心思想

在单位时间(固定时间窗口)内限制请求的数量,即将时间分为固定的窗口,限制每个窗口的请求数量,如下图所示
在这里插入图片描述

2、具体实现

假设单位时间是1s,限流阈值为5,在单位时间内,每来一次请求,计数器count +1,若count>5,后续请求全部拒绝,等到1s结束,count清零

package com.example.test.limit.fixedWindow;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName fixedWindow.java
 * @Description 固定窗口限流算法
 * 核心思想:在单位时间(固定时间窗口)内限制请求的数量,即将时间分为固定的窗口,限制每个窗口的请求数量。
 * 具体实现:假设单位时间是1s,限流阈值为5,在单位时间内,每来一次请求,计数器count +1,若count>5,后续请求全部拒绝,等到1s结束,count清零
 * 优点:易于理解和实现
 * 缺点:存在临界问题,例如0-1和1-2是两个时间窗口,若在0.8-1s内有5个请求,在1-1.2s内有5个请求,虽然在各自的窗口上没有超限,但是在0.8-1.2s这个时间范围内
 * 实际上也是一个窗口的,这样就超限了
 * @createTime 2023/4/19
 */
public class fixedWindow {
    //计数器
    public static AtomicInteger count = new AtomicInteger(0);
    //时间窗口,单位s
    public static final Long window = 1000L;
    //窗口阈值
    public static final int threshold = 5;
    //上次请求时间
    public static long lastAcquireTime = 0L;

    public static synchronized boolean fixedWindowTryAcquire(){
        //当前系统时间
        long currentTime = System.currentTimeMillis();
        //看看本次请求是否在窗口内
        if(currentTime - lastAcquireTime > window){
            //重置计数器和上次请求时间
            count.set(0);
            lastAcquireTime = currentTime;
        }
        //检查计数器是否超过阈值,注意这里getAndIncrement和incrementAndGet的区别
        if(count.incrementAndGet() <= threshold){
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(150);
                System.out.println(Thread.currentThread().getName()+"--------------->"+fixedWindowTryAcquire());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3、优缺点

  • 优点:易于理解和实现
  • 缺点:存在临界问题,例如0-1和1-2是两个时间窗口,若在0.8-1s内有5个请求,在1-1.2s内有5个请求,虽然在各自的窗口上没有超限,但是在0.8-1.2s这个时间范围内,实际上也是一个窗口的,这样就超限了,如下图
    在这里插入图片描述

滑动窗口限流

1、核心思想

在固定窗口中,存在跨越两个小窗口(指的是一个单位窗口内我们自行理解的划分)的临界问题,那么,如果我们让这个小窗口动起来,不断去删除已经过去的时间,这样,动态的判断是否超限,如下图所示
在这里插入图片描述

2、具体实现

假设单位时间是100s(单位窗口),限流阈值为5,我们将单位窗口分为10个小窗口(每个小窗口为10s),然后不断的更新每个小窗口的请求和小窗口的起始时间,这样就是动起来了

package com.example.test.limit.slideWindow;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName slideWindow.java
 * @Description 滑动窗口限流算法
 * 核心思想:在固定窗口中,存在跨越两个小窗口(指的是一个单位窗口内我们自行理解的划分)的临界问题,那么,如果我们让这个小窗口动起来,不断去删除已经过去的时间,这样,动态的判断是否超限
 * 具体实现:假设单位时间是100s(单位窗口),限流阈值为5,我们将单位窗口分为10个小窗口(每个小窗口为10s),然后不断的更新每个小窗口的请求和小窗口的起始时间,这样就是动起来了
 * 优点:精度高,对比固定窗口算法,减少了临界状态下的失真
 * 缺点:无法应对突发流量(短时间大量请求),许多请求会被直接拒绝
 * @createTime 2023/4/20
 */
public class slideWindow {
    //时间窗口,单位s
    public static final Long window = 100L;
    //窗口阈值
    public static final int threshold = 5;
    //小窗口个数
    public static final int windowCount = 10;
    //存储每个小窗口开始时间及其流量
    public static Map<Long, Integer>windowCounters = new HashMap<>();

    public static synchronized boolean tryAcquire(){
        //取当前时间,做了取整操作,保证在同一个小窗口内的时间落到同一个起点,将时间戳取秒计算,方便理解
        long currentTime = System.currentTimeMillis()/1000 / windowCount * windowCount;
        int currentCount = calculate(currentTime);
        if(currentCount > threshold){
            return false;
        }
        windowCounters.put(currentTime - windowCount* (window/windowCount-1),currentCount);
        return true;
    }

    private static int calculate(long currentTime) {
        //计算小窗口开始时间
        //这里可以理解为 currentTime - entry.key > window/windowCount
        long startTime = currentTime - windowCount* (window/windowCount-1);
        System.out.println(startTime);
        int count = 1;
        //遍历计数器
        Iterator<Map.Entry<Long,Integer>> iterator = windowCounters.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Long,Integer>entry = iterator.next();
            //删除无效过期的子窗口
            if(entry.getKey() < startTime){
                iterator.remove();
                System.out.println("移除了");
            }else{
                //累加请求
                count = entry.getValue()+1;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"--------------->"+tryAcquire());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3、优缺点

  • 优点:精度高,对比固定窗口算法,减少了临界状态下的失真
  • 缺点:无法应对突发流量(短时间大量请求),许多请求会被直接拒绝

漏桶算法

1、核心思想

对于每个请求,检查漏桶是否有容量,没有直接拒绝;有的话放入漏桶,漏桶以一定速率处理桶内的请求,生产者-消费者模型,请求作为生产者,系统作为消费者,如下所示
在这里插入图片描述

2、具体实现

假设漏桶容量为5,速率为1个/s,那么我们还需要有一个当前的水量和当前时间分别用来判断是否可以存放请求和消耗请求

漏桶定义
package com.example.test.limit.leakBucket;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName leakBucket.java
 * @Description 漏桶的定义
 * @createTime 2023/4/21
 */
public class leakBucket {
    //漏桶的容量
    private long capacity;
    //滴水(消耗请求)速率
    private long rate;
    //当前桶中的水(未消耗的请求)
    private long water;
    //上次滴水时间(用来计算现在要消耗多少个)
    private long lastLeakTime;

    public leakBucket(long capacity, long rate, long water, long lastLeakTime) {
        this.capacity = capacity;
        this.rate = rate;
        this.water = water;
        this.lastLeakTime = lastLeakTime;
    }

    public long getCapacity() {
        return capacity;
    }

    public void setCapacity(long capacity) {
        this.capacity = capacity;
    }

    public long getRate() {
        return rate;
    }

    public void setRate(long rate) {
        this.rate = rate;
    }

    public long getWater() {
        return water;
    }

    public void setWater(long water) {
        this.water = water;
    }

    public long getLastLeakTime() {
        return lastLeakTime;
    }

    public void setLastLeakTime(long lastLeakTime) {
        this.lastLeakTime = lastLeakTime;
    }
}

实现
package com.example.test.limit.leakBucket;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName leakBucket.java
 * @Description 漏桶算法
 * 核心思想:对于每个请求,检查漏桶是否有容量,没有直接拒绝;有的话放入漏桶,漏桶以一定速率处理桶内的请求,生产者-消费者模型,请求作为生产者,系统作为消费者
 * 具体实现:假设漏桶容量为5,速率为1个/s,那么我们还需要有一个当前的水量和当前时间分别用来判断是否可以存放请求和消耗请求
 * 优点:漏桶算法还是很容易理解的,毕竟接近生活;可以平滑的处理请求,可以控制请求速度,避免过载或闲置
 * 缺点:需要缓存请求,面对突发流量,处理不能及时响应
 * @createTime 2023/4/21
 */
public class leakBucketAlgorithm {
    //初始化漏桶
    public static leakBucket leakBucket = new leakBucket(5,1,0,0);
    public static synchronized boolean tryAcquire(){
        leak();
        //判断是否有空间,这里我设为每个请求都要判断,也可以直接传参多少个请求
        if(leakBucket.getWater()+1 <leakBucket.getCapacity()){
            leakBucket.setWater(leakBucket.getWater()+1);
            return true;
        }
        return false;
    }

    //漏水方法
    private static void leak() {
        //当前时间,以秒操作
        long currentTime = System.currentTimeMillis() / 1000;
        //最后一次漏水到现在应该漏多少
        long leakWater = (currentTime - leakBucket.getLastLeakTime()) * leakBucket.getRate();
        if(leakWater > 0){
            leakBucket.setWater(Math.max(0,leakBucket.getWater() - leakWater));
            leakBucket.setLastLeakTime(currentTime);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(200);
            System.out.println(tryAcquire());
        }
    }
}

3、优缺点

  • 优点:漏桶算法还是很容易理解的,毕竟接近生活;可以平滑的处理请求,可以控制请求速度,避免过载或闲置
  • 缺点:需要缓存请求,面对突发流量,处理不能及时响应

令牌桶算法

1、核心思想

令牌桶算法相当于漏桶算法的翻版,之前说过,漏桶算法生产者是客户端,令牌桶是服务端作为生产者,以一定速率生成令牌放入桶中,桶满丢弃令牌,每次来一个请求消耗一个令牌,没有令牌就丢弃请求;相对于漏桶算法变化:生产者–>服务端,更符合限流的定义;消费者–>客户端,不需要另外的存储请求,如下图
在这里插入图片描述

2、具体实现

其实就是将漏桶的实现转变下思路

令牌桶定义
package com.example.test.limit.tokenBucket;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName tokenBucket.java
 * @Description 令牌桶定义
 * @createTime 2023/4/21
 */
public class tokenBucket {
    //令牌桶的容量
    private long capacity;
    //生成令牌的速率
    private long rate;
    //当前桶中的令牌数
    private long tokenNum;
    //上次生成令牌的时间
    private long lastMakeTime;

    public tokenBucket(long capacity, long rate, long tokenNum, long lastMakeTime) {
        this.capacity = capacity;
        this.rate = rate;
        this.tokenNum = tokenNum;
        this.lastMakeTime = lastMakeTime;
    }

    public long getCapacity() {
        return capacity;
    }

    public void setCapacity(long capacity) {
        this.capacity = capacity;
    }

    public long getRate() {
        return rate;
    }

    public void setRate(long rate) {
        this.rate = rate;
    }

    public long getTokenNum() {
        return tokenNum;
    }

    public void setTokenNum(long tokenNum) {
        this.tokenNum = tokenNum;
    }

    public long getLastMakeTime() {
        return lastMakeTime;
    }

    public void setLastMakeTime(long lastMakeTime) {
        this.lastMakeTime = lastMakeTime;
    }
}

实现
package com.example.test.limit.tokenBucket;

/**
 * @author zy
 * @version 1.0.0
 * @ClassName tokenBucketAlgorithm.java
 * @Description 令牌桶算法
 * 核心思想:令牌桶算法相当于漏桶算法的翻版,之前说过,漏桶算法生产者是客户端,令牌桶是服务端作为生产者,以一定速率生成令牌放入桶中,桶满丢弃令牌,每次来一个
 * 请求消耗一个令牌,没有令牌就丢弃请求
 * 变化:生产者-->服务端,更符合限流的定义;消费者-->客户端,不需要另外的存储请求
 * 优点:稳定性高,可以控制请求处理速度,使系统负载稳定;精度高,可以根据实际情况动态调整生成令牌的速率,可以实现较高精度的限流;可以处理突发流量,可以在短时间内提供更多的处理能力
 * 以处理突发流量
 * 缺点:令牌桶算法需要在固定的时间间隔内生成令牌,因此要求时间精度较高,如果系统时间不准确,可能会导致限流效果不理想
 * @createTime 2023/4/21
 */
public class tokenBucketAlgorithm {
    public static tokenBucket tokenBucket = new tokenBucket(5,1,5,0);

    public static synchronized boolean tryAcquire(){
        makeToken();
        if(tokenBucket.getTokenNum() > 0){
            tokenBucket.setTokenNum(tokenBucket.getTokenNum() - 1);
            return true;
        }
        return false;
    }

    private static void makeToken() {
        //当前时间,以秒计算
        Long currentTime = System.currentTimeMillis() / 1000;
        if(currentTime > tokenBucket.getLastMakeTime()){
            //计算应该生成多少令牌
            long makeNums = (currentTime - tokenBucket.getLastMakeTime()) * tokenBucket.getRate();
            //放入令牌桶,多余容量的抛弃
            tokenBucket.setTokenNum(Math.min(tokenBucket.getCapacity(),makeNums+tokenBucket.getTokenNum()));
            //更新生成令牌时间
            tokenBucket.setLastMakeTime(currentTime);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(100);
            System.out.println(tryAcquire());
        }
    }
}

3、优缺点

  • 优点:稳定性高,可以控制请求处理速度,使系统负载稳定;精度高,可以根据实际情况动态调整生成令牌的速率,可以实现较高精度的限流;可以处理突发流量,可以在短时间内提供更多的处理能力以处理突发流量
  • 缺点:令牌桶算法需要在固定的时间间隔内生成令牌,因此要求时间精度较高,如果系统时间不准确,可能会导致限流效果不理想

总结

虽然看上去限流算法很难,但是只要理解它们的核心思想,操作起来还是比较容易的,上述代码是完整可运行的,可以选择本地debug一下,看看具体怎么调用的。PS:滑动窗口算法那个开始时间的计算属实给我搞懵了,还好最后转过弯儿来了,最后,有轮子还是用轮子,但是我们要了解下轮子的原理。

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

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

相关文章

基于 Flink CDC 的现代数据栈实践

摘要&#xff1a;本文整理自阿里云技术专家&#xff0c;Apache Flink PMC Member & Committer, Flink CDC Maintainer 徐榜江和阿里云高级研发工程师&#xff0c;Apache Flink Contributor & Flink CDC Maintainer 阮航&#xff0c;在 Flink Forward Asia 2022 数据集成…

初识C语言————4

文章目录 常见关键字 1、 关键字 typedef 2、关键字static define 定义常量和宏 指针 结构体 前言 这是博主初识C语言系列的最后一篇&#xff0c;之后博主会更新更详细的关于C语言学习的知识。希望各位老铁多多支持。 一、常见关键字 1、 关键字 typedef typedef 顾名思义是…

海康威视发布2022年ESG报告:科技为善, 助力可持续的美好未来

近日&#xff0c;海康威视正式发布《2022环境、社会及管治报告》&#xff08;以下简称“海康威视ESG报告”)&#xff0c;连续5年呈现在环境、社会发展、企业治理等领域的思考和创新成果。此外&#xff0c;报告中首次披露了碳中和业务蓝图&#xff0c;积极布局绿色生产、绿色运营…

HTTP特性

1 HTTP/1.1 的优点有哪些&#xff1f; 2 HTTP/1.1 的缺点有哪些&#xff1f; 3 HTTP/1.1 的性能如何&#xff1f; HTTP 协议是基于 TCP/IP&#xff0c;并且使用了「请求 - 应答」的通信模式&#xff0c;所以性能的关键就在这两点里。 3.1 长连接 早期 HTTP/1.0 性能上的一…

分布式Id生成之雪花算法(SnowFlake)

目录 前言 回顾二进制 二进制概念 运算法则 位&#xff08;Bit&#xff09; 字节&#xff08;Byte&#xff09; 字符 字符集 二进制原码、反码、补码 有符号数和无符号数 疑问&#xff1a;为什么不是-127 &#xff5e; 127 &#xff1f; 为什么需要分布式全局唯一ID…

sql中 join 的简单用法总结(带例子)

join 常见的用法有&#xff1a; 目录 left join&#xff08;left outer join&#xff09;right join&#xff08;right outer join&#xff09;join&#xff08;inner join&#xff09;full join&#xff08;full outer join 、outer join&#xff09;cross join 说明&#xf…

docker自定义镜像

文章目录 一、自定义镜像1.1 镜像结构1.2 Dockerfile1.3 dockerCompose1.3.1 dockerCompose的作用1.3.2 dockerCompose的常用命令 1.4 镜像仓库 一、自定义镜像 1.1 镜像结构 自定义镜像通常包含三个基本部分&#xff1a;基础镜像、应用程序代码和配置文件。 基础镜像&#…

asp.net+sqlserver+C#网上洗衣店的设计与实现

选题的目的、理论与实践意义&#xff1a; 随着洗衣店服务的日渐完善和复杂&#xff0c;以前单纯的文本记录人工管理方式不仅效率低下&#xff0c;且易出错&#xff0c;直接导致管理费用的增加&#xff0c;服务质量的下降。由于这种人工管理方式不能完全适应需求的发展&#xff…

打包后dist包中app.**.js文件暴露大量接口信息,webpack-obfuscator对打包后的js代码混淆加密

问题描述 打包后dist包中app.**.js文件暴露大量接口信息&#xff0c;而webpack-obfuscator可以对打包后的js代码混淆加密 版本信息 webpack: 4.x.x node: 14.18.0 webpack4环境下使用webpack-obfuscator不能使用最新版本 我的下载版本是&#xff1a; npm install --save-de…

回溯算法模板(python)

#回溯模板&#xff0c;伪代码 def backtracking(参数):if (终止条件):存放结果return #如果要将数层中间的结果也插入&#xff0c;就不用写return&#xff0c;比如子集问题for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;):处…

通过Python的PIL库进行图像的过滤

文章目录 前言一、素材准备二、演示1.引入库2.定义图片路径3.打开原图4.过滤方法4.1图像的模糊效果代码效果图 4.2图像的轮廓效果代码效果图 4.3图像的细节效果代码效果图 4.4图像的边界效果代码效果图 4.5图像的边界加强效果代码效果图 4.6图像的阈值边界加强效果代码效果图 4…

自定义bean对象实现hadoop序列化

文章目录 一、源代码1.UserSaleMapper类2. UserSaleReducer类3. UserSaleDriver类4.pom.xml 二、执行结果 指导参考图&#xff1a; 一、源代码 1.UserSaleMapper类 package org.example.writable;import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Tex…

大厂对ChatGPT的开发利用和评估案例收录

ChatGPT已经进入各行各业&#xff0c;但是实际在工作中的有哪些应用呢&#xff1f;这里分享互联网一线大厂分享的一些实际使用案例&#xff0c;所有文章收录到 大厂对ChatGPT的开发利用和评估案例收录http://​www.webhub123.com/#/home/detail?projectHashid67792343&own…

Visual Studio 2019 C# 上位机入门(2):写一个简单的串口助手

前言 本文记录一下用Visual Studio 2019 C# 写一个简单的串口助手的过程&#xff0c;由于没有先从小处学习&#xff0c;而是直接找相关资料就开始做&#xff0c;免不了很多奇怪的问题花了一些时间&#xff0c;基于此情况&#xff0c;我将尽可能整理出更多细节&#xff0c;尤其…

Linux基础内容(20)—— 共享内存

Linux基础内容&#xff08;19&#xff09;—— 进程间通信(介绍与管道内容)_哈里沃克的博客-CSDN博客 目录 1.共享内存的原理 2.共享内存的概念和特点 创建共享内存 共享内存的形式 共享内存(ipc资源)的调用和特征 用户接口删除共享内存 共享内存关联 去关联 特点 …

React基础学习(一)

一、虚拟DOM和真实DOM <script type"text/babel"> // 此处一定要写babel!!!!!!!// 1. 创建虚拟DOM// const VDOM <h1 id"title">Hello, React!</h1> // 此处一定不要写引号 因为这不是字符串!!!!!!!const VDOM ( // 如果有多层嵌套&a…

PostgreSQL15.2最新版本安装_远程连接_Navicat操作_pgAdmin操作_Windows10上安装---PostgreSQL工作笔记001

首先去下载postgresql https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 下载地址: 去上面的地址下载,最好下载10版本的,我这里下载的是15版本的,有问题,后面说吧 下载以后 然后双击安装 next 选择目录next next 输入密码next

好程序员:Java线下培训有必要吗?零基础想学Java怎么学?

有粉丝问好程序员&#xff1a;自己只有周末有时间&#xff0c;想报班学习Java编程&#xff0c;是线上学编程好还是线下学编程好&#xff1f;小源从实际客观以及学习效果的角度来讲&#xff0c;毫无疑问是线下学编程的效果会更好。为什么这样说呢&#xff1f; 比如&#xff1a;家…

初探强化学习

1.引言 人生中充满选择&#xff0c;每次选择就是一次决策&#xff0c;我们正是从一次次决策中&#xff0c;把自己带领到人生的下一段旅程中。在回忆往事的时候&#xff0c;我们会对生命中某些时刻的决策印象深刻&#xff1a;“还好当时选择了读研&#xff0c;毕业后找到了一份自…

学习小程序基础内容之逻辑交互

我们先来看一下实现的效果。 然后再来分享结构。 结构分为左右3:7 分配&#xff0c; 左侧是类别&#xff0c;右侧是该类别对应的品牌。 后台会在onload的请求把左侧的类别返回来&#xff0c;然后我们通过循环把数据展示出来。然后通过点击事件&#xff0c;把对应的品牌请求回来…