本文主要介绍常见的限速算法,以及这些算法在 Golang 中有哪些开源实现,并提供一些简单的示例。
常见的限速算法有:
项目地址:
https://github.com/uber-go/ratelimit
安装:
xxxxxxxxxxgo get go.uber.org/ratelimit使用示例:
x
package main
import ( "fmt" "go.uber.org/ratelimit" "time")
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 }}运行上面的示例,将看到类似下面的输出:
xxxxxxxxxx0 225ns1 10ms2 10ms3 10ms4 10ms5 10ms6 10ms7 10ms8 10ms9 10ms
在上面的例子中,限流器每秒可以通过 100 个请求,每个请求间隔 10 ms。如果想了解更多关于 uber/ratelimit 的信息,可以参考文末列出的文档。
文档地址:
https://pkg.go.dev/golang.org/x/time/rate
安装:
xxxxxxxxxxgo get golang.org/x/time/rate使用示例:
xxxxxxxxxxpackage main
import ( "golang.org/x/time/rate" "sync")
func main() { // 每秒产生 10 个令牌,令牌桶的最大容量为 10 limiter := rate.NewLimiter(10, 10)
var wg sync.WaitGroup // 开启 15 个协程 for i := 0; i < 15; i++ { wg.Add(1) go func() { if !limiter.Allow() { println("rejected") } else { println("passed") } wg.Done() }() }
wg.Wait()}运行上面的例子,将看到类似下面的输出:
xxxxxxxxxxpassedpassedpassedpassedpassedpassedpassedpassedpassedpassedrejectedrejectedrejectedrejectedrejected
除了 Allow/AllowN 之外,rate/Limter 还提供了 Wait/WaitN、Reserver/ReserverN 等方法。如果想了解更多关于 golang.org/x/time/rate 的信息,可以参考文末列出的文档。
项目地址:
https://github.com/alibaba/sentinel-golang
安装:
xxxxxxxxxxgo get -u github.com/alibaba/sentinel-golang@v1.0.3使用示例:
xxxxxxxxxxpackage main
import ( sentinel "github.com/alibaba/sentinel-golang/api" "github.com/alibaba/sentinel-golang/core/base" "github.com/alibaba/sentinel-golang/core/flow" "sync")
func main() { // 使用默认配置初始化 Sentinel err := sentinel.InitDefault() if err != nil { // 初始化 Sentinel 失败 panic(err) }
// 加载流量控制规则:对于资源 test-resource 而言,一秒内允许通过 10 个请求,多余的请求将被拒绝 if _, err := flow.LoadRules([]*flow.Rule{ { Resource: "test-resource", Threshold: 10, StatIntervalInMs: 1000, ControlBehavior: flow.Reject, TokenCalculateStrategy: flow.Direct, }, }); err != nil { panic(err) }
// 开启 15 个协程 var wg sync.WaitGroup for i := 0; i < 15; i++ { wg.Add(1) go func() { e, b := sentinel.Entry("test-resource", sentinel.WithTrafficType(base.Inbound)) if b != nil { println("rejected") } else { println("passed") // 务必保证业务逻辑结束后 Exit e.Exit() } wg.Done() }() } wg.Wait()}运行上面的例子,将看到类似下面的输出:
xxxxxxxxxxpassedpassedpassedpassedpassedpassedpassedpassedpassedpassedrejectedrejectedrejectedrejectedrejected
当触发流控时,Sentinel 不仅支持拒绝请求,还支持匀速排队。如果想了解更多关于 Sentinel Go 的信息,请参考文末列出的文档。
文件中有 20 个图片的 URL,要求分别使用每个 URL 调用图片识别服务的接口,但是图片识别服务将请求速率限制为最大 2 张/秒。
接下来使用 uber/ratelimit 实现(也可以使用其它限速器,比如 Sentinel Go 等):
xxxxxxxxxxpackage main
import ( "fmt" "go.uber.org/ratelimit" "math/rand" "sync" "time")
// 限速器var rl = ratelimit.New(2)
// producer 生产者func producer(ch chan<- string) { // 模拟从文件读出 URL,然后发送到 channel for i := 1; i <= 20; i++ { url := fmt.Sprintf("url_%d", i) ch <- url } // 处理完毕,关闭 channel close(ch) println("producer exited")}
// consumer 消费者func consumer(ch <-chan string, finished chan struct{}) { // 开启 10 个协程。不开 2 个的原因是:如果请求的平均 RT 超过 1 秒,并且只开 2 个协程,那么最大请求速率将达不到 2 张/秒。 // 所以多开一些协程,可以减少客户端的处理时间 var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { for { // 先从令牌桶获取令牌 t := rl.Take() // 再从 channel 获取要处理的 URL url, ok := <- ch // 如果 channel 已经被关闭,那么退出循环 if !ok { break } println(fmt.Sprintf("%s %s", t.String(), url)) // 模拟使用 URL 请求图片识别服务 time.Sleep(time.Duration(rand.Int() % 2000) * time.Millisecond) } wg.Done() }() } wg.Wait() finished <- struct{}{}}
func main() { // 创建一个带缓冲的 channel,用于生产者和消费者之间的通信 ch := make(chan string, 10) // 启动生产者 go producer(ch) // 启动消费者 finished := make(chan struct{}) go consumer(ch, finished) // 等待消费者结束 <-finished}