golang令牌桶算法實現庫 go-rate

關於我

個人博客|文章首發java

go-rate是速率限制器庫,基於 Token Bucket(令牌桶)算法實現。 go-rate被用在LangTrend的生產中 用於遵照GitHub API速率限制。git

速率限制能夠完成一些特殊的功能需求,包括但不限於服務器端垃圾郵件保護、防止api調用飽和等。程序員

庫使用說明

構造限流器

咱們首先構造一個限流器對象:github

limiter := NewLimiter(10, 1);

這裏有兩個參數:golang

  1. 第一個參數是 r Limit。表明每秒能夠向 Token 桶中產生多少 token。Limit 其實是 float64 的別名。
  2. 第二個參數是 b int。b 表明 Token 桶的容量大小。

上述的限流器的含義是:擁有一個容量爲1的令牌桶,以每鈔10個的速度向桶中放令牌。算法

除了直接指定每秒產生的 Token 個數外,還能夠用 Every 方法來指定向 Token 桶中放置 Token 的間隔,例如:api

limiter := NewLimiter(Every(100 * time.Millisecond), 1);

以上就表示每 100ms 往桶中放一個 Token。本質上也就是一秒鐘產生 10 個。數組

消費令牌Token

Limiter 提供了三類方法供用戶消費 Token,用戶能夠每次消費一個 Token,也能夠一次性消費多個 Token。 而每種方法表明了當 Token 不足時,各自不一樣的對應手段。服務器

Wait/WaitN

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 的最長時間。

Allow/AllowN

Allow 實際上就是 AllowN(time.Now(),1)

AllowN 方法表示,截止到某一時刻,目前桶中數目是否至少爲 n 個,知足則返回 true,同時從桶中消費 n 個 token。 反之返回不消費 Token,false。

一般對應這樣的線上場景,若是請求速率過快,就直接丟到某些請求。

Reserve/ReserveN

Reserve 至關於 ReserveN(time.Now(), 1)

ReserveN 的用法就相對來講複雜一些,當調用完成後,不管 Token 是否充足,都會返回一個 Reservation * 對象。

你能夠調用該對象的 Delay() 方法,該方法返回了須要等待的時間。若是等待時間爲 0,則說明不用等待。必須等到等待時間以後,才能進行接下來的工做。

或者,若是不想等待,能夠調用 Cancel() 方法,該方法會將 Token 歸還。

使用一個僞代碼來舉例,咱們能夠如何使用 Reserve 方法。

r := lim.Reserve()
//是否願意等待
f !r.OK() {
    //不肯意等待直接退出
    return
}

//若是願意等待,將等待時間拋給用戶 time.Sleep表明用戶須要等待的時間。
time.Sleep(r.Delay())
Act() // 一段時間後生成生成新的令牌,開始執行相關邏輯

動態調整速率

Limiter 支持能夠調整速率和桶大小:

  1. SetLimit(Limit) 改變放入 Token 的速率
  2. SetBurst(int) 改變 Token 桶大小

有了這兩個方法,能夠根據現有環境和條件以及咱們的需求,動態地改變 Token 桶大小和速率。

案例1-單位時間只容許一次郵件發送操做

客戶端軟件客戶點擊發送郵件,若是客戶一秒鐘內點擊10次,就會發送10次,這明顯是不合適的。若是使用速率限制,咱們就能夠限制一秒內只能發送一次,實現方法爲:

(令牌桶)容量爲1,速度爲每一秒生成一個令牌,這樣能夠保證一秒鐘只會被執行一次,僞代碼實現以下

//初始化 limiter 每秒生成1個令牌,令牌桶容量爲20
limiter := rate.NewLimiter(rate.Every(time.Second), 1)
//模擬單位時間執行屢次操做
for i := 0; i < 5; i++ {
	if limiter.Allow() {
		fmt.Println("發送郵件")
	} else {
		fmt.Println("請求屢次,過濾")
	}
}
if limiter.Allow() {
		fmt.Println("發送郵件")
}

執行結果

發送郵件 請求屢次,過濾 請求屢次,過濾 請求屢次,過濾 請求屢次,過濾 發送郵件

咱們發現,第一次執行是能夠被容許的由於第一次的令牌被容許,以後的請求失敗是由於尚未生成新的令牌,因此須要等待1秒,以後又能夠進行發送郵件操做。

經過這樣一個案例,相信你們對令牌桶的實現場景有了一個基本的瞭解。

案例2——令牌取出單個和多個

初始化令牌桶容量爲20,設置每100毫秒生成一個令牌,即1秒生產10個令牌。編碼測試功能

//初始化 limiter 每秒10個令牌,令牌桶容量爲20
limiter := rate.NewLimiter(rate.Every(time.Millisecond*100), 20)
for i := 0; i < 25; i++ {
	if limiter.Allow() {
		fmt.Println("success") //do something
	} else {
		fmt.Println("busy")
	}
}

//阻塞直到獲取足夠的令牌或者上下文取消
ctx, _ := context.WithTimeout(context.Background(), time.Second*2)
fmt.Println("start get token", time.Now())
err := limiter.WaitN(ctx, 20)
if err != nil {
	fmt.Println("error", err)
	return
}
fmt.Println("success get token", time.Now())

第二段編碼阻塞的場景在於,一次性取出20個令牌給予2秒的等待時間,若是有20個令牌能夠取出打印成功消息,若是2秒等待時間內沒有20個令牌能夠取出,程序直接退出,即失敗。

參考

go-rate

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

Golang限流器rate使用

END

歡迎關注公衆號 程序員工具集 👍👍 致力於分享優秀的開源項目、學習資源 、經常使用工具

回覆關鍵詞「關注禮包」,送你一份最全的程序員技能圖譜。

相關文章
相關標籤/搜索