Go语言中的速率限流策略全面详解
作者:低配全栈
引言
在前一篇文章中我们提到,为了防止缓存穿透,我们可以在应用里面的缓存层的上一层添加了singleflight
。但是,我们也提到,即使使用了singleflight
,我们一样还是会存在问题:
使用singleflight本身并不会直接导致OOM的发生,但是需要考虑到极端情况下导致内存使用增加,如果有大量唯一的请求快速连续到来,并且它们的处理时间相对较长,则这些请求可能会在内存中积累,从而增加内存使用.
为了解决这种问题,今天在这里介绍一下GO语言中限流策略(Rating Limit)的使用。
1. 什么是速率限流
在 Go 语言中,Rate Limit(速率限制)是一种控制资源利用率的重要机制,尤其适用于控制对外部资源的访问速率,例如 API 请求或数据库操作。Go 标准库中的golang.org/x/time/rate包提供了实现速率限制的功能。
核心概念
Limiter(限制器):
rate.Limiter
结构体是实现速率限制的主要组件。它使用令牌桶算法来控制事件发生的频率。令牌桶算法: 这是一种通过固定速率向桶中添加令牌来控制资源访问速率的算法。如果桶中有足够的令牌,请求就可以立即处理;如果没有,则请求需要等待或被拒绝。
2. 基本使用
首先看一个具体的例子:
import ( "context" "fmt" "log" "time" "golang.org/x/time/rate" ) func main() { // 创建一个新的限制器,每秒产生5个令牌,最大桶容量为5 limiter := rate.NewLimiter(5, 5) // 模拟连续请求 for i := 0; i < 10; i++ { i := i go func() { // 等待下一个令牌 err := limiter.Wait(context.Background()) if err != nil { log.Fatal(err) } // 令牌已获取,执行API请求 fmt.Println("Sending API request", i, "at", time.Now().Format(time.RFC3339)) // 这里可以添加执行实际 API 请求的代码 }() } time.Sleep(10 * time.Second) }
运行结果如下:
我们可以看到,因为rate.Limit的限制,大约每1秒被限制发送5次请求。
细节说明
创建限制器:
rate.NewLimiter(5, 5)
创建了一个每秒生成 5 个令牌,最大桶容量为 5 的限制器。循环请求: 通过一个循环来模拟连续的 API 请求。
等待令牌: 使用
limiter.Wait(context.Background())
在每次请求之前等待令牌。这个调用会阻塞,直到获取到令牌为止。执行请求: 一旦获取到令牌,就打印一条消息表示发送了一个 API 请求。在实际应用中,这里可以替换为实际的 API 调用代码。
3. 动态调整限速器速率
在 Go 语言中,rate.Limiter
提供了动态调整速率的功能。通过这种方式i允许你在运行时根据需要改变速率限制,这在很多实际应用中非常有用,比如基于当前服务器负载或外部服务的可用性来调整请求速率。
rate.Limiter
提供了 SetLimit
和 SetBurst
两个方法来动态调整限制器的速率和桶大小:
SetLimit(rate.Limit): 这个方法用来设置每秒可以生成的令牌数。
rate.Limit
是一个基于浮点数的类型,用来表示每秒允许的事件数。SetBurst(int): 这个方法用来设置限制器的桶大小。桶大小决定了在任何给定时间内限制器可以允许的最大事件数。
package main import ( "context" "fmt" "time" "golang.org/x/time/rate" ) func main() { // 初始速率为每秒2个请求 limiter := rate.NewLimiter(2, 2) // 模拟动态调整速率 go func() { for { time.Sleep(5 * time.Second) // 每5秒动态调整速率和桶大小 newRate := rate.Limit(float64(time.Now().Second()) / 10) limiter.SetLimit(newRate) limiter.SetBurst(int(newRate)) fmt.Println("Rate updated to:", newRate) } }() // 模拟请求 for i := 0; ; i++ { err := limiter.Wait(context.Background()) if err != nil { fmt.Println("Error:", err) continue } fmt.Println("Request", i, "at", time.Now().Format(time.RFC3339)) } }
通过动态调整 rate.Limiter
的速率和桶大小,你可以灵活地控制应用中的速率限制,使其更加适应变化的环境和需求。上述例子中,每5s会更新速率,每次更新速率之后,请求数量可以实现动态调整:
运行结果:
4. 应用场景
rate.Limiter
在 Go 语言中的应用场景广泛,主要用于控制资源的使用频率,以防止过载、滥用或达到限制。以下是一些常见的应用场景:
1. API 请求限制
当与外部服务(如 REST API)交互时,通常会有每秒或每分钟的请求限制。使用 rate.Limiter
可以确保你的应用不会超过这些限制,从而避免触发服务端的速率限制错误或被暂时禁止访问。
2. 数据库访问控制
在高并发环境下,过多的数据库查询可能会导致性能下降或服务不可用。通过限制数据库操作的频率,可以减轻数据库的负载,提高应用的稳定性和响应速度。
3. 限制用户操作
在某些应用中,可能需要限制用户执行特定操作的频率,例如发送消息、提交表单或请求验证码。这有助于防止滥用和自动化攻击,同时保持系统资源的合理使用。
总结
rate.Limiter
的使用场景体现了其灵活性和实用性。无论是保护外部服务不被过载、控制资源访问、还是提高应用的整体稳定性,它都是一个非常有效的工具。在设计系统时,考虑到这些场景,合理地应用速率限制,可以显著提升系统的健壮性和用户体验。
以上就是Go语言中的速率限流策略全面详解的详细内容,更多关于Go语言速率限流的资料请关注脚本之家其它相关文章!