Loading... # golang标准库中的令牌桶限流器time/rate ## 一、令牌桶算法 令牌桶算法是指一个固定大小的桶,可以存放的令牌的最大个数也是固定的。此算法以一种**固定速率**不断的往桶中存放令牌,而每次请求调用前必须先从桶中获取令牌才可以。否则进行拒绝或等待,直到获取到有效令牌为止。如果桶内的令牌数量已达到桶的最大允许上限的话,则丢弃令牌。 库名:`golang.org/x/time/rate ` ## 二、限流器rate的使用 ### 1、结构体 ``` type Limiter struct { mu sync.Mutex // 互斥锁(排他锁) limit Limit // 放入桶的频率 float64 类型 burst int // 桶的大小 tokens float64 // 令牌 token 当前剩余的数量 last time.Time // 最近取走 token 的时间 lastEvent time.Time // 最近限流事件的时间 } ``` limit,burst,tokens是限流器的核心参数,来实现并发请求的大小。 ### 2、创建限流器 ``` func NewLimiter(r Limit, b int) *Limiter { return &Limiter{ limit: r, burst: b, } } ``` 参数说明 `r`:令牌桶每秒可以产生`r`个token `b`:令牌桶的大小,若桶的大小为0,则拒绝所有的操作 ### 3、limiter的主要函数 三个主要函数为:Allow, Reserve, Wait ``` 每个方法可以消费一个令牌,当没有可用令牌时,处理方法为: ``` ``` ① Allow 返回 false ``` ``` ② Wait 会阻塞走到有令牌可用或者超时取消(context.Context) ``` ``` ③ Reserve 返回一个 reservation,以便token的预订时,调用之前必须等待一段时间 ``` #### (1)Allow/AllowN ##### AllowN方法表示: ``` 截止在某一时刻,目前桶中数目是否至少为n个。如果条件满足,则从桶中消费n个token,同时返回true。反之不消费Token,返回false ``` ##### 使用场景: ``` 一般用在如果请求速率过快,直接拒绝请求的情况 ``` ``` package main import ( "fmt" "time" "golang.org/x/time/rate" ) func main() { // 初始化状态桶是满的 // 每秒产生10个令牌,桶的大小为100个 var limiter = rate.NewLimiter(10, 100) for i := 0; i < 20; i++ { if limiter.AllowN(time.Now(), 25) { fmt.Printf("%03d Ok %sn\n", i, time.Now().Format("2006-01-02 15:04:05.000")) } else { fmt.Printf("%03d Err %sn\n", i, time.Now().Format("2006-01-02 15:04:05.000")) } time.Sleep(500 * time.Millisecond) } } ``` ##### 结果:  #### (2)Wait/WaitN ##### WaitN方法表示: ``` 如果此时桶内Token数量不足(小于N),那么WaitN方法将会阻塞一段时间,直至Token满足条件。否则直接返回 ``` ##### 使用场景: ``` 可以通过设置context的Deadline或者Timeout,来决定此次的Wait的最长时间 ``` ``` func main() { // 指定令牌桶大小为5,每秒补充3个令牌 limiter := rate.NewLimiter(3, 5) // 指定超时时间为5秒 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() for i := 0; ; i++ { fmt.Printf("%03d %sn\n", i, time.Now().Format("2006-01-02 15:04:05.000")) // 每次消费2个令牌 err := limiter.WaitN(ctx, 2) if err != nil { fmt.Printf("timeout: %sn\n", err.Error()) return } } } ``` ##### 结果:  #### (3)Reserve/ReserveN ##### ReserveN方法表示: ``` 它返回的是一个*Reservation类型,后续操作主要针对的全是这个类型,通过判断限制器是否能够在指定时间提供指定N个请求令牌。 ``` ##### 使用场景: ``` ① 如果Reservation.OK()为true,则表示需要等待一段时间才可以提供,其中Reservation.Delay()返回需要的延时时间。 ``` ``` ② 如果Reservation.OK()为false,则Delay返回InfDuration, 此时不想等待的话,可以调用 Cancel()取消此次操作并归还使用的token ``` ``` func main() { // 指定令牌桶大小为5,每秒补充3个令牌 limiter := rate.NewLimiter(3, 5) // 指定超时时间为5秒 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() for i := 0; ; i++ { fmt.Printf("%03d %sn", i, time.Now().Format("2006-01-02 15:04:05.000")) reserve := limiter.Reserve() if !reserve.OK() { //返回是异常的,不能正常使用 fmt.Println("Not allowed to act! Did you remember to set lim.burst to be > 0 ?") return } delayD := reserve.Delay() fmt.Println("sleep delay ", delayD) time.Sleep(delayD) select { case <-ctx.Done(): fmt.Println("timeout, quit") return default: } //TODO 业务逻辑 } } ``` ##### 结果:  最后修改:2023 年 02 月 20 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏