限流--4种经典限流算法讲解--单机限流和分布式限流的实现

news2024/11/26 14:48:33
为什么需要限流

系统的维护使用是需要成本的,用户可能使用科技疯狂刷量,消耗系统资源,出现额外的经济开销
问题:

  1. 控制成本=>限制用户的调用次数
  2. 用户在短时间内疯狂使用,导致服务器资源被占满,其他用户无法使用=>限流

那么限流阈值多大合适?比如限制单个用户在每秒只能使用1次。

限流的算法

推荐阅读:https://juejin.cn/post/6967742960540581918

1)固定窗口限流
单位时间内允许部分操作
1小时只允许10个用户操作

优点:最简单
缺点:可能出现流量突刺
比如:前59分钟没有1个操作,第59分钟来了10个操作;第1小时又来了10个操作。相当于2分钟内执行了20个操作,服务器仍然有高峰危险。

2)滑动窗口限流
单位时间内允许部分操作,但是这个单位时间是滑动的,需要指定一个滑动单位。
滑动窗口与固定窗口相比,将一个时间段又划分成了几个更小的切片。随着时间的前进,滑动窗口不断向前加载切片,向后遗弃切片,但是切片的总长度是固定的,滑动窗口保证了自己时间段内所有的访问不会超过阈值。

优点:能够解决流量突刺问题,第59分钟和第1小时分为了两个切片,但是属于一个时间段,更多的操作会被拒绝
缺点:实现相对复杂,限流效果和滑动单位有关,滑动单位越小,限流效果越好,但往往很难选取到一个特别合适的滑动单位。

3)漏桶限流(推荐)
固定的速率处理请求(漏水),当请求桶满了后,拒绝请求。
每秒处理10个请求,同的容量是10,每0.1秒固定处理一次请求,如果1秒来了10个请求;都可以处理完,但如果1秒内来了11个请求,最后那个请求就会溢出桶,被拒绝。

优点:能够一定程度上应对流量突刺,能够以固定速率处理请求,保证服务器的安全
缺点:没有办法迅速处理一批请求,只能一个一个按顺序来处理(固定速率的缺点)

4)令牌桶限流(推荐)
管理员先生成一批令牌,每秒生成10个令牌;当用户要操作前,先去拿到一个令牌,有令牌的人就有资格执行操作、能同时执行操作;拿不到令牌就等着

优点:能够并发处理同时的请求,并发性能会更高
需要考虑的问题:还是存在时间单位选取的问题

想看这些算法的具体实现的话,可以参考这篇文章:4种经典限流算法讲解
在实际开发中,我们一般调用第三方库,无需关注这些算法的具体实现,只需要理解上边我说的这些算法的思想就行

限流粒度
  1. 针对某个方法限流,即单位时间内最多允许同时XX个操作使用这个方法
  2. 针对某个用户限流,比如单个用户单位时间内最多执行XX次操作
  3. 针对某个用户X方法限流,比如单个用户单位时间内最多执行XX次这个方法
限流的实现
1)本地限流(单机限流)

每个服务器单独限流,一般适用于单体项目,就是项目只有一个服务器

在Java中,使用Guava库实现单机限流:

Guava库提供了RateLimiter类,它使用令牌桶算法来控制请求的速率。

  1. 添加依赖:首先需要在项目的pom.xml文件中添加Guava库的依赖。
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version> <!-- 请使用最新版本 -->
</dependency>
  1. 创建RateLimiter实例:创建一个RateLimiter实例,设置每秒可以处理的请求数。
import com.google.common.util.concurrent.RateLimiter;

RateLimiter rateLimiter = RateLimiter.create(5.0); // 每秒5个请求
  1. 限流操作:在需要限流的方法调用前,调用rateLimiter.acquire()来获取一个令牌。
public void doSomething() {
    if (rateLimiter.tryAcquire()) {
        // 执行操作
    } else {
        // 限流逻辑,如等待或返回错误
    }
}

4.**如何在第3点中的else里写等待逻辑:**可以实现几种不同的等待逻辑:

  1. 主动等待:使用 Thread.sleep() 或其他同步等待方法等待一段时间后再次尝试获取令牌。
  2. 被动等待:实现一个循环,不断尝试获取令牌,直到成功为止。
  3. 随机等待:使用随机等待时间以避免多个请求同时触发。
  4. 指数退避:等待时间逐渐增加,直到成功获取令牌。
  5. 返回错误:如果等待时间过长,可以选择返回错误,避免无限期的等待。

以下是几种等待逻辑的示例代码:

主动等待

解释public void doSomething() {
    if (rateLimiter.tryAcquire()) {
        // 执行操作
    } else {
        try {
            // 等待一段时间后再次尝试,例如等待500毫秒
            Thread.sleep(500);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 可以选择返回错误或继续执行
        }
        // 再次尝试获取令牌
        if (rateLimiter.tryAcquire()) {
            // 执行操作
        } else {
            // 如果再次获取失败,可以选择返回错误或继续等待
        }
    }
}

被动等待(不断尝试)

解释public void doSomething() {
    while (!rateLimiter.tryAcquire()) {
        // 可以选择在这里实现一个短暂的等待,如Thread.sleep(10),以避免CPU占用过高
    }
    // 执行操作
}

随机等待

解释import java.util.Random;

public void doSomething() {
    Random random = new Random();
    while (!rateLimiter.tryAcquire()) {
        try {
            // 随机等待一段时间,例如1到500毫秒之间
            int waitTime = random.nextInt(500);
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 可以选择返回错误或继续执行
        }
    }
    // 执行操作
}

指数退避

复制解释public void doSomething() {
    int retries = 0;
    while (!rateLimiter.tryAcquire()) {
        try {
            // 指数退避等待时间
            Thread.sleep((int) (Math.pow(2, retries) * 100));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 可以选择返回错误或继续执行
        }
        retries++;
        if (retries > MAX_RETRIES) {
            // 超过最大重试次数,可以选择返回错误
            break;
        }
    }
    // 执行操作
}

在实际应用中,选择哪种等待逻辑取决于你的具体需求和场景。例如,如果你希望避免重试对系统造成额外压力,可以选择随机等待或指数退避。如果你希望确保请求最终能够被处理,可以选择被动等待。如果你希望快速失败,可以选择返回错误。

2)分布式限流(多机限流)

如果项目有多个服务器,比如微服务,那么建议使用分布式限流。

  1. 把用户的使用频率等数据放到一个集中的存储进行统计,比如Redis,这样无论用户的请求落到了哪台服务器,都以集中的数据存储内的数据为准(Redisson - 是一个操作Redis的工具库)
  2. 在网关集中进行限流和统计(比如Sentinel、Spring Cloud Gateway)
import org.redisson.Redisson;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;

public static void main(String[] args){
    //创建RedissonClient
    RedissonClient redisson = Redisson.create();

    //获取限流器
    RRateLimiter semaphore = redisson.getRateLimiter("mySemaphore");

    //尝试获取许可证
    boolean result = semaphore.tryAcquire();
    if(result){
        //处理请求
    }else{
        //超过流量限制,需要做何处理
    }

}
Redisson限流实现

Redisson内置了一个限流工具类,可以借助Redis来存储统计。
官方仓库:https://github.com/redisson/redisson
看不懂方法的参数含义怎么办?

  1. 看官方文档
  2. 下载源码

image.png
点进任意一个包里的代码,然后点击下载源代码即可自动下载
image.png

步骤:

  1. 安装Redis
  2. 引入Redisson代码包:
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.21.3</version>
        </dependency>
  1. 创建RedissonConfig配置块,用于初始化RedissonClient对象单例
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
    private Integer database;
    private String host;
    private Integer port;
    private String password;

    @Bean
    public RedissonClient getRedissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setDatabase(database)
                .setAddress("redis://" + host + ":" + port)
                .setPassword(password);
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}
  1. 编写RedisLimiterManager

什么是Manager?提供了通用的能力,可以放到任何一个项目里

/**
 * 专门提供RedisLimiter限流基础服务(提供了通用的能力)
 */
@Service
public class RedisLimiterManager {

    @Resource
    private RedissonClient redissonClient;

    /**
     * 限流操作
     * @param key 区分不同的限流器,比如不同的用户id应该分别统计
     */
    public void doRateLimit(String key) {
        //创建一个名称为key的限流器,每秒最多访问2次
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
        rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);

        //每当一个操作来了之后,请求一个令牌
        boolean canOp = rateLimiter.tryAcquire(1);
        if (!canOp) {
            throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
        }
    }
}
  1. 单元测试:
@SpringBootTest
class RedisLimiterManagerTest {

    @Resource
    private RedisLimiterManager redisLimiterManager;

    @Test
    void doRateLimit() throws InterruptedException {
        String userId = "1";
        for (int i = 0; i < 2; i++) {
            redisLimiterManager.doRateLimit(userId);
            System.out.println("成功");
        }
        Thread.sleep(2000);
        for (int i = 0; i < 5; i++) {
            redisLimiterManager.doRateLimit(userId);
            System.out.println("成功");
        }
    }
}
  1. 应用到要限流的方法中,比如智能分析接口:
//必须登录
User loginUser = userService.getLoginUser(request);
//限流判断,每个用户一个限流器
redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());

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

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

相关文章

大象机器人开源六轴协作机械臂myCobot 320 手机摄影技术!

引言 有没有遇到过这样的情况&#xff1a;当你手持手机或相机准备拍摄视频时&#xff0c;心中已经构想了完美的画面&#xff0c;但却因为实际的限制无法捕捉到理想中的角度&#xff1f;这种情况可能会让人感到挫折。例如&#xff0c;如果想要从地面一只蚂蚁的视角拍摄&#xff…

dremio数据湖sql行列转换及转置

1、行转列 (扁平化) 数据准备 表 aa 1.1 cross join unnest 在Dremio中&#xff0c;UNNEST 函数用于将数组或复杂类型的列&#xff08;如JSON、Map或Array类型&#xff09;中的值“炸裂”&#xff08;分解&#xff09;成多行. with aa as ( select 上海 as city, ARRAY[浦东…

asp.net结课作业中遇到的问题解决1

作业要求 实现增删改查导出基本功能。 1、如何设置使得某个背景就是一整个而不是无限填充或者是这个图片的某一部分。 这就要求在设置这一块的时候&#xff0c;长和宽按照背景图片的大小进行设置&#xff0c;比如&#xff1a; 如果&#xff0c;图片的大小不符合你的要求&am…

技术团队的管理方法和日常总结建议

管理学家德鲁克有言“管理是一种实践&#xff0c;其本质不在于知&#xff0c;而在于行&#xff0c;其验证不在于逻辑&#xff0c;而在于成果&#xff0c;其唯一的权威就是成就” &#xff0c;因此管理重实践看效果&#xff0c;但如果管理实践有理论依凭&#xff0c;那么实践起来…

云手机对出海企业有什么帮助?

近些年&#xff0c;越来越多的企业开始向海外拓展&#xff0c;意图发掘更广阔的市场。在这过程中&#xff0c;云手机作为一个新型工具为很多企业提供了助力&#xff0c;尤其在解决海外市场拓展过程中的诸多挑战方面发挥着作用。 首先&#xff0c;云手机的出现解决了企业在海外拓…

VS2022 嘿嘿

还是大二的时候就开始用这个&#xff0c;但居然是为了用PB&#xff0c;-_-|| 用了段时间换成了C#&#xff0c;依稀还记得大佬们纠正我的读法&#xff0c;别读C井&#xff0c;应该读C夏普。。。 安装过程其实也没啥&#xff0c;就是关键Key得花时间找&#xff0c;我好不容易搞…

Android如何使用XML自定义属性

1、定义 在res/values文件下定义一个attrs.xml文件&#xff0c;代码如下: 2、使用 在布局中使用&#xff0c; 示例代码如下&#xff1a; 3、获取 最终来到这里&#xff1a;

设计模式——保护性暂停

同步模式之保护性暂停 文章目录 同步模式之保护性暂停定义实现应用带超时版 GuardedObject扩展——原理之join扩展——多任务版 GuardedObject 定义 即 Guarded Suspension&#xff0c;用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程&…

秋招后端开发面试题 - Java语言基础(下)

目录 Java基础下前言面试题toString() 、String.valueof()、(String)&#xff1f;hashCode() 方法&#xff1f;hashCode 和 equals 方法判断两个对象是否相等&#xff1f;为什么重写 equals 时必须重写 hashCode 方法&#xff1f;String、StringBuffer、StringBuilder?String …

VoxAtnNet:三维点云卷积神经网络

VoxAtnNet:三维点云卷积神经网络 摘要IntroductionProposed VoxAtnNet 3D Face PAD3D face point cloud presentation attack Dataset (3D-PCPA) VoxAtnNet: A 3D Point Clouds Convolutional Neural Network for 摘要 面部生物识别是智能手机确保可靠和可信任认证的重要组件。…

react 学习笔记二:ref、状态、继承

基础知识 1、ref 创建变量时&#xff0c;需要运用到username React.createRef()&#xff0c;并将其绑定到对应的节点。在使用时需要获取当前的节点&#xff1b; 注意&#xff1a;vue直接使用里面的值&#xff0c;不需要再用this。 2、状态 组件描述某种显示情况的数据&#…

[ACTF2020 新生赛]BackupFile 1 [极客大挑战 2019]BuyFlag 1 [护网杯 2018]easy_tornado 1

目录 [ACTF2020 新生赛]BackupFile 1 1.打开页面&#xff0c;叫我们去找源文件 2.想到用disearch扫描&#xff0c;发现源文件index.php.bak 3.访问这个文件&#xff0c;下载一个文件&#xff0c;用记事本打开 4.翻译php代码 5.构造payload url/?key123&#xff0c;得到fl…

【哈希】Leetcode 面试题 01.02. 判定是否互为字符重排

题目讲解 面试题 01.02. 判定是否互为字符重排 算法讲解 直观的想法&#xff1a;我们找到一个字符串的全排列&#xff0c;然后对比当前的排列是否等于另一个字符串。如果两个字符串如果互为排列&#xff0c;所以我们知道两个字符串对应的字符出现的个数相同&#xff0c;那么…

Windows 容器镜像踩坑记录

为什么研究windows容器&#xff1f;emm&#xff0c;公司需要&#xff0c;不想多说。 dotnet后端 问题描述&#xff1a; 基于mcr.microsoft.com/dotnet/aspnet:6.0镜像撰写dockerfile编译.net core后端项目后运行容器出现类库不存在问题&#xff1a; 程序中使用了fastreport&a…

编写你的第一个 golang 的应用程序

进行你的第一个golang的程序 当你把程序都安装好以后 环境变量配置 好 vscode 插件下载好以后 1. 创建一个test.go 的文件 //主包&#xff0c;可执行文件所在包 package main//导入包 import "fmt"//主函数&#xff0c;入口函数 func main() { }2.解释 需要导入包 …

Linux PTP学习

前言 本文是对Linux PTP的学习记录&#xff0c;不足之处请指出。Linux PTP用于在Linux系统的精确时钟同步&#xff0c;支持IEEE 1588 Precision Time Protocol&#xff08;PTP&#xff09;标准&#xff0c;目的是实现在网络中&#xff0c;设备之间的高精度时间同步。它是一个工…

Meta分析在生态环境领域里的应用教程

原文链接&#xff1a;Meta分析在生态环境领域里的应用教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247602936&idx4&sn50c2b3141baaa8635905fc405767d6ed&chksmfa82131fcdf59a09b57750e50657b2a06706f3b46806fc7ef5341d16701b99a14d4f7d82d3b9&am…

【算法】搜索插入位置

本题来源---《搜索插入位置》 题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1…

02.Kafka部署安装

1 Linux 安装 Kafka 1.1 安装前的环境准备 由于 Kafka 是用 Scala 语言开发的&#xff0c;运行在 JVM 上&#xff0c;因此在安装Kafka之前需要先安装JDK。 yum install java-1.8.0-openjdk* -y kafka 依赖 zookeeper&#xff0c;所以需要先安装 zookeeper。 wget https://ar…

2024深圳杯(东北三省)数学建模C题完整论文讲解(含完整python代码及所有残骸音爆位置求解结果)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024深圳杯&#xff08;东北三省数学建模联赛&#xff09;A题多个火箭残骸的准确定位完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊…