Golang 服务流量限流器

1、简介

限流器是后台服务中的非常重要的组件,可以用来限制请求速率,保护服务,以免服务过载。 限流器的实现方法有很多种,例如滑动窗口法、Token Bucket、Leaky Bucket等。

golang标准库中就自带了限流算法的实现,即golang.org/x/time/rate。 该限流器是基于Token Bucket(令牌桶)实现的。

简单来说、令牌桶就是想象一个固定大小的桶、系统会以为恒定速率向桶中存放Token,桶满则暂时不放。

而用户则从桶中获取Token,存在Token就会一直获取,如果没有Token,则需要等待系统中被放置Token才行

2、使用

2.1、NewLimiter 创建限流器对象
limiter := NewLimiter(10,1)

两个参数:

  • 第一参数r Limit:代表每秒可以向Token桶中存放多少Token,Limit类型实际上是float64
  • 第二参数b int:b代表Token桶容量大小。

其构造出的限流器含义是令牌桶大小为1,以每秒10个Token的速率想桶中存放Token

2.2、Every 设置存放间隔时间
limit := Every(100 * time.Millisecond)
limiter := NewLimiter(limit,1)

上述表示以每100ms向桶中存放一个Token,本质上就是1s存放10个Token

2.3、Wait/WaitN
func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context,n int) (err error)

Wait实际上就是WaitN(ctx,1)

当Wait方法消费Token时,此时桶内Token不足(小于N),Wait就会阻塞一段时间,直到Token满足条件。如果充足则直接返回。

Wait方法有context参数,可通过context的Deadline或Timeout,设置Wait等待时长。

2.4、Allow/AllowN
func (lim *Limiter) Allow() bool
func (lim *Limiter) AllowN(now time.Time,n int) bool

Allow实际上就是AllowN(time.Now(),1)

AllowN表示截止到某一时刻,目前桶中数据是否至少为n个,满足返回true,同时消费n个Token。反之不消费Token,false

对应线上场景,请求速率过快,直接丢到某些请求

2.5、Reserve/ReserveN
func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation

Reserve相当于ReserveN(time.Now(), 1)。

ReserveN的用法相对复杂,当调用完后,无论Token是否充足,都会返回一个Reveration*对象

可以调用该对象的Delay()方法,该方法返回需要等待的时间。如果等待时间为0,无需等待。反之必须等到等待时间后,才能进行接下来的操作。

如果不想等待可以调用Cancel()方法,该方法会将Token归还。

ReserveN 返回一个 Reservation,指示调用者在 n 个事件发生之前必须等待多长时间。 限制器在允许未来事件时会考虑此保留。 如果 n 超过限制器的突发大小,则返回的 Reservation 的 OK() 方法返回 false。

用法示例:

r := lim.ReserveN(time.Now(), 1)
if !r.OK() {
    // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
    return
}
time.Sleep(r.Delay())
Act()
2.6、动态速率

Limiter支持可以调整速率和桶大小:

SetLimit(Limit) //改变放入Token的速率
SetBurst(int)//改变Token桶大小

根据当前需求、环境、条件,动态改变Token桶大小和速率

3、实例代码

package main

import (
	"context"
	"log"
	"time"

	"golang.org/x/time/rate"
)

func main() {
	//TODO limit每秒产生Token数量,burst最多存放Token数量
	limit := rate.Every(time.Second * 1)
	limiter := rate.NewLimiter(limit, 5)
	log.Println(limiter.Limit(), limiter.Burst())
	for i := 0; i < 10; i++ {
		//TODO 阻塞等待,直到获取一个Token
		log.Println("before wait")
		c, _ := context.WithTimeout(context.Background(), time.Second*2)
		if err := limiter.Wait(c); err != nil {
			log.Println("limiter wait err :" + err.Error())
		}
		log.Println("after wait")
		//TODO 返回需要等待多久获取新的Token,等待指定时间执行任务
		r := limiter.Reserve()
		log.Println("reserve Delay:", r.Delay())
		//TODO 判断当前是否可以取到Token
		a := limiter.Allow()
		log.Println("Allow:", a)

	}
}
posted @ 2021-07-31 11:40  自己有自己的调调、  阅读(662)  评论(0编辑  收藏  举报