本主題為系列文章,分上下兩篇。本文主要介紹time/rate的具體使用方法,下一篇文章將會著重介紹其內部實現原理。
限流器是後台服務中的非常重要的組件,可以用來限制請求速率,保護服務,以免服務過載。限流器的實現方法有很多種,例如滑動窗口法、Token Bucket、Leaky Bucket等。
其實golang標準庫中就自帶了限流算法的實現,即golang.org/x/time/rate。該限流器是基於Token Bucket(令牌桶)實現的。
簡單來說,令牌桶就是想像有一個固定大小的桶,系統會以恆定速率向桶中放Token,桶滿則暫時不放。而用戶則從桶中取Token,如果有剩餘Token就可以一直取。如果沒有剩餘Token,則需要等到系統中被放置了Token才行。
本文則主要集中介紹下該組件的具體使用方法:
我們可以使用以下方法構造一個限流器對象:
limiter := NewLimiter(10, 1);
這裡有兩個參數:
那麼,對於以上例子來說,其構造出的限流器含義為,其令牌桶大小為1, 以每秒10個Token的速率向桶中放置Token。
除了直接指定每秒產生的Token個數外,還可以用Every方法來指定向Token桶中放置Token的間隔,例如:
limit := Every(100 * time.Millisecond);
limiter := NewLimiter(limit, 1);
以上就表示每100ms往桶中放一個Token。本質上也就是一秒鐘產生10個。
Limiter提供了三類方法供用戶消費Token,用戶可以每次消費一個Token,也可以一次性消費多個Token。而每種方法代表了當Token不足時,各自不同的對應手段。
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的最長時間。
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。
通常對應這樣的線上場景,如果請求速率過快,就直接丟到某些請求。
func (lim *Limiter) Reserve() *Reservation
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
Reserve相當於ReserveN(time.Now(), 1)。
ReserveN的用法就相對來說複雜一些,當調用完成後,無論Token是否充足,都會返回一個Reservation*對象。
你可以調用該對象的Delay()方法,該方法返回了需要等待的時間。如果等待時間為0,則說明不用等待。必須等到等待時間之後,才能進行接下來的工作。
或者,如果不想等待,可以調用Cancel()方法,該方法會將Token歸還。
舉一個簡單的例子,我們可以這麼使用Reserve方法。
r := lim.Reserve()
f !r.OK() {
// Not allowed to act! Did you remember to set lim.burst to be > 0 ?
return
}
time.Sleep(r.Delay())
Act() // 執行相關邏輯
Limiter支持可以調整速率和桶大小:
有了這兩個方法,可以根據現有環境和條件,根據我們的需求,動態的改變Token桶大小和速率。