限流--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.mzph.cn/diannao/4769.shtml

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

相关文章

Q1季度方便速食行业线上市场(京东天猫淘宝)销售数据分析

方便食品行业作为快速消费品市场的重要组成部分&#xff0c;近几年表现出较为强劲的发展势头。当然&#xff0c;每年的食品安全问题也在一定程度上影响着市场的良性健康发展。那么&#xff0c;今年Q1季度方便食品的线上发展如何&#xff1f; 根据鲸参谋数据显示&#xff0c;Q1…

制造企业如何打造客户服务核心竞争力?[AMT企源典型案例]

引言 产品同质化严重&#xff0c;竞争的焦点从产品转向服务&#xff0c;企业的管理模式也要相应转变。那么如何打造围绕服务的核心竞争力&#xff1f;相信以下案例会给大家一些启发。 项目背景&#xff1a; 售后服务在市场竞争中的作用凸显 A公司是一家医疗器械生产制造企业…

kali 网络环境设置

一、修改网卡配置 1.1 系统桌面上单击右键&#xff0c;在弹出的菜单中选择 Open Terminal Here。 1.2 输入命令 vim /etc/network/interfaces&#xff0c;显示配置网卡参数为。iface lo 一般指 本地环回接口&#xff0c; iface eth0 网卡为系统正在使用的网卡&#xff0c;其中的…

浏览器的本地存储---localstorage

web存储对象 Web 存储对象 localStorage 和 sessionStorage 允许我们在浏览器上保存键/值对。 这两个对象保存再本地&#xff08;客户端&#xff09;&#xff0c;允许保存至少 5MB 的数据&#xff08;或更多&#xff09;&#xff0c;这些数据不会因为页面刷新而销毁&#xff0…

高扬程水泵的性能与应用领域 /恒峰智慧科技

在现代社会中&#xff0c;科技的发展为我们的生活带来了无数便利和可能性。其中&#xff0c;高扬程水泵作为一种高效能的水泵&#xff0c;其独特的设计使其在各个领域都有着广泛的应用&#xff0c;尤其是在森林消防中。 一、高扬程水泵的性能 1. 高扬程&#xff1a;高扬程水泵…

TinyML之Hello world----基于Arduino Nano 33 BLE Sense Rev2的呼吸灯

早期版本的Hello World 这应该是一个逼格比较高的呼吸灯了&#xff0c;用ML来实现呼吸灯功能&#xff0c;之前已经有大佬发过类似的文章&#xff1a;https://blog.csdn.net/weixin_45116099/article/details/126310816 当前版本的Hello World 这是一个ML的入门例程&#xff…

常见面试题总结

1. 苍穹外卖的模块 苍穹外卖大方向上主要分为管理端和用户端 管理端使用vue开发&#xff0c;主要是商家来使用&#xff0c;提供餐品的管理功能&#xff0c;主要有下面几个模块&#xff1a; 员工模块&#xff0c;提供员工账号的登录功能和管理功能 分类、菜品、套餐模块&…

promise笔记

1.介绍 之前的异步编程都是回调函数&#xff08;数据库操作、ajax、定时器、fs读取文件 &#xff09; promise是es6异步编程新的解决方案&#xff0c;是一个构造函数 优点&#xff1a;支持链式调用&#xff0c;可以解决回调地狱&#xff0c;可以指定回调函数 2.使用 functio…

SpringCloud之负载均衡Ribbon

Ribbon 是一个客户端负载均衡工具&#xff0c;主要功能是将面向服务的Rest模板&#xff08;RestTemplate&#xff09;请求转换成客户端负载均衡的服务调用。通过Ribbon&#xff0c;开发人员可以在客户端实现请求的负载均衡&#xff0c;而无需单独部署负载均衡器。Ribbon支持多…

在config.json文件中配置出来new mars3d.graphic.PolylineCombine({大量线合并渲染类型的geojson图层

在config.json文件中配置出来new mars3d.graphic.PolylineCombine({大量线合并渲染类型的geojson图层 问题场景&#xff1a; 1.浏览官网示例的时候图层看到大量线数据合并渲染的示例 2.矢量数据较大量级的时候&#xff0c;这种时候怎么在config.json文件中尝试配置呢&#x…

软件更新 | TSMaster 2024.04 最新版已上线,来看看新增了哪些实用功能

TSMaster是集汽车总线嵌入式代码生成、监控、仿真、开发、UDS诊断、CCP/XCP标定、ECU刷写、I/O控制、测试测量等功能于一体的国产软件工具。在最新更新的软件版本里&#xff0c;增加了很多新功能&#xff0c;其中期待已久的DoIP诊断功能终于在最新升级版本里可以实现&#xff0…

Redis__数据类型

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__数据类型 ⏱️ 创作时间&#xff1a;2024年04月28日 ———————————————— 这里写目录标题 文…

理清STM32的内存(ram)与flash(rom)空间

keil工程变异代码的时候&#xff0c;会有如下输出信息 code:代码机器编译后生成的一系列指令&#xff0c;永远只放在flsah&#xff0c;内存ram不会存在&#xff1b; RO-data:只读常量&#xff0c;永远只放在flash内&#xff0c;存ram不会存在&#xff1b;&#xff1b; RW-dat…

SCP收容物001

注 &#xff1a;本文是特别版&#xff0c;本文只供开玩笑 ,与steve_gqq_MC合作。 --------------------------------------------------------------------------------------------------------------------------------- 前言&#xff1a;我的第一篇文章说过&#xff0c;SC…

【树莓派】yolov5 Lite,目标检测,行人检测入侵报警,摄像头绑定

延续之前的程序&#xff1a; https://qq742971636.blog.csdn.net/article/details/138172400 文章目录 播放声音pygame不出声音怎么办&#xff08;调节音量&#xff09;树莓派上的音乐播放器&#xff08;可选&#xff09;命令行直接放歌&#xff08;尝试放mp3歌曲&#xff09; …

word添加行号

打开页面设置&#xff0c;找到行号

Java设计模式 _创建型模式_工厂模式(普通工厂和抽象工厂)

一、工厂模式 属于Java设计模式创建者模式的一种。在创建对象时不会对客户端暴露创建逻辑&#xff0c;并且是通过使用一个共同的接口来指向新创建的对象。 二、代码示例 场景&#xff1a;花店有不同的花&#xff0c;通过工厂模式来获取花。 1、普通工厂模式 逻辑步骤&#…

【算法每日一练】

蛮有意思的的一道题&#xff0c;最后要判断能否成为一种1~n的全排列&#xff0c;我最这样做的&#xff1a; 整个数组先排序一下。假设遍历到了i&#xff0c;那就判断前面b和r的个数&#xff0c;但是有想到了后面可能还会对前面的结果产生影响&#xff0c;所以就抛弃了这个想法…

安卓intent+传递Serializable接口

从Mainactivity1传递对象给MainActivity2可以通过Serializable对象。 <?xml version"1.0" encoding"utf-8"?> <LinearLayoutxmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.co…

鸿蒙开发HarmonyOS4.0入门与实践

鸿蒙开发HarmonyOS4.0 配合视频一起食用&#xff0c;效果更佳 课程地址&#xff1a;https://www.bilibili.com/video/BV1Sa4y1Z7B1/ 源码地址&#xff1a;https://gitee.com/szxio/harmonyOS4 准备工作 官网地址 鸿蒙开发者官网&#xff1a;https://developer.huawei.com/con…