Golang 限流器的使用和實現


Golang 限流器的使用和實現

發布於 6月28日

限流器是服務中非常重要的一個組件,在網關設計、微服務、以及普通的后台應用中都比較常見。它可以限制訪問服務的頻次和速率,防止服務過載,被刷爆。

限流器的算法比較多,常見的比如令牌桶算法、漏斗算法、信號量等。本文主要介紹基於漏斗算法的一個限流器的實現。文本也提供了其他幾種開源的實現方法。

基於令牌桶的限流器實現

在golang 的官方擴展包 time 中(github/go/time),提供了一個基於令牌桶算法的限流器的實現。

原理

令牌桶限流器,有兩個概念:

  • 令牌:每次都需要拿到令牌后,才可以訪問
  • 桶:有一定大小的桶,桶中最多可以放一定數量的令牌
  • 放入頻率:按照一定的頻率向通里面放入令牌,但是令牌數量不能超過桶的容量

因此,一個令牌桶的限流器,可以限制一個時間間隔內,最多可以承載桶容量的訪問頻次。下面我們看看官方的實現。

實現

限流器的定義

下面是對一個限流器的定義:

type Limiter struct { limit Limit // 放入桶的頻率 (Limit 為 float64類型) burst int // 桶的大小 mu sync.Mutex tokens float64 // 當前桶內剩余令牌個數 last time.Time // 最近取走token的時間 lastEvent time.Time // 最近限流事件的時間 }

其中,核心參數是 limit,burst。 burst 代表了桶的大小,從實際意義上來講,可以理解為服務可以承載的並發量大小;limit 代表了 放入桶的頻率,可以理解為正常情況下,1s內我們的服務可以處理的請求個數。

在令牌發放后,會被保留在Reservation 對象中,定義如下:

type Reservation struct { ok bool // 是否滿足條件分配到了tokens lim *Limiter // 發送令牌的限流器 tokens int // tokens 的數量 timeToAct time.Time // 滿足令牌發放的時間 limit Limit // 令牌發放速度 }

Reservation 對象,描述了一個在達到 timeToAct 時間后,可以獲取到的令牌的數量tokens。 (因為有些需求會做預留的功能,所以timeToAct 並不一定就是當前的時間。

限流器如何限流

官方提供的限流器有阻塞等待式的,也有直接判斷方式的,還有提供了自己維護預留式的,但核心的實現都是下面的reserveN 方法。

// 在 now 時間需要拿到n個令牌,最多可以等待的時間為maxFutureResrve // 結果將返回一個預留令牌的對象 func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { lim.mu.Lock() // 首先判斷是否放入頻次是否為無窮大,如果為無窮大,說明暫時不限流 if lim.limit == Inf { // ... } // 拿到截至now 時間時,可以獲取的令牌tokens數量,上一次拿走令牌的時間last now, last, tokens := lim.advance(now) // 然后更新 tokens 的數量,把需要拿走的去掉 tokens -= float64(n) // 如果tokens 為負數,說明需要等待,計算等待的時間 var waitDuration time.Duration if tokens < 0 { waitDuration = lim.limit.durationFromTokens(-tokens) } // 計算是否滿足分配條件 // ① 需要分配的大小不超過桶容量 // ② 等待時間不超過設定的等待時常 ok := n <= lim.burst && waitDuration <= maxFutureReserve // 最后構造一個Reservation對象 r := Reservation{ ok: ok, lim: lim, limit: lim.limit, } if ok { r.tokens = n r.timeToAct = now.Add(waitDuration) } // 並更新當前limiter 的值 if ok { lim.last = now lim.tokens = tokens lim.lastEvent = r.timeToAct } else { lim.last = last } lim.mu.Unlock() return r }

從實現上看,limiter 並不是每隔一段時間更新當前桶中令牌的數量,而是記錄了上次訪問時間和當前桶中令牌的數量。當再次訪問時,通過上次訪問時間計算出當前桶中的令牌的數量,決定是否可以發放令牌。

使用

下面我們通過一個簡單的例子,學習上面介紹的限流器的使用。

  limiter := rate.NewLimiter(rate.Every(100*time.Millisecond), 10) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if limiter.Allow() {// do something log.Println("say hello") } }) _ = http.ListenAndServe(":13100", nil)

上面,每100 ms 放入令牌桶中1個令牌,所以當批量訪問該接口時,可以看到如下結果:

2020/06/26 14:34:16 say hello  有18 條記錄
2020/06/26 14:34:17 say hello  有10 條記錄
2020/06/26 14:34:18 say hello  有10 條記錄
  ...

一開始漏斗滿着,可以緩解部分突發的流量。當漏斗未空時,訪問的頻次和令牌放入的頻次變為一致。

其他限流器的實現

  1. uber 開源庫中基於漏斗算法實現了一個限流器。漏斗算法可以限制流量的請求速度,並起到削峰填谷的作用。 https://github.com/uber-go/ratelimit
  2. 滴滴開源實現了一個對http請求的限流器中間件。可以基於以下模式限流。

    • 基於IP,路徑,方法,header,授權用戶等限流
    • 通過自定義方法限流
    • 還支持基於 http header 設置限流數據
    • 實現方式是基於 github/go/time 實現的,不同類別的數據都存儲在一個帶超時時間的數據池中。
    • 代碼地址 https://github.com/didip/tollbooth
  3. golang 網絡包中還有基於信號量實現的限流器。 https://github.com/golang/net/blob/master/netutil/listen.go 也值得我們去學習下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM