【分布式技术专题】Guava RateLimiter 针对于限流器的入门到实战(含源码分析介绍)

Guava RateLimiter 针对于限流器的入门到实战

      • 限流器的思路和算法
        • 漏桶算法
        • 令牌算法
      • RateLimiter限流器
      • RateLimiter的作用
        • create方法
        • acquire方法
        • tryAcquire方法
      • 详细分析
        • 参数
        • 抛出异常
        • 参数
        • 抛出异常
        • 实践案例
          • 第1次获取10个令牌
          • 第2次获取1个令牌
          • 第3次获取10个令牌
      • 资料参考

限流器的思路和算法

如果让你来造一个限流器,有啥想法?

漏桶算法
  • 用一个固定大小的队列。比如设置限流为5qps,1s可以接受5个请求;那我们就造一个大小为5的队列,如果队列为满了,就拒绝请求;如果队列未满,就往队列添加请求。
令牌算法

令牌听起来挺酷的。以固定的速率往桶里发放令牌。然后消费者每次要取到令牌(acquire)才可以响应请求。

由于令牌是固定间隔发放的,假设还是5qps,如果我有1s内没有请求,我的令牌桶就满了,可以一瞬间响应5个请求(一次过取5个令牌),也就是可以应对瞬时流量。

那么这里也涉及一个固定间隔发放的问题,难道也是需要定时任务往”桶里“放令牌吗?

那我们来看下Guava怎么搞的,假设限流为2qps,那么固定发放令牌的时间stableIntervalMicros就是500ms,初始化的storedPermits当前桶里的令牌数是0。

RateLimiter限流器

RateLimiter从概念上来讲,速率限制器会在可配置的速率下分配许可证。如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证。一旦获取到许可证,不需要再释放许可证。

RateLimiter使用的是一种叫令牌桶的流控算法,RateLimiter会按照一定的频率往桶里扔令牌,线程拿到令牌才能执行,比如:你希望自己的应用程序QPS不要超过1000,那么RateLimiter设置1000的速率后,就会每秒往桶里扔1000个令牌。

com.google.common.util.concurrent.RateLimiter
@ThreadSafe
@Beta
public abstract class RateLimiter extends Object

RateLimiter的作用

RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。与Semaphore 相比,Semaphore 限制了并发访问的数量而不是使用速率。(注意尽管并发性和速率是紧密相关的,比如参考Little定律)

通过设置许可证的速率来定义RateLimiter。在默认配置下,许可证会在固定的速率下被分配,速率单位是每秒多少个许可证。为了确保维护配置的速率,许可会被平稳地分配,许可之间的延迟会做调整。

可能存在配置一个拥有预热期的RateLimiter 的情况,在这段时间内,每秒分配的许可数会稳定地增长直到达到稳定的速率。

举例来说明如何使用RateLimiter,想象下我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个:

//速率是每秒两个许可
final RateLimiter rateLimiter = RateLimiter.create(2.0);
void submitTasks(List tasks, Executor executor) {for (Runnable task : tasks) {rateLimiter.acquire(); // 也许需要等待executor.execute(task);}
}

再举另外一个例子,想象下我们制造了一个数据流,并希望以每秒5kb的速率处理它。可以通过要求每个字节代表一个许可,然后指定每秒5000个许可来完成:

// 每秒5000个许可
final RateLimiter rateLimiter = RateLimiter.create(5000.0);
void submitPacket(byte[] packet) {rateLimiter.acquire(packet.length);networkService.send(packet);
}

有一点很重要,那就是请求的许可数从来不会影响到请求本身的限制(调用acquire(1) 和调用acquire(1000) 将得到相同的限制效果,如果存在这样的调用的话),但会影响下一次请求的限制。

如果一个高开销的任务抵达一个空闲的RateLimiter,它会被马上许可,但是下一个请求会经历额外的限制,从而来偿付高开销任务。注意:RateLimiter并不提供公平性的保证。

create方法
  • create(double permitsPerSecond, long warmupPeriod, TimeUnit unit):根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
  • create(double permitsPerSecond):根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
acquire方法
  • acquire():从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
public double acquire()

从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求。如果存在等待的情况的话,告诉调用者获取到该请求所需要的睡眠时间。该方法等同于acquire(1)。

  • 返回值

执行速率的所需要的睡眠时间,单位为妙;如果没有则返回0

  • acquire(int permits):从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求。
public double acquire(int permits)

从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求数。如果存在等待的情况的话,告诉调用者获取到这些请求数所需要的睡眠时间。

  • 参数:permits – 需要获取的许可数
  • 返回:执行速率的所需要的睡眠时间,单位为妙;如果没有则返回0
  • 抛出:IllegalArgumentException – 如果请求的许可数为负数或者为0
tryAcquire方法

  • tryAcquire():从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话

  • tryAcquire(int permits):从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话

  • tryAcquire(int permits, long timeout, TimeUnit unit):从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)

  • tryAcquire(long timeout, TimeUnit unit):从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

详细分析

public static RateLimiter create(double permitsPerSecond)

根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)。

返回的RateLimiter 确保了在平均情况下,每秒发布的许可数不会超过permitsPerSecond,每秒钟会持续发送请求。

当传入请求速率超过permitsPerSecond,速率限制器会每秒释放一个许可(1.0 / permitsPerSecond 这里是指设定了permitsPerSecond为1.0) 。

当速率限制器闲置时,允许许可数暴增到permitsPerSecond,随后的请求会被平滑地限制在稳定速率permitsPerSecond中。

参数
  • permitsPerSecond – 返回的RateLimiter的速率,意味着每秒有多少个许可变成有效。
抛出异常
  • IllegalArgumentException – 如果permitsPerSecond为负数或者为0
public static RateLimiter create(double permitsPerSecond,long warmupPeriod,TimeUnit unit)

根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率(只要存在足够请求数来使其饱和)。

同样地,如果RateLimiter 在warmupPeriod时间内闲置不用,它将会逐步地返回冷却状态。

它会像它第一次被创建般经历同样的预热期。返回的RateLimiter 主要用于那些需要预热期的资源,这些资源实际上满足了请求(比如一个远程服务),而不是在稳定(最大)的速率下可以立即被访问的资源。返回的RateLimiter 在冷却状态下启动(即预热期将会紧跟着发生),并且如果被长期闲置不用,它将回到冷却状态。

参数
  • permitsPerSecond – 返回的RateLimiter的速率,意味着每秒有多少个许可变成有效。
  • warmupPeriod – 在这段时间内RateLimiter会增加它的速率,在抵达它的稳定速率或者最大速率之前
  • unit – 参数warmupPeriod 的时间单位
抛出异常
  • IllegalArgumentException – 如果permitsPerSecond为负数或者为0
实践案例

第1次获取10个令牌
  • nowMicro是刚开始运行的时间,是一个很小的数,约等于0;

  • resync(nowMicro),更新令牌数,由于nowMicro约等于0,其实令牌数不会更新((0-0)/5000 = 0),令牌数还是0(约等于0)

  • storedPermitsToSpend,其实当前并没有令牌,所以取min,约等于0;

  • freshPermits,需要预支付10个令牌,约等于10;

  • 预支付之后需要等待10 * interval = 10 * 500 ,约等于5000ms,5000000微秒

  • this.nextFreeTicketMicros 需要加上 waitMicros 也就是 下一次可以获得令牌的时间是5000ms之后。

  • 所以我们看到输出信息的第一行在第0s获取了10个令牌之后,下一次再想获取1个令牌需要等待5000ms也就是5s。

第2次获取1个令牌

然后再一次想获取1个令牌,当前时间还是约等于0,这时候resync,nowMicros(0)比nextFreeTicketMicros(5000)小,令牌不更新。

returnValue=5000,storedPermitsToSpend=0,freshPermits=1,需要再等 waitMicros=1 * 500ms,然后nextFreeTicketMicros更新为5000+500=5500,返回returnValue=5000;外层函数睡眠5000ms,返回5000(输出打印获取1个token,约5s)

第3次获取10个令牌

上面说的,睡了5000ms,当前时间nowMicros=5000;

resync,nowMicros(5000)比nextFreeTicketMicros(5500)小,令牌不更新,还是欠费状态,只能预支付。

returnValue=5500, storedPermitsToSpend=0,freshPermits=10,需要预支付10个令牌, waitMicros=10 * 500ms = 5000,然后nextFreeTicketMicros更新为5500+5000=10500,返回returnValue=5500;外层函数睡眠5500-5000=500ms,返回500(输出打印获取10个token,约0.5s)

资料参考

https://zhuanlan.zhihu.com/p/60979444

https://blog.csdn.net/waltonhuang/article/details/106763512

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

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

相关文章

maven: 使用profiles进行多环境配置

文章目录 一、背景二、如何配置 一、背景 在项目开发的过程中会用到多个环境比如:本地环境(开发自测)、开发环境(环境部署自测)、生产环境等,由于不同的环境需要不同的配置信息,为了便于开发使…

python-自动化篇-运维-监控-简单实例-道出如何使⽤Python⾃动化数据库备份?

使⽤Python⾃动化数据库备份是⼀种有效的⽅式,可以确保数据库数据的安全性和可恢复性。 以下是⼀般步骤,说明如何使⽤Python⾃动化数据库备份: 选择数据库备份⼯具:⾸先,选择适合数据库类型的备份⼯具。不同的数据库系…

opencv——将2张图片合并

效果演示: 带有绿幕的图片的狮子提取出来,放到另一种风景图片里! 1. 首先我们要先口出绿色绿幕,比如: 这里将绿色绿色绿幕先转为HSV,通过修改颜色的明暗度,抠出狮子的轮廓。 代码 : import cv2 as cv import numpy as np import matplotlib.pyplot as plt def showI…

C++模板判断类中是否存在某个名称的成员函数

由一道C面试题引发的思考 - 知乎 //判断是否存在指定的成员函数名 template <typename T, typename void> struct HasMemF_Fun : public std::false_type {};template <typename T> struct HasMemF_Fun<T, std::void_t<decltype(&T::Fun123)>>: s…

Unity MonoBehaviour 单例和标准单例的区别

共同特点 单一实例&#xff1a;无论是 MonoBehaviour 单例还是标准单例模式&#xff0c;它们都保证类只有一个实例存在。 全局访问点&#xff1a;两种单例模式都提供一个全局访问点&#xff0c;允许从应用程序的任何地方访问单例实例。这通常是通过一个公共的静态方法或属性实…

spring-boot-admin的介绍和使用

概述 Spring Boot 有一个非常好用的监控和管理的源软件&#xff0c;这个软件就是 Spring Boot Admin。该软件能够将 Actuator 中的信息进行界面化的展示&#xff0c;也可以监控所有 Spring Boot 应用的健康状况&#xff0c;提供实时警报功能。 主要的功能点有&#xff1a; 显…

tcpdump 抓包无法落盘

文章目录 问题背景解决办法 问题背景 在嵌入式设备中(Linux系统)&#xff0c;为了分析两个网络节点的通讯问题&#xff0c;往往需要用到tcpdump&#xff0c;抓一个.pcap的包在PC端进行分析。博主在实际操作中发现&#xff0c;抓包无法实时落盘。 解决办法 # 下面的命令是写在…

面试八股文(2)

文章目录 1.ArrayList和LinkedList区别2.HashMap和HashTable区别3.线程的创建方式4.Java中异常处理5.Java序列化中某些字段不想进行序列化&#xff1f;6.Java序列化7.静态方法和实例方法8.List、Set、Map三者区别9.ArrayList和Vector区别10.HashMap和HashSet区别 1.ArrayList和…

还在用findViewById,不来了解下其它方式?

众所周知&#xff0c;都2225年了&#xff0c;如果你还在用Java敲安卓代码&#xff0c;findViewById已经是一种非常繁琐的操作&#xff0c;如果要去获取的id数量多&#xff0c;则对开发更加不友好。如果一个页面id过多&#xff0c;经常会有如下场景&#xff1a; TextView title…

ISOLAR-A/B问题总结

ISOLAR-A/B报错问题一&#xff1a; 1. Target ARObject: <ECUC-MODULE-CONFIGURATION-VALUES-REF> Unable to resolve reference /ETAS_Project/EcucModuleConfigurationValuess/E2E. - Line: 99. [Infos] <ECUC-MODULE-CONFIGURATION-VALUES-REF> : </ARPacka…

Pytest 与allure测试报告集成

通过Feature, story, step 记录测试的功能&#xff0c;场景及测试步骤 # login.pylogin_func函数 传入参数是name 和 password 当输入的name和password与数据库db_data中数据一致时&#xff0c;返回“XXX成功登录系统&#xff01;” 当输入的name存在于数据库db_data但密码不正…

Qt应用开发(安卓篇)——调用ioctl、socket等C函数

一、前言 在 Qt for Android 中没办法像在嵌入式linux中一样直接使用 ioctl 等底层函数&#xff0c;这是因为因为 Android 平台的安全性和权限限制。 在 Android 中&#xff0c;访问设备硬件和系统资源需要特定的权限&#xff0c;并且需要通过 Android 系统提供的 API 来进行。…

自定义SpringBoot启动图标

在SpringBoot项目的resources目录下创建banner.txt文件 在https://www.bootschool.net/网站上复制Ascll艺术字&#xff08;图&#xff09;粘贴到banner.txt中保存。 启动项目就会加载 可以修改颜色&#xff0c;和版本号 ${application.version} 输出版本 ${spring-boot.v…

【leetcode100-081到090】【动态规划】一维五题合集1

【爬楼梯】 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 思路&#xff1a; 【状态】 dp[i];//爬i级台阶有几种方法 【初始】 dp[0] 1;//爬0级1种&#xff08;不爬&#xff09;dp[1] 1;/…

剑指offer面试题14 调整数组顺序使奇数位于偶数前面

考察点 双指针遍历数组知识点 题目 分析 要求奇数位于偶数前面&#xff0c;如果按照循环遍历的方式&#xff0c;可以每找到一个偶数就把该数字后面所有的数都往前移动1位&#xff0c;然后把该偶数放在最后空出来的位置&#xff0c;这样的时间复杂度是O(n^2)。由于只要求奇数…

HubSpot是如何通过社交媒体与用户建立互动?

HubSpot善于利用社交媒体平台&#xff0c;与用户建立深度互动&#xff0c;增强用户对品牌的参与感与黏性。以下是HubSpot在社交媒体上建立互动的关键策略&#xff1a; 1. 及时回应用户评论&#xff1a;建立积极互动氛围 HubSpot注重在社交媒体上及时回应用户的评论。无论是表…

用的到的linux-文件移动-Day2

前言&#xff1a; 在上一节&#xff0c;我们复习了cd大法和创建生成文件和文件夹的方法&#xff0c;介绍了一些“偷懒”&#xff08;高效&#xff09;的小技巧&#xff0c;本节&#xff0c;我们一起来探讨下&#xff0c;我们对文件移动操作时有哪些可以偷懒的小技巧~ 一、复制…

引领AI创意教育新浪潮,瑞云AIGC实训平台解决方案来了

过去的2023年&#xff0c;AI&#xff08;人工智能&#xff09;成为了年度科技圈关键词&#xff0c;各行各业都在AI化&#xff0c;据统计&#xff0c;AIGC市场规模预计到2030年将达到万亿级别&#xff0c;这不仅是市场的趋势&#xff0c;更是创新的机遇。 教育行业更是如此&…

【网络基础】网络协议传输层UDP和TCP

UDP 解包和分用 解包&#xff08;解析数据包&#xff09; 捕获数据包&#xff1a;首先&#xff0c;接收端的网络栈捕获UDP数据包。检查目的端口&#xff1a;接收端检查数据包头部的目的端口&#xff0c;以确定哪个应用程序应该接收该数据包。验证校验和&#xff1a;接收端可能…

阿赵UE学习笔记——14、LOD

阿赵UE学习笔记目录   大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的用法。这次看看虚幻引擎的Level Of Detail(LOD)的用法。 一、测试场景准备 用植物系统&#xff0c;在地形上面刷了好多草&#xff1a; 这个时候看一下网格&#xff0c;会发现网格比较多和密集。 …