[Go] rate 限流器

程式語言:Go
Package:
rate
官方文件

功能:限制單位時間內的工作次數

演算法描述
該限流器是基於 Token Bucket(令牌桶) 實現的
簡單來說,令牌桶就是想像有一個固定大小 b 的桶,系統會以恆定速率 r 向桶中放 Token,桶滿則不放
而用戶則從桶中取 Token,如果有剩餘 Token 就可以一直取。
如果沒有剩餘 Token,則需要等到桶中被放置了 Token 才行
建立 Limiter
  • func NewLimiter(r Limit, b int) *Limiter
    • 建立 Limiter
    • 一開始 Token 桶中全滿,速率可能會超過 limit r,之後逐漸下降至 r
    • 參數
      • r: 每秒向 Token 桶中產生多少 Token
      • b: Token 桶的容量大小,決定瞬間最大可取量
    • 用法
      每秒產生 0.5 個 Token,放進容量為 1 的 Token 桶
      limiter := NewLimiter(0.5, 1)
  • func Every(interval time.Duration) Limit
    • 每隔幾秒投入 Token,可用來產生 Limiter 的參數 r
    • 參數
      • interval: 產生的間隔時間
    • 用法
      每天產生 50 個 Token,放進容量為 1 的 Token 桶
      rt := rate.Every(24*time.Hour / 50)
      limiter := rate.NewLimiter(rt, 1)
Limiter 設定值
  • func (lim *Limiter) Burst() int
    • 得到目前 Token 桶的容量,相當於瞬間最大可取量
  • func (lim *Limiter) Limit() Limit
    • 得到目前每秒產生的 Token 數目
  • func (lim *Limiter) SetBurst(newBurst int)
    • 等同 SetBurstAt(time.Now(), newBurst)
  • func (lim *Limiter) SetBurstAt(now time.Time, newBurst int)
    • 指定時間設定目前 Token 桶的容量,相當於瞬間最大可取量
    • 調用之前尚未執行的操作可能會違反或未充分利用新的 Burst
  • func (lim *Limiter) SetLimit(newLimit Limit)
    • 等同 SetLimitAt(time.Now(), newLimit)
  • func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit)
    • 指定時間設定目前每秒產生的 Token 數目
    • 調用之前尚未執行的操作可能會違反或未充分利用新的 Limit
操作(皆會消耗對應數目的 Token)
  • 常用操作為 Wait
    若需要保留等之後再處理,可用 Reserve
    若需要等待並由 context 控制去留,可用 Wait
    若需要丟棄或跳過超出速率限制的事件,可用 Allow
  • func (lim *Limiter) Allow() bool
    • 等同 AllowN(time.Now(), 1)
  • func (lim *Limiter) AllowN(now time.Time, n int) bool
    • 是否可以同時提供 n 個 Token
  • func (lim *Limiter) Wait(ctx context.Context) (err error)
    • 等同 WaitN(ctx, 1)
  • func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
    • 一直等待直到可提供 n 個 Token,會造成 block
    • error 的狀況
      • n 大於 Token 桶的容量大小,也就是大於瞬間最大可取量
        • 例外:當 r 為 rate.Inf,則忽略突發限制
      • context.Canceled
      • context.DeadlineExceeded
  • func (lim *Limiter) Reserve() *Reservation 
    • 等同 ReserveN(time.Now(), 1)
  • func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
    • 保留 n 個 Token
      如果 n 大於 Token 桶的容量大小,也就是大於瞬間最大可取量
      r.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()
Reservation 對應 function
  • func (r *Reservation) Cancel()
    • 等同 CancelAt(time.Now())
  • func (r *Reservation) CancelAt(now time.Time)
    • 在特定時間取消保留,會將保留的 Token 歸還
  • func (r *Reservation) Delay() time.Duration
    • 等同 DelayFrom(time.Now())
  • func (r *Reservation) DelayFrom(now time.Time) time.Duration
    • 在特定時間後,需等待多少時間才能得到指定數量的 Token
    • 若回傳 rate.InfDuration 表示無法在最大等待時間內得到指定數量的 Token
  • func (r *Reservation) OK() bool
    • 確定保留是否 OK
      如果為 false,則 Delay 返回 InfDuration,而 Cancel 不執行任何操作
範例程式碼
The Go Playground
package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

var _ rate.Limit

func main() {
	const (
		n  = 10
		tf = 5 * time.Second
		b  = 1
	)
	lim := rate.NewLimiter(rate.Every(tf/n), b)

	// _ = lim.ReserveN(time.Now(), b) //             <<<--- 可試著不註解,看看會發生什麼事

	fmt.Println("expected limit:", lim.Limit())
	fmt.Println("completed  elapsed  actual rate")
	ctx := context.Background()
	start := time.Now()
	for i := 0; i < 2*n+2; i++ {
		err := lim.Wait(ctx)
		if err != nil {
			log.Fatal(err)
		}
		elapsed := time.Since(start)
		completed := i + 1
		actual := float64(completed) / elapsed.Seconds()
		fmt.Printf("%8d %8v  %v\n", completed, elapsed, actual)
	}
}

參考

Golang 標準庫限流器 time/rate 使用介紹

留言