本文主要介绍常见的限速算法,以及这些算法在 Golang 中有哪些开源实现,并提供一些简单的示例。
常见的限速算法有:
项目地址:
https://github.com/uber-go/ratelimit
安装:
xxxxxxxxxx
go 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
}
}
运行上面的示例,将看到类似下面的输出:
xxxxxxxxxx
0 225ns
1 10ms
2 10ms
3 10ms
4 10ms
5 10ms
6 10ms
7 10ms
8 10ms
9 10ms
在上面的例子中,限流器每秒可以通过 100 个请求,每个请求间隔 10 ms。如果想了解更多关于 uber/ratelimit 的信息,可以参考文末列出的文档。
文档地址:
https://pkg.go.dev/golang.org/x/time/rate
安装:
xxxxxxxxxx
go get golang.org/x/time/rate
使用示例:
xxxxxxxxxx
package 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()
}
运行上面的例子,将看到类似下面的输出:
xxxxxxxxxx
passed
passed
passed
passed
passed
passed
passed
passed
passed
passed
rejected
rejected
rejected
rejected
rejected
除了 Allow/AllowN 之外,rate/Limter 还提供了 Wait/WaitN、Reserver/ReserverN 等方法。如果想了解更多关于 golang.org/x/time/rate 的信息,可以参考文末列出的文档。
项目地址:
https://github.com/alibaba/sentinel-golang
安装:
xxxxxxxxxx
go get -u github.com/alibaba/sentinel-golang@v1.0.3
使用示例:
xxxxxxxxxx
package 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()
}
运行上面的例子,将看到类似下面的输出:
xxxxxxxxxx
passed
passed
passed
passed
passed
passed
passed
passed
passed
passed
rejected
rejected
rejected
rejected
rejected
当触发流控时,Sentinel 不仅支持拒绝请求,还支持匀速排队。如果想了解更多关于 Sentinel Go 的信息,请参考文末列出的文档。
文件中有 20 个图片的 URL,要求分别使用每个 URL 调用图片识别服务的接口,但是图片识别服务将请求速率限制为最大 2 张/秒。
接下来使用 uber/ratelimit 实现(也可以使用其它限速器,比如 Sentinel Go 等):
xxxxxxxxxx
package 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
}