[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
  1. package main
  2.  
  3. import (
  4. "context"
  5. "fmt"
  6. "log"
  7. "time"
  8.  
  9. "golang.org/x/time/rate"
  10. )
  11.  
  12. var _ rate.Limit
  13.  
  14. func main() {
  15. const (
  16. n = 10
  17. tf = 5 * time.Second
  18. b = 1
  19. )
  20. lim := rate.NewLimiter(rate.Every(tf/n), b)
  21.  
  22. // _ = lim.ReserveN(time.Now(), b) // <<<--- 可試著不註解,看看會發生什麼事
  23.  
  24. fmt.Println("expected limit:", lim.Limit())
  25. fmt.Println("completed elapsed actual rate")
  26. ctx := context.Background()
  27. start := time.Now()
  28. for i := 0; i < 2*n+2; i++ {
  29. err := lim.Wait(ctx)
  30. if err != nil {
  31. log.Fatal(err)
  32. }
  33. elapsed := time.Since(start)
  34. completed := i + 1
  35. actual := float64(completed) / elapsed.Seconds()
  36. fmt.Printf("%8d %8v %v\n", completed, elapsed, actual)
  37. }
  38. }

參考

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

留言