關於我
個人博客|文章首發java
go-rate是速率限制器庫,基於 Token Bucket(令牌桶)算法實現。 go-rate被用在LangTrend的生產中 用於遵照GitHub API速率限制。git
速率限制能夠完成一些特殊的功能需求,包括但不限於服務器端垃圾郵件保護、防止api調用飽和等。程序員
庫使用說明
構造限流器
咱們首先構造一個限流器對象:github
limiter := NewLimiter(10, 1);
這裏有兩個參數:golang
- 第一個參數是
r Limit
。表明每秒能夠向 Token 桶中產生多少 token。Limit 其實是 float64 的別名。 - 第二個參數是
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 支持能夠調整速率和桶大小:
- SetLimit(Limit) 改變放入 Token 的速率
- 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個令牌能夠取出,程序直接退出,即失敗。
參考
END
歡迎關注公衆號 程序員工具集 👍👍 致力於分享優秀的開源項目、學習資源 、經常使用工具
回覆關鍵詞「關注禮包」,送你一份最全的程序員技能圖譜。