开源限流组件分析(二):uber-go/ratelimit
文章目录
- 本系列
- 漏桶限流算法
- uber的漏桶算法
- 使用
- mutex版本
- 数据结构
- 获取令牌
- 松弛量
- atomic版本
- 数据结构
- 获取令牌
- 测试漏桶的松弛量
- 总结
本系列
- 开源限流组件分析(一):juju/ratelimit
- 开源限流组件分析(二):uber-go/ratelimit(本文)
- 开源限流组件分析(三):golang-time/rate
漏桶限流算法
漏桶限流算法思路很简单,水(数据或者请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,漏桶算法能强行限制请求的处理速度
相比于令牌桶中只要桶内还有剩余令牌,调用方就可以马上消费令牌的策略。漏桶相对来说更加严格,调用方只能按照预定的间隔顺序消费令牌
例如:假设漏桶出水的间隔是10ms,上次在0ms时刻出水,那么下次是10ms时刻,再下次是20ms时刻
uber的漏桶算法
uber对标准的漏桶算法做了一些优化,加了一些松弛量,这么做了后就能应对突发流量,达到和令牌桶一样的效果
关于什么是松弛量,在介绍获取令牌流程时再详细分析
阅读的源码:https://github.com/uber-go/ratelimit,版本:v0.3.1
使用
下面展示一个没有使用松弛量的漏桶使用示例:
func Example_default() {// 参数100:代表每秒放行100个请求,即每10ms放行一个请求rl := ratelimit.New(100)prev := time.Now()for i := 0; i < 10; i++ {// Take获取令牌,返回获取令牌成功的时间now := rl.Take()if i > 0 {// 打印每次放行间隔fmt.Println(i, now.Sub(prev))}prev = now}
}
打印结果:
1 10ms
2 10ms
3 10ms
4 10ms
5 10ms
6 10ms
7 10ms
8 10ms
9 10ms
可以看出每次放行间隔10ms,符合预期
uber的漏桶提供了mutex版本
和atomic版本
两种实现,主要区别在于怎么控制并发,以及怎么记录松弛量
mutex版本
数据结构
type mutexLimiter struct {sync.Mutex// 上次请求放行的时刻last time.Time// 桶中积累的松弛量sleepFor time.Duration// 每个请求之间的间隔perRequest time.Duration// 最大松弛量maxSlack time.Durationclock Clock
}
初始化桶:
func newMutexBased(rate int, opts ...Option) *mutexLimiter {config := buildConfig(opts)perRequest := config.per / time.Duration(rate)l := &mutexLimiter{perRequest: perRequest,// config.slack默认值=10, 例如当perRequest=10ms时// maxSlack就是 -10 * 100ms = -1smaxSlack: -1 * time.Duration(config.slack) * perRequest,clock: config.clock,}return l
}func buildConfig(opts []Option) config {c := config{clock: clock.New(),// 最大松弛量默认是perRequest的10倍slack: 10,per: time.Second,}for _, opt := range opts {opt.apply(&c)}return c
}
主要设置两个变量:
-
每次放行时间间隔
perRequest
:根据参数rate计算,rate表示每单位时间per能放行多少请求- 例如当per=1s,rate=100时,每次放行时间间隔perRequest=10ms
-
最大松弛量
maxSlack
:config.slack默认值=10- 例如当perRequest=10ms时,maxSlack就是
-10 * 100ms = -1s
- 例如当perRequest=10ms时,maxSlack就是
获取令牌
要实现标准的漏桶算法,其实比较简单:
记录上次放行时间last
,当本次请求到来时, 如果此刻的时间与last
相比并没有达到 perRequest 规定的间隔大小, sleep 一段时间即可
对应代码为(删除了松弛相关代码):
func (t *mutexLimiter) Take() time.Time {t.Lock()defer t.Unlock()now := t.clock.Now()// 如果第一次请求,直接放行if t.last.IsZero() {t.last = nowreturn t.last}// 当前时间距离上次放行时间的间隔,sleepFor := t.perRequest - now.Sub(t.last)// 如果比规定的间隔小,需要sleepif sleepFor > 0 {t.clock.Sleep(t.sleepFor)t.last = now.Add(t.sleepFor)t.sleepFor = 0return t.last
}
松弛量
如果不引入松弛量,按照标准漏桶算法的流程:
假设现在有三个请求,req1
、req2
和 req3
,获取令牌间隔perRequest规定为 10ms
-
req1
先到来 -
当
req1
完成之后20ms
,req2
才到来,此时距离上次放行有20ms
,大于10ms,可以对req2
放行 -
当
req2
放行后5ms
,req3
到来,此时距离上次放行才5ms
,不足 规定的间隔10ms
,因此还需要等待5ms
才能继续放行req3
这种策略有什么问题?无法应对任何突发流量,没有弹性,定死了相邻两次请求放行的间隔必须大于等于规定的值
于是引入了松弛量的概念:当较长时间(超过perRequest)没有请求到来时,会在桶中积累一些松弛量,这样接下来的请求可以先消耗松弛量,而不会被漏洞的获取令牌间隔限制。直到把松弛量耗尽为止
当然松弛量也不是无限积累,这样当很长时间没有请求后,就跟没有限流没区别了。因此桶中规定了最多积累多少松弛量maxSlack
加上松弛量后代获取令牌代码如下:
-
计算
perRequest - now.Sub(t.last)
- 如果大于0,说明当前时间距离上次放行时间不足规定间隔,需要等待,或者需要消耗松弛量
- 如果小于0,说明当前时间距离上次放行时间超过了规定间隔,那么当前请求一定可以放行,并且可以在桶中积累一些松弛量,给下次请求使用
- 将结果累加到t.sleepFor
-
假设累加的松弛值超过了maxSlack,修正为maxSlack,默认为10倍的规定间隔
-
如果
t.sleepFor > 0
,说明本次请求应该等待的时间,在使用完桶中的松弛量后还不够,需要sleep这段时间,结束后将sleepFor置为0 -
否则
t.sleepFor <= 0
,说明桶中的存储松弛余量可以满足本次请求,本次请求放行
func (t *mutexLimiter) Take() time.Time {t.Lock()defer t.Unlock()now := t.clock.Now()// 如果第一次请求,直接放行if t.last.IsZero() {t.last = nowreturn t.last}// 假设perRequest=10ms,now.Sub(t.last)=5ms,那么需要睡5ms// 假设perRequest=10ms,now.Sub(t.last)=15ms,说明距离上传请求的时间超过了漏桶的限流间隔10ms,那么sleepFor变为-5t.sleepFor += t.perRequest - now.Sub(t.last)// 默认最多松弛 10倍的perRequest// 如果累加的松弛值超过了maxSlack,修正为maxSlackif t.sleepFor < t.maxSlack {t.sleepFor = t.maxSlack}// 需要sleepif t.sleepFor > 0 {t.clock.Sleep(t.sleepFor)t.last = now.Add(t.sleepFor)t.sleepFor = 0// 如果sleepFor <= 0,说明还有存储的松弛余量,可以放行} else {t.last = now}return t.last
}
atomic版本
数据结构
type atomicInt64Limiter struct {// 上次在哪个时刻发放许可state int64// 每次请求间隔perRequest time.Duration// 最大松弛量maxSlack time.Durationclock Clock
}func newAtomicInt64Based(rate int, opts ...Option) *atomicInt64Limiter {config := buildConfig(opts)perRequest := config.per / time.Duration(rate)l := &atomicInt64Limiter{perRequest: perRequest,// 最大松弛量:10倍的perRequestmaxSlack: time.Duration(config.slack) * perRequest,clock: config.clock,}atomic.StoreInt64(&l.state, 0)return l
}
获取令牌
atomic版本的漏桶不是用一个变量存储桶中的松弛量,而是记录了上次在哪个时刻发放许可timeOfNextPermissionIssue
,那松弛量用什么表示呢?当前时间now减去上次发放许可时间timeOfNextPermissionIssue就代表松弛量
怎么理解?这个值代表应该在什么时刻发放令牌:
- 如果该值 比now大,说明需要sleep一段时间等待时间流逝,本次才能获取到令牌
- 如果该值 比now小,说明应该在更早的时间就可以获取令牌,本次请求不需要等待
每次请求时会在timeOfNextPermissionIssue的基础上加上perRequest,并更新timeOfNextPermissionIssue,表示本次发放许可的时间比上次前进了perRequest时间
如果较长时间没有请求,那天然就积累了一些松弛量,因为上次发放许可时间就会比较小,即便加上本次的消耗perRequest也没有now大,就可以直接放行
-
如果上次发放许可时间,加上固定间隔perRequest小于now,说明本次可以放行,并更新上次发放许可时间 += perRequest
- 当然timeOfNextPermissionIssue不能比now小太多,否则限流就没意义了。因此规定该值最多比now小10倍的perRequest,表示松弛量最多为perRequest的10倍
-
否则不能放行,需要等待时间流逝到timeOfNextPermissionIssue + perRequest为止
func (t *atomicInt64Limiter) Take() time.Time {var (newTimeOfNextPermissionIssue int64now int64)for {now = t.clock.Now().UnixNano()// 上次在哪个时刻发放许可timeOfNextPermissionIssue := atomic.LoadInt64(&t.state)switch {// 第一次请求,或者不是第一次请求,且(没有松弛量,且now 比 上次发放许可时间 多了超过perRequest )case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)):// 本次放行,本次在now时刻发放许可newTimeOfNextPermissionIssue = now// 可以有松弛量,且 当前时间 - 上次发放许可时间 > 11倍perRequestcase t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest):// 本次在 now - 最大松弛量 时刻发放许可newTimeOfNextPermissionIssue = now - int64(t.maxSlack)/**1.没有松弛量,且当前时间距离 上次发放许可时间 超过了 不到perRequest,本次应该在上次发放许可时间 + perRequest 再发放2.有松弛量,且 当前时间 - 上次发放许可时间 <= 11倍perRequest,那本次应该在上次发放许可时间 + perRequest 再发放*/default:// 更新上次发放许可时间,+= perRequestnewTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest)}if atomic.CompareAndSwapInt64(&t.state, timeOfNextPermissionIssue, newTimeOfNextPermissionIssue) {break}}sleepDuration := time.Duration(newTimeOfNextPermissionIssue - now)// 没有余量了,需要sleepif sleepDuration > 0 {t.clock.Sleep(sleepDuration)// 返回放行时的时间return time.Unix(0, newTimeOfNextPermissionIssue)}return time.Unix(0, now)
}
测试漏桶的松弛量
下面对漏桶的松弛量进行测试:如果先积累一些松弛量,就会起到因对突发流量的效果,例如让时间先走45ms
func Example_default() {// 每10ms放行一个请求, 最大松弛量=100msrl := ratelimit.New(100)// 在0ms发放许可rl.Take()// 此时时间来到45mstime.Sleep(time.Millisecond * 45)prev := time.Now()for i := 0; i < 10; i++ {now := rl.Take()if i > 0 {fmt.Println(i, now.Sub(prev))}prev = now}
}
打印结果:
1 1µs
2 103µs
3 4µs
4 4.79ms
5 10ms
6 10ms
7 10ms
8 10ms
9 10ms
可以看出:前4次都没等,直接获取到令牌,第4次等了大约5ms,之后每次放行间隔都是10ms
执行到for循环时,上次发放许可时间10ms,当前时刻45ms
拆解for循环中的每次take中的漏桶状态:
- i=0, 下次应该在
10ms
发放许可,当前时刻45ms,放行 - i=1, 下次应该在
20ms
发放许可,当前时刻45ms,放行 - i=2, 下次应该在
30ms
发放许可,当前时刻45ms,放行 - i=3, 下次应该在
40ms
发放许可,当前时刻45ms,放行 - i=4, 下次应该在
50ms
发放许可,当前时刻45ms,更新timeOfNextPermissionIssue=50,需要sleep5ms
- i=5, 下次应该在
60ms
发放许可,当前50ms,需要sleep10ms
,更新timeOfNextPermissionIssue=60
如下图所示:
总结
加上松弛量后,这个漏桶和标准的令牌桶区别就不大了:
- 都能应对一定的突发流量
- 当桶中没有松弛量(没有令牌时),都按照固定时间间隔放行请求
这个库有个问题是:对桶中等待中的请求数没有限制,这样当并发量非常大时,会导致请求一直在桶中堆积
因此工程中要使用的话,最好对源码进行改造:当等待中的请求数超过阈值就快速返回失败
相关文章:

开源限流组件分析(二):uber-go/ratelimit
文章目录 本系列漏桶限流算法uber的漏桶算法使用mutex版本数据结构获取令牌松弛量 atomic版本数据结构获取令牌测试漏桶的松弛量 总结 本系列 开源限流组件分析(一):juju/ratelimit开源限流组件分析(二):u…...

探索 SVG 创作新维度:svgwrite 库揭秘
文章目录 **探索 SVG 创作新维度:svgwrite 库揭秘**背景介绍库简介安装指南基础函数使用实战场景常见问题与解决方案总结 探索 SVG 创作新维度:svgwrite 库揭秘 背景介绍 在数字艺术和网页设计领域,SVG(Scalable Vector Graphic…...

为什么要做PFAS测试?PFAS检测项目详细介绍
PFAS测试之所以重要,主要归因于PFAS(全氟和多氟化合物)的广泛存在、持久性、生物累积性和潜在的毒性。这些特性使得PFAS在环境和人体中可能长期存在,并对生态系统和人类健康构成威胁。以下是对PFAS检测项目的详细介绍以及进行PFAS…...

稀土阻燃协效剂的应用
稀土阻燃协效剂是一类利用稀土元素(如铈、镧、钕、铕等)具有的独特性质,来增强材料阻燃性能的化学物质。在聚合物材料燃烧时可催化酯花成碳,迅速在高分子表面形成致密连续的碳层,隔绝聚合物材料内部的可燃性气体与氮气…...

Java的异常处理
常见异常 ① 运行时异常 a、ClassNotFoundException b、FileNotFoundException c、IOException ② 编译时异常 a、ArrayIndexOutOfBoundsException b、NullPointerException c、ClassCastException d、InputFormatException e、InputMismatchException f、ArithmeticException …...

免费域名邮箱申请和使用教程:有哪些步骤?
免费域名邮箱设置指南?如何免费注册烽火域名邮箱? 对于个人和企业而言,拥有一个专属的域名邮箱不仅能提升专业形象,还能增强品牌识别度。烽火将详细介绍如何申请和使用免费域名邮箱,帮助您轻松拥有一个专属的电子邮件…...

Linux之实战命令45:swapon应用实例(七十九)
简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【…...

提升数据处理效率:TDengine S3 的最佳实践与应用
在当今数据驱动的时代,如何高效地存储与处理海量数据成为了企业面临的一大挑战。为了解决这一问题,我们在 TDengine 3.2.2.0 首次发布了企业级功能 S3 存储。这一功能经历多个版本的迭代与完善后,逐渐发展成为一个全面和高效的解决方案。 S3…...

高级算法设计与分析 学习笔记13 线性规划
注意是线性规划不是动态规划哦 好家伙,这不是凸优化吗? 凸优化标准形式: 先改成统一最大化(凸优化那边怎么是统一最小化?) 原来的x2正负无所谓,但我希望每个x都是有限制的,所以把它改…...

2024年11月软考中项应试技巧与机考注意事项!
软考中项的备考技巧 重点来了!这部分是我辛苦总结出来的备考技巧,都是我当年备考时逐渐整合出来的,绝对够用,赶紧跟我一起掌握吧! 1.基础知识 在学习时建议大家先跟着班课老师结合教材过一遍基础知识。强调跟着班课…...

网络编程中容易踩的坑罗列,谨记!
1、TCP没考虑粘包分包 TCP是面向连接的可靠协议,TCP是流式协议,创建TCP套接字的类型为SOCK_STREAM int sockfd socket(AF_INET, SOCK_STREAM, 0);很多同学面试时对书上的话背诵如流,在实际TCP编程中却没有处理粘包和分包的代码,以…...

SD-WAN:推动企业网络优化与发展
近年来,软件定义广域网(SD-WAN)逐渐成为众多企业的首选网络解决方案。这背后的原因是什么?接下来我们将深入探讨这一趋势。 在快速发展的通信技术领域,企业对高效、灵活且可扩展的网络架构需求愈发迫切。随着数据流量的…...

[MyBatis-Plus]扩展功能详解
代码生成 使用MP的步骤是非常固定的几步操作 基于插件, 可以快速的生成基础性的代码 安装插件安装完成后重启IEDA连接数据库 mp是数据库的名字?serverTimezoneUTC 是修复mysql时区, 不加会报错 生成代码 TablePrefix选项是用于去除表名的前缀, 比如根据tb_user表生成实体类U…...

循序渐进丨MogDB 5.0 远程访问 MogDB/Oracle 数据库的简便方法(使用@符号)
概述 早期的 MogDB 就提供了Postgres_fdw、Oracle_fdw、MySQL_fdw3个插件,用于远程访问 MogDB/Oracle/MySQL数据库。 旧的版本中,访问远程数据库的表,需要显式创建外部表,而在 MogDB 5.0当中,这种用法得到了简化&…...

大模型训练触达「瓶颈」,基座模型厂商还有必要坚持预训练吗?
进入2024年来,中国大模型行业从狂奔进入到了“长跑阶段”。无论是在技术侧,还是在产业侧,行业内都产生了更多新的思考。 从技术发展上看,在算力受限的情况下,中国基座模型的研发能力在全球范围内处在什么身位、如何追赶…...

media3 exoplayer 扩展解码库在这里 take it , please !
Media3 ExoPlayer 扩展解码库介绍 请注意,本文讨论的是 Media3 ExoPlayer 而不是 Google ExoPlayer2。详细参考:Media3 ExoPlayer 迁移指南 文章最后提供了已经编译好的AAR文件,直接引用即可!!! 为什么选…...

在Xshell中查看日志文件详情
学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...

深入理解计算机系统--计算机系统漫游
对于一段最基础代码的文件hello.c,解释程序的运行 #include <stdio.h>int main() {printf ( "Hello, world\n") ;return 0; }1.1、信息就是位上下文 源程序是由值 0 和 1 组成的位(比特)序列,8 个位被组织成一组…...

哪些指标可以用来评估精益生产现场管理和改善的效果?
在探讨如何评估精益生产现场管理和改善效果时,我们需要关注一系列关键指标,这些指标不仅反映了生产流程的效率,还体现了质量和客户满意度的提升。详细如天行健企业管理咨询公司下文所述: 1. 生产效率 每小时生产数量(…...

在 Linux 系统上安装免费杀毒软件
选择合适的免费杀毒软件 Linux 上流行的免费杀毒软件: . ClamAV:最为知名的开源免费杀毒软件,支持多种 Linux 发行版。它可以扫描病毒、恶意软件以及 Windows 系统上的威胁。 Sophos Antivirus for Linux:虽然是商业软件ÿ…...

第 7 章:Vue UI 组件库
1. PC 端常用 UI 组件库 Element UI:https://element.eleme.cnIView UI:https://www.iviewui.com 2. 移动端常用 UI 组件库 Vant:https://youzan.github.io/vantCube UI:https://didi.github.io/cube-uiMint UI:htt…...

【SQL】SQL用户管理和权限
🎄 管理用户 📢 用来管理数据库用户、控制数据库的访 问权限 ⭐ 查询用户 📢 mysql数据库中的user表中,存放了当前数据库中所有的用户和用户的权限 use mysql; select * from user; 📢 其中Host代表当前用户访问的主机, 如果为…...

STM32应用详解(5)USART串口初始化
文章目录 一、USART初始化二、代码说明1.原理图2.main函数3.USART串口初始化函数4.代码整体结构 三、USART串口初始化总结 一、USART初始化 所谓的对USART进行初始化,就是对USART固件库函数的调用,来完成串口(USART)的设置,比如设置波特率、…...

渗透实战 JS文件怎么利用
1.前言 关于JS在渗透测试中的关键作用,想必不用过多强调,在互联网上也有许多从JS中找到敏感信息从而拿下关键系统的案例。大部分师傅喜欢使用findsomething之类的浏览器插件,也有使用诸如Unexpected.information以及APIFinder之类的Burp插件…...

啥是CTF?新手如何入门CTF?
CTF是啥 CTF 是 Capture The Flag 的简称,中文咱们叫夺旗赛,其本意是西方的一种传统运动。在比赛上两军会互相争夺旗帜,当有一方的旗帜已被敌军夺取,就代表了那一方的战败。在信息安全领域的 CTF 是说,通过各种攻击手法…...

解决python多环境冲突问题
解决Python多环境冲突问题,以下是一些详细的解决方法: 1. 使用虚拟环境 虚拟环境允许你为每个项目创建独立的Python环境,每个环境可以有自己的库和依赖。常用的工具包括venv、virtualenv和pipenv。 使用 venv venv 是Python 3.3及以上版本…...

Aatrox-Bert-VITS2部署指南
一、模型介绍 【AI 剑魔 ①】在线语音合成(Bert-Vits2),将输入文字转化成暗裔剑魔亚托克斯音色的音频输出。 作者:Xz 乔希 https://space.bilibili.com/5859321 声音归属:Riot Games《英雄联盟》暗裔剑魔亚托克斯 …...

计算不停歇,百度沧海数据湖存储加速方案 2.0 设计和实践
本文整理自百度云智峰会 2024 —— 云原生论坛的同名演讲。 今天给大家介绍下百度沧海存储团队在数据湖加速方面的工作进展情况。 数据湖这个概念,从 2012 年产生到现在已经有十余年的时间,每家公司对它内涵的解读都不太一样。但是数据湖的主要存储底座…...

vue2项目 实现上边两个下拉框,下边一个输入框 输入框内显示的值为[“第一个下拉框选中值“ -- “第二个下拉框选中的值“]
效果: 思路: 采用vue中 [computed:] 派生属性的方式实现联动效果,上边两个切换时,下边的跟随变动 demo代码: <template><div><!-- 第一个下拉框 --><select v-model"firstValue"><option v-for"option in options" :key&q…...

el-radio 点击报错 Element with focus: inputAncestor with aria-hidden....
一、序言 浏览器版本影响的问题(与代码无关,可能是web或浏览器相关协议更新导致),不影响功能的使用. 翻译:元素上的Blocked aria-hidden,因为刚刚接收焦点的元素不能对辅助技术用户隐藏。避免在焦点元素或…...