「GoCN酷Go推荐」漏桶限流库 — uber-go/ratelimit
2022/10/2 14:19:24
本文主要是介绍「GoCN酷Go推荐」漏桶限流库 — uber-go/ratelimit,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
上次有同学分享了 单机限流器 time/rate 库,讲了 Golang 标准库中基于令牌桶实现限流组件的 time/rate 使用,同时也讲了一些限流算法原理。
这里分享一个 uber 开源的一套基于漏桶实现的用于服务限流的 golang 库 ratelimit。
漏洞算法的理解起来,相较于令牌桶,没有那么直观。因为令牌桶是限制 “令牌” 的速率,拿到令牌的请求才能被放行,而漏桶是限制单位时间内允许通过的请求数目,调用方只能严格按照预定的间隔顺序进行消费调用。
1
ratelimit 的基本使用
官方示例:
import ( "fmt" "time" "go.uber.org/ratelimit" ) func main() { rl := ratelimit.New(100) // per second prev := time.Now() for i := 0; i < 10; i++ { now := rl.Take() fmt.Println(i, now.Sub(prev)) prev = now } }
在这个例子中,我们给定限流器每秒可以通过 100 个请求,也就是平均每个请求间隔 10ms。因此,最终会每 10ms 打印一行数据。输出结果如下:
// Output: // 0 0 // 1 10ms // 2 10ms // 3 10ms // 4 10ms // 5 10ms // 6 10ms // 7 10ms // 8 10ms // 9 10ms
2
高级用法
最大松弛量
传统的漏桶,每个请求的间隔是固定、匀速的,两次请求 req1 和 req2 之间的延迟至少应该 >=perRequest,然而,在实际上的应用中,流量经常是突发性的。对于突发这种情况,uber-go 对 Leaky Bucket 做了一些改良,引入了最大松弛量(maxSlack)的概念, 表示允许抵消的最长时间
漏桶算法的限速逻辑:
sleepFor = t.perRequest - now.Sub(t.last) if sleepFor > 0 { t.clock.Sleep(sleepFor) t.last = now.Add(sleepFor) } else { t.last = now }
上面计算 sleepFor 的代码,不使用最大松弛量的 sleep 逻辑,严格要求两个请求之间必须间隔 t.perRequest 的时间,那么计算 sleepFor 的代码就是:
t.sleepFor = t.perRequest - now.Sub(t.last)
这样就会有以下问题:
我们假设现在有三个请求,req1、req2 和 req3,限速区间为 10ms。req1 最先到来,当 req1 完成之后 15ms ,req2 才到来,依据限速策略可以对 req2 立即处理,当 req2 完成后,5ms 后, req3 到来,这个时候距离上次请求还不足 10ms,因此还需要等待 5ms 才能继续执行, 但是,对于这种情况,实际上这三个请求一共消耗了 25ms 才完成,并不是预期的 20ms。
上面这个 case 模拟了一种请求量突发的状况。但这里我们可以把之前间隔比较长的请求的时间(累加超出 perRequest 的时延),匀给后面的请求判断限流时使用。对于上面的 case,因为 req2 相当于多等了 5ms,我们可以把这 5ms 移给 req3 使用。加上 req3 本身就是 5ms 之后过来的,一共刚好 10ms,所以 req3 无需等待,直接可以处理。此时三个请求也恰好一共是 20ms。如下:
t.sleepFor += t.perRequest - now.Sub(t.last) if t.sleepFor > 0 { t.clock.Sleep(t.sleepFor) t.last = now.Add(t.sleepFor) t.sleepFor = 0 } else { t.last = now }
当 t.sleepFor > 0,代表此前的请求多余出来的时间,无法完全抵消此次的所需量,因此需要 sleep 相应时间, 同时将 t.sleepFor 置为 0。
当 t.sleepFor < 0,说明此次请求间隔大于预期间隔,将多出来的时间累加到 t.sleepFor 即可。
但是,对于某种情况,请求 1 完成后,请求 2 过了很久到达 ,那么此时对于请求 2 的请求间隔 now.Sub(t.last),会非常大。以至于即使后面大量请求瞬时到达,也无法抵消完这个时间。那这样就失去了限流的意义。
为了防止这种情况,ratelimit 就引入了最大松弛量 (maxSlack) 的概念, 该值为负值,表示允许抵消的最长时间,防止以上情况的出现。
// 当计算出来的 sleepFor 超过一个 maxSlack 时,那么就只 sleep 一个 maxSlack 时间,用于两次请求时间间隔过大的场景 if t.sleepFor < t.maxSlack { t.sleepFor = t.maxSlack }
ratelimit 中 maxSlack 的值为 -10 * time.Second / time.Duration(rate), 是十个请求的间隔大小。我们也可以理解为 ratelimit 允许的最大瞬时请求为 10。
用法
ratelimit 的 New 函数,除了可以配置每秒请求数 (QPS), 还提供了一套可选配置项 Option。
func New(rate int, opts ...Option) Limiter
WithoutSlack
上面说到 ratelimit 中引入了最大松弛量的概念,而且默认的最大松弛量为 10 个请求的间隔时间。
但是对于需要严格的限制请求的固定间隔的时,我们可以利用 WithoutSlack 来取消松弛量的影响。
limiter := ratelimit.New(100, ratelimit.WithoutSlack)
另外,ratelimit 还有其他选项,比如WithClock(clock Clock) 可以用来替换默认时钟,来实现精度更高或者特殊需求的计时场景。
3
Reference
uber-go 漏桶限流器使用与原理分析 | 编程沉思录 (cyhone.com)
开源限流组件分析(一):Uber 的 Leaky Bucket - 熊喵君的博客 | PANDAYCHEN
限流算法之一 | hzSomthing (hedzr.com)
uber-go/ratelimit: A Golang blocking leaky-bucket rate limit implementation (github.com)
《酷Go推荐》招募:
各位Gopher同学,最近我们社区打算推出一个类似GoCN每日新闻的新栏目《酷Go推荐》,主要是每周推荐一个库或者好的项目,然后写一点这个库使用方法或者优点之类的,这样可以真正的帮助到大家能够学习到
新的库,并且知道怎么用。
扫码也可以加入 GoCN 的大家族哟~
上次有同学分享了 单机限流器 time/rate 库,讲了 Golang 标准库中基于令牌桶实现限流组件的 time/rate 使用,同时也讲了一些限流算法原理。 这里分享一个 uber 开源的一套基于漏桶实现的用于服务限流的 golang 库 ratelimit。 漏洞算法的理解起来,相较于令牌桶,没有那么直观。因为令牌桶是限制 “令牌” 的速率,拿到令牌的请求才能被放行,而漏桶是限制单位时间内允许通过的请求数目,调用方只能严格按照预定的间隔顺序进行消费调用。 1 ratelimit 的基本使用 官方示例: import ( "fmt" "time" "go.uber.org/ratelimit" ) func main() { rl := ratelimit.New(100) // per second prev := time.Now() for i := 0; i < 10; i++ { now := rl.Take() fmt.Println(i, now.Sub(prev)) prev = now } } 在这个例子中,我们给定限流器每秒可以通过 100 个请求,也就是平均每个请求间隔 10ms。因此,最终会每 10ms 打印一行数据。输出结果如下: // Output: // 0 0 // 1 10ms // 2 10ms // 3 10ms // 4 10ms // 5 10ms // 6 10ms // 7 10ms // 8 10ms // 9 10ms 2 高级用法 最大松弛量 传统的漏桶,每个请求的间隔是固定、匀速的,两次请求 req1 和 req2 之间的延迟至少应该 >=perRequest,然而,在实际上的应用中,流量经常是突发性的。对于突发这种情况,uber-go 对 Leaky Bucket 做了一些改良,引入了最大松弛量(maxSlack)的概念, 表示允许抵消的最长时间 漏桶算法的限速逻辑: sleepFor = t.perRequest - now.Sub(t.last) if sleepFor > 0 { t.clock.Sleep(sleepFor) t.last = now.Add(sleepFor) } else { t.last = now } 上面计算 sleepFor 的代码,不使用最大松弛量的 sleep 逻辑,严格要求两个请求之间必须间隔 t.perRequest 的时间,那么计算 sleepFor 的代码就是: t.sleepFor = t.perRequest - now.Sub(t.last) 这样就会有以下问题: 我们假设现在有三个请求,req1、req2 和 req3,限速区间为 10ms。req1 最先到来,当 req1 完成之后 15ms ,req2 才到来,依据限速策略可以对 req2 立即处理,当 req2 完成后,5ms 后, req3 到来,这个时候距离上次请求还不足 10ms,因此还需要等待 5ms 才能继续执行, 但是,对于这种情况,实际上这三个请求一共消耗了 25ms 才完成,并不是预期的 20ms。 上面这个 case 模拟了一种请求量突发的状况。但这里我们可以把之前间隔比较长的请求的时间(累加超出 perRequest 的时延),匀给后面的请求判断限流时使用。对于上面的 case,因为 req2 相当于多等了 5ms,我们可以把这 5ms 移给 req3 使用。加上 req3 本身就是 5ms 之后过来的,一共刚好 10ms,所以 req3 无需等待,直接可以处理。此时三个请求也恰好一共是 20ms。如下: t.sleepFor += t.perRequest - now.Sub(t.last) if t.sleepFor > 0 { t.clock.Sleep(t.sleepFor) t.last = now.Add(t.sleepFor) t.sleepFor = 0 } else { t.last = now } 当 t.sleepFor > 0,代表此前的请求多余出来的时间,无法完全抵消此次的所需量,因此需要 sleep 相应时间, 同时将 t.sleepFor 置为 0。 当 t.sleepFor < 0,说明此次请求间隔大于预期间隔,将多出来的时间累加到 t.sleepFor 即可。 但是,对于某种情况,请求 1 完成后,请求 2 过了很久到达 ,那么此时对于请求 2 的请求间隔 now.Sub(t.last),会非常大。以至于即使后面大量请求瞬时到达,也无法抵消完这个时间。那这样就失去了限流的意义。 为了防止这种情况,ratelimit 就引入了最大松弛量 (maxSlack) 的概念, 该值为负值,表示允许抵消的最长时间,防止以上情况的出现。 // 当计算出来的 sleepFor 超过一个 maxSlack 时,那么就只 sleep 一个 maxSlack 时间,用于两次请求时间间隔过大的场景 if t.sleepFor < t.maxSlack { t.sleepFor = t.maxSlack } ratelimit 中 maxSlack 的值为 -10 * time.Second / time.Duration(rate), 是十个请求的间隔大小。我们也可以理解为 ratelimit 允许的最大瞬时请求为 10。 用法 ratelimit 的 New 函数,除了可以配置每秒请求数 (QPS), 还提供了一套可选配置项 Option。 func New(rate int, opts ...Option) Limiter WithoutSlack 上面说到 ratelimit 中引入了最大松弛量的概念,而且默认的最大松弛量为 10 个请求的间隔时间。 但是对于需要严格的限制请求的固定间隔的时,我们可以利用 WithoutSlack 来取消松弛量的影响。 limiter := ratelimit.New(100, ratelimit.WithoutSlack) 另外,ratelimit 还有其他选项,比如WithClock(clock Clock) 可以用来替换默认时钟,来实现精度更高或者特殊需求的计时场景。 3 Reference uber-go 漏桶限流器使用与原理分析 | 编程沉思录 (cyhone.com) 开源限流组件分析(一):Uber 的 Leaky Bucket - 熊喵君的博客 | PANDAYCHEN 限流算法之一 | hzSomthing (hedzr.com) uber-go/ratelimit: A Golang blocking leaky-bucket rate limit implementation (github.com) 《酷Go推荐》招募: 各位Gopher同学,最近我们社区打算推出一个类似GoCN每日新闻的新栏目《酷Go推荐》,主要是每周推荐一个库或者好的项目,然后写一点这个库使用方法或者优点之类的,这样可以真正的帮助到大家能够学习到 新的库,并且知道怎么用。 扫码也可以加入 GoCN 的大家族哟~这篇关于「GoCN酷Go推荐」漏桶限流库 — uber-go/ratelimit的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-20go-zero 框架的 RPC 服务 启动start和停止 底层是怎么实现的?-icode9专业技术文章分享
- 2024-12-19Go-Zero 框架的 RPC 服务启动和停止的基本机制和过程是怎么实现的?-icode9专业技术文章分享
- 2024-12-18怎么在golang中使用gRPC测试mock数据?-icode9专业技术文章分享
- 2024-12-15掌握PageRank算法核心!你离Google优化高手只差一步!
- 2024-12-15GORM 中的标签 gorm:"index"是什么?-icode9专业技术文章分享
- 2024-12-11怎么在 Go 语言中获取 Open vSwitch (OVS) 的桥接信息(Bridge)?-icode9专业技术文章分享
- 2024-12-11怎么用Go 语言的库来与 Open vSwitch 进行交互?-icode9专业技术文章分享
- 2024-12-11怎么在 go-zero 项目中发送阿里云短信?-icode9专业技术文章分享
- 2024-12-11怎么使用阿里云 Go SDK (alibaba-cloud-sdk-go) 发送短信?-icode9专业技术文章分享
- 2024-12-10搭建个人博客网站之一、使用hugo创建个人博客网站