写点什么

Golang 高效流控实践

作者:俞凡
  • 2024-03-16
    上海
  • 本文字数:2878 字

    阅读完需:约 9 分钟

流控对于构建高可靠弹性系统至关重要,本文介绍了 Golang 内置的流控组件,通过该组件就可以打造适合各种业务场景的流控系统。原文: Rate Limiting in Go: Controlling Traffic with Efficiency


Jon Cellier @Unsplash

导言

流控(Rate limiting)是构建可扩展弹性系统的重要技术之一,目的是通过限制指定时间内允许通过的请求数量来控制流量。在 Go 中实施流控可以确保最佳的资源利用率,并保护应用不被过多的流量或滥用行为所冲垮。本文将探讨 Go 中的流控技术,并提供代码示例,帮助感兴趣的读者有效实施这些技术。

了解流控

流控包括定义一套规则,确定客户端在给定时间窗口内可以发出多少请求,从而确保系统能够处理负载,防止滥用或拒绝服务攻击。两种常见的流控方法是:


  • 固定窗口流控(Fixed Window Rate Limiting):在这种方法中,在一个固定时间窗口内执行流控。例如,如果流控设置为每分钟 100 个请求,则系统在任何给定的 60 秒窗口内最多允许 100 个请求,超过此限制的请求将被拒绝或延迟到下一个时间窗口。

  • 令牌桶流控(Token Bucket Rate Limiting):令牌桶流控基于令牌从桶中消耗的概念。令牌桶最初装满固定数量的令牌,每个令牌代表一个请求。当客户端要发出请求时,必须从桶中获取一个令牌。如果桶是空的,客户端必须等待,直到有令牌可用。

在 Go 中实施流控

Go 提供了一个名为 golang.org/x/time/rate 的内置软件包,实现了流控功能。接下来我们看看如何使用固定窗口和令牌桶两种方法来实现流控。

固定窗口流控
package main
import ( "fmt" "golang.org/x/time/rate" "time")
func main() { limiter := rate.NewLimiter(rate.Limit(100), 1) // Allow 100 requests per second
for i := 0; i < 200; i++ { if !limiter.Allow() { fmt.Println("Rate limit exceeded. Request rejected.") continue } // Process the request fmt.Println("Request processed successfully.") time.Sleep(time.Millisecond * 100) // Simulate request processing time }}
复制代码


在上面的代码片段中,我们用 rate.NewLimiter 创建了一个限制器,其速率限制为每秒 100 个请求。每个请求都会调用 limiter.Allow() 方法,如果允许请求,则返回 true,如果超过速率限制,则返回 false,超过速率限制的请求将被拒绝。

令牌桶流控
package main
import ( "fmt" "golang.org/x/time/rate" "time")
func main() { limiter := rate.NewLimiter(rate.Limit(10), 5) // Allow 10 requests per second with a burst of 5
for i := 0; i < 15; i++ { if err := limiter.Wait(context.TODO()); err != nil { fmt.Println("Rate limit exceeded. Request rejected.") continue } // Process the request fmt.Println("Request processed successfully.") time.Sleep(time.Millisecond * 100) // Simulate request processing time }}
复制代码


在上述代码中,我们用 rate.NewLimiter 创建了一个限制器,其速率限制为每秒 10 个请求,允许 5 个并发请求。每个请求都会调用 limiter.Wait() 方法,该方法会阻塞直到有令牌可用。如果令牌桶是空的,没有可用令牌,请求就会被拒绝。

动态流控

动态流控是指根据客户端行为、系统负载或业务规则等动态因素调整速率限制。这种技术允许我们实时调整流控,以优化资源利用率并提供更好的用户体验。让我们看看 Go 中动态流控的示例:


package main
import ( "fmt" "golang.org/x/time/rate" "time")
func main() { limiter := rate.NewLimiter(rate.Limit(100), 1) // Initial rate limit of 100 requests per second
// Dynamic rate adjustment go func() { time.Sleep(time.Minute) // Adjust rate every minute limiter.SetLimit(rate.Limit(200)) // Increase rate limit to 200 requests per second }()
for i := 0; i < 300; i++ { if !limiter.Allow() { fmt.Println("Rate limit exceeded. Request rejected.") continue } // Process the request fmt.Println("Request processed successfully.") time.Sleep(time.Millisecond * 100) // Simulate request processing time }}
复制代码


在上面的代码片段中,我们创建了一个限制器,初始速率限制为每秒 100 个请求。然后,启动一个 goroutine,在一分钟后将速率限制调整为每秒 200 个请求。这样,我们就能根据不断变化的情况动态调整流控。

自适应流控

自适应流控可根据之前请求的响应时间或错误率动态调整速率限制,从而允许系统自动适应不同的流量条件,确保获得最佳性能和资源利用率。让我们看看 Go 中自适应流控示例:


package main
import ( "fmt" "golang.org/x/time/rate" "time")
func main() { limiter := rate.NewLimiter(rate.Limit(100), 1) // Initial rate limit of 100 requests per second
// Adaptive rate adjustment go func() { for { responseTime := measureResponseTime() // Measure the response time of previous requests if responseTime > 500*time.Millisecond { limiter.SetLimit(rate.Limit(50)) // Decrease rate limit to 50 requests per second } else { limiter.SetLimit(rate.Limit(100)) // Increase rate limit to 100 requests per second } time.Sleep(time.Minute) // Adjust rate every minute } }()
for i := 0; i < 200; i++ { if !limiter.Allow() { fmt.Println("Rate limit exceeded. Request rejected.") continue } // Process the request fmt.Println("Request processed successfully.") time.Sleep(time.Millisecond * 100) // Simulate request processing time }}
func measureResponseTime() time.Duration { // Measure the response time of previous requests // Implement your own logic to measure the response time return time.Millisecond * 200}
复制代码


在上述代码片段中,我们用 measureResponseTime 函数模拟测量之前请求的响应时间。根据测量到的响应时间,通过 limiter.SetLimit 设置不同的值来动态调整速率限制。这样,系统就能根据观察到的响应时间调整其流控策略。

结论

Jo Jo @Unsplash


流控是保障 Go 应用程序稳定性和安全性的基本技术。通过有效控制传入请求的流量,可以防止资源耗尽并确保资源的公平分配。本文探讨了固定窗口和令牌桶流控的概念,并提供了代码片段,演示了如何基于 golang.org/x/time/rate 包实现流控策略,帮助读者将流控纳入应用程序,以构建能够高效处理不同流量水平的弹性系统。




你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

发布于: 刚刚阅读数: 5
用户头像

俞凡

关注

公众号:DeepNoMind 2017-10-18 加入

俞凡,Mavenir Systems研发总监,关注高可用架构、高性能服务、5G、人工智能、区块链、DevOps、Agile等。公众号:DeepNoMind

评论

发布
暂无评论
Golang高效流控实践_golang_俞凡_InfoQ写作社区