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

为什么需要限流

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

  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){//创建RedissonClientRedissonClient 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.1port: 6379database: 0
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {private Integer database;private String host;private Integer port;private String password;@Beanpublic 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 {@Resourceprivate 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 {@Resourceprivate RedisLimiterManager redisLimiterManager;@Testvoid 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.rhkb.cn/news/317983.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python的魅力】:利用Pygame实现游戏坦克大战——含完整源码

文章目录 一、游戏运行效果二、代码实现2.1 项目搭建2.2 加载我方坦克2.3 加载敌方坦克2.4 添加爆炸效果2.5 坦克大战之音效处理 三、完整代码 一、游戏运行效果 二、代码实现 坦克大战游戏 2.1 项目搭建 本游戏主要分为两个对象&#xff0c;分别是我方坦克和敌方坦克。用户可…

【大模型系列】大模型的上下文长度解释与拓展

文章目录 1 什么是大模型的上下文长度&#xff1f;2 拓展大模型上下文长度的方式参考资料 1 什么是大模型的上下文长度&#xff1f; 大模型的上下文长度&#xff08;Context Length&#xff09;是指在自然语言处理&#xff08;NLP&#xff09;的大型语言模型&#xff08;Large…

Qt Creator导入第三方so库和jar包——Qt For Android

前言 之前了解了在Android Studio下导入so库和jar包&#xff0c;现在实现如何在Qt上导入so库和jar包。 实现 下面是我安卓开发&#xff08;需调用安卓接口的代码&#xff09;的目录&#xff08;图1&#xff09;&#xff0c;此目录结构和原生态环境&#xff08;Android Studi…

15.Blender Eevee和Cycles渲染引擎对比

初步介绍 Eevee是实时渲染的引擎&#xff0c;会省略一些解算方式&#xff0c;尤其对光线和阴影 Cycles会考虑这些因素&#xff0c;所以会对光线和阴影的表达更加真实&#xff0c;有一个实时光线追踪的功能 Cycles渲染完之后&#xff0c;每移动一次画面&#xff0c;都会重新渲染…

AI大模型探索之路-训练篇10:大语言模型Transformer库-Tokenizer组件实践

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

Android 设置头像 - 裁剪及圆形头像

书接上文 Android 设置头像 - 相册拍照&#xff0c;通过相册和照片的设置就可以获取到需要的头像信息&#xff0c;但是在通常情况下&#xff0c;我们还想要实现针对头像的裁剪功能和圆形头像功能。 先上截图&#xff1a; 图像裁剪 通常裁剪可以分为程序自动裁剪和用户选择裁剪…

LT6911GX HDMI2.1 至四端口 MIPI/LVDS,带音频 龙迅方案

1. 描述LT6911GX 是一款面向 VR / 显示应用的高性能 HDMI2.1 至 MIPI 或 LVDS 芯片。HDCP RX作为HDCP中继器的上游&#xff0c;可以与其他芯片的HDCP TX配合使用&#xff0c;实现中继器功能。对于 HDMI2.1 输入&#xff0c;LT6911GX 可配置为 3/4 通道。自适应均衡功能使其适合…

Redis运维篇-快速面试笔记(速成版)

文章目录 1. Redis的持久化1.1 RDB&#xff08;快照模式&#xff09;1.2 AOF 模式 2. Redis主从模型&#xff08;高可用&#xff09;2.1 Redis的主从复制2.2 Redis拓扑结构 3. Redis集群模式&#xff08;高并发&#xff09;3.1 Redis的Slots3.2 集群模式的常用命令3.3 多主多从…

全景剖析阿里云容器网络数据链路(七):Terway DataPath V2(Terway≥1.8.0)

作者&#xff1a;余凯 前言 近几年&#xff0c;企业基础设施云原生化的趋势越来越强烈&#xff0c;从最开始的IaaS化到现在的微服务化&#xff0c;客户的颗粒度精细化和可观测性的需求更加强烈。容器网络为了满足客户更高性能和更高的密度&#xff0c;也一直在高速的发展和演…

2024年五一数学建模C题完整解题思路代码

2024年第二十一届五一数学建模竞赛题目 C题 煤矿深部开采冲击地压危险预测 煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动力灾害风险越来越大&#xff0c;严重影响着煤矿的安全高效开采。在各类深…

MySQL之多表查询

1. 前言 多表查询&#xff0c;也称为关联查询.指两个或两个以上的表一起完成查询操作.前提条件 : 这些一起查询的表之间是有关系的(一对一/一对多).他们之间一定是有关联字段&#xff0c;这个关联字段可能建立了外键&#xff0c;也可能没有建立外键. 2. 笛卡尔积现象(交叉连接…

【Vulhub靶场】Nginx 漏洞复现

Nginx 漏洞复现 一、Nginx 文件名逻辑漏洞&#xff08;CVE-2013-4547&#xff09;1、影响版本2、漏洞原理3、漏洞复现 二、Nginx 解析漏洞1、版本信息&#xff1a;2、漏洞详情3、漏洞复现 一、Nginx 文件名逻辑漏洞&#xff08;CVE-2013-4547&#xff09; 1、影响版本 Nginx …

【数据结构】:链表的带环问题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 链表的带环问题在链表中是一类比较难的问题&#xff0c;它对我们的思维有一个比较高的要求&#xff0c;但是这一类…

【数据结构】链表专题3

前言 本篇博客我们继续来讨论链表专题&#xff0c;今天的链表算法题是经典中的经典 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 目录 1.判断链表是否…

【Scala---01】Scala『 Scala简介 | 函数式编程简介 | Scala VS Java | 安装与部署』

文章目录 1. Scala简介2. 函数式编程简介3. Scala VS Java4. 安装与部署 1. Scala简介 Scala是由于Spark的流行而兴起的。Scala是高级语言&#xff0c;Scala底层使用的是Java&#xff0c;可以看做是对Java的进一步封装&#xff0c;更加简洁&#xff0c;代码量是Java的一半。 因…

操作系统安全:Linux安全审计,Linux日志详解

「作者简介」:2022年北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖系统安全、信息收集等12个知识域的一百多个知识点,持续更新。 这一章节需要直到Linux…

09_Scala函数和对象

文章目录 函数和对象1.函数也是对象 scala中声明了一个函数 等价于声明一个函数对象2.将函数当作对象来用&#xff0c;也就是访问函数&#xff0c;但是不执行函数结果3.对象拥有数据类型(函数类型)&#xff0c;对象可以进行赋值操作4.函数对象类型的省略写法&#xff0c;也就是…

Java创建并遍历N叉树(前序遍历)

力扣 title589&#xff1a;N叉树的前序遍历 给定一个 n 叉树的根节点 root &#xff0c;返回 其节点值的 前序遍历 。 n 叉树 在输入中按层序遍历进行序列化表示&#xff0c;每组子节点由空值 null 分隔&#xff08;请参见示例&#xff09;。 思路&#xff1a; 1.初始化时…

CSS-复合选择器

作用&#xff1a; 后代选择器&#xff1a; 子代选择器 并集选择器 用逗号隔开&#xff0c;在style里面写的时候&#xff0c;每一个标签空一行。 <title>Document</title><style>p,div,span{color: aqua;}</style> </head> <body><p>…

细说SVPWM原理及软件实现原理,关联PWM实现

细说SVPWM原理及软件实现原理&#xff0c;关联PWM实现 文章目录 细说SVPWM原理及软件实现原理&#xff0c;关联PWM实现1. 前言2. 基础控制原理回顾2.1 FOC 原理回顾2.2 细说 SVPWM2.2.1 矢量扇区计算2.2.2 矢量作用时间计算 2.2.3 如何理解 U4 U6 2/3Udc?2.2.4 如何理解 U4m…