速率限制;它限制了某种资源在某段时间内 被访问的次数。 资源可以是任何东西: API连接, 磁盘读写, 网络包, 异常。 通常情况下 用户对系统的访问应当 被沙盒化,既不会影响其他用户的活动, 也不会受到其他用户的影响。 访问 收费系统 时, 速率限制可以使你与客户保持良好的关系。Google 的云服务中 就用到了 速率限制。 Go语言 中 如何 进行限速呢? 大多数的限速 是基于 令牌桶算法的。 要想访问资源,必须拥有资源的访问令牌,没有令牌的请求会被拒绝。 桶的深度为 d ;代表可以存放 d个令牌。
先看一个没有使用限速 的例子:
type APIConnection struct{}
func Open() *APIConnection{
return &APIConnection{}
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
//执行一些操作
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
//执行一些操作
return nil
}
func main() {
defer log.Printf("Done.")
log.SetOutput(os.Stdout)
log.SetFlags(log.Ltime | log.LUTC)
apiConnection := Open()
var wg sync.WaitGroup
wg.Add(20)
for i:=0 ; i<10; i++{
go func() {
defer wg.Done()
err := apiConnection.ReadFile(context.Background())
if err != nil{
log.Printf("cannot ReadFile: %v", err)
}
log.Printf("ReadFile")
}()
}
for i:=0; i<10; i++{
go func() {
defer wg.Done()
err := apiConnection.ResolveAddress(context.Background())
if err != nil{
log.Printf("cannot ResolveAddress: %v", err)
}
log.Printf("ResolveAddress")
}()
}
wg.Wait()
}
打算把 限速 放在 APIConnection 中,但通常限速器会在服务器上运行,这可以防止用户轻易的绕过它。 使用 golang.org/x/time/rate 中 令牌桶限速器实现。 创建一个Limiter func NewLimiter(r Limit, b int) *Limiter 使用Limiter 的 Wait 来 阻塞我们的请求,直到获得访问令牌。
func Open() *APIConnection{
return &APIConnection{
rateLimiter: rate.NewLimiter(rate.Limit(1), 1),
}
}
type APIConnection struct{
rateLimiter *rate.Limiter
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//在这里执行一些逻辑
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//在这里执行一些逻辑
return nil
}
可能 会想要建立多层次的限制:用细粒度的控制 来限制每秒的请求,用粗粒度的控制来限制每分钟,每小时 或每天的请求。 在某些情况下,可以用单一的 限速器 来做到这一点。然而并不能适应所有情况, 所以呢,把不同粒度的限速器独立,然后将它们组合成一个限速器组 来管理 会更容易达到目的。
type RateLimiter interface {
Wait(ctx context.Context) error
Limit() rate.Limit
}
type multiLimiter struct{
limiters []RateLimiter
}
func MultiLimiter(limiters ...RateLimiter) *multiLimiter{
byLimit := func(i, j int) bool {
return limiters[i].Limit() < limiters[j].Limit()
}
sort.Slice(limiters, byLimit)
return &multiLimiter{limiters:limiters}
}
func (l *multiLimiter) Wait(ctx context.Context) error{
for _, l := range l.limiters {
if err := l.Wait(ctx); err != nil{
return err
}
}
return nil
}
//速度上 以 最慢的 那个 返回了,针对下面的 例子 就是 每分钟十个 返回了
func (l *multiLimiter) Limit() rate.Limit{
return l.limiters[0].Limit()
}
func Per(eventCount int, duration time.Duration) rate.Limit{
return rate.Every(duration/time.Duration(eventCount))
}
type APIConnection struct{
rateLimiter RateLimiter
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//执行一些逻辑
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
if err := a.rateLimiter.Wait(ctx); err != nil{
return err
}
//执行一些逻辑
return nil
}
func Open() *APIConnection {
//这里定义 每秒 的限制, 避免突发请求
secondLimit := rate.NewLimiter(Per(2, time.Second),1)
//这里 定义每分钟的 限制, 为用户提供初始池。 每秒的限制 将确保我们的系统 不会因为突发的请求 而超载
minuteLimit := rate.NewLimiter(Per(10, time.Minute), 10)
return &APIConnection{
rateLimiter: MultiLimiter(secondLimit, minuteLimit),
}
}
//10:44:19 ReadFile
//10:44:19 ReadFile
//10:44:20 ReadFile
//10:44:20 ResolveAddress
//10:44:21 ReadFile
//10:44:21 ResolveAddress
//10:44:22 ResolveAddress
//10:44:22 ResolveAddress
//10:44:23 ResolveAddress
//10:44:23 ResolveAddress
//10:44:25 ResolveAddress
//10:44:31 ResolveAddress
//10:44:37 ResolveAddress
//10:44:43 ReadFile
//10:44:49 ReadFile
//10:44:55 ReadFile
//10:45:01 ReadFile
//10:45:07 ReadFile
//10:45:13 ReadFile
//10:45:19 ResolveAddress
//10:45:19 Done.
分析: 客户端 每秒 发出两个请求。 直到第十一个请求,我们开始每6秒种 发出一次请求。 这是因为 耗尽了 每分钟限速器的可用令牌,所以限制了请求速度。 除了考虑时间维度, 还有可能 使用其他维度来限制。 如:除了对API请求的数量有一些限制, 同时也可能对其他资源(如磁盘访问,网络访问等)有限制。
type APIConnection struct{
networkLimit,
diskLimit,
apiLimit RateLimiter
}
func Open() *APIConnection{
return &APIConnection{
apiLimit: MultiLimiter(
rate.NewLimiter(Per(2, time.Second), 2),
rate.NewLimiter(Per(10, time.Minute), 10),
),
diskLimit:MultiLimiter(
rate.NewLimiter(rate.Limit(1), 1),
),
networkLimit:MultiLimiter(
rate.NewLimiter(Per(3, time.Second), 3),
),
}
}
func (a *APIConnection) ReadFile(ctx context.Context) error{
err := MultiLimiter(a.apiLimit, a.diskLimit).Wait(ctx)
if err != nil {
return err
}
//这里执行一些逻辑
return nil
}
func (a *APIConnection) ResolveAddress(ctx context.Context) error{
err := MultiLimiter(a.apiLimit, a.networkLimit).Wait(ctx)
if err != nil{
return err
}
//这里执行一些逻辑
return nil
}
//12:18:33 ReadFile
//12:18:33 ResolveAddress
//12:18:33 ResolveAddress
//12:18:34 ReadFile
//12:18:35 ReadFile
//12:18:35 ResolveAddress
//12:18:35 ResolveAddress
//12:18:36 ResolveAddress
//12:18:36 ReadFile
//12:18:37 ReadFile
//12:18:39 ReadFile
//12:18:45 ReadFile
//12:18:51 ResolveAddress
//12:18:57 ReadFile
//12:19:03 ResolveAddress
//12:19:09 ResolveAddress
//12:19:15 ResolveAddress
//12:19:21 ReadFile
//12:19:27 ResolveAddress
//12:19:33 ReadFile
//12:19:33 Done.
/**
rate.Limiter 还有一些特殊 技巧,遇到时在进一步研究
rate.Limiter 还有一些特殊 技巧,遇到时在进一步研究