go-rate是速率限制器庫,基于 Token Bucket(令牌桶)算法實(shí)現(xiàn)箕般。 go-rate被用在LangTrend的生產(chǎn)中 用于遵守GitHub API速率限制孔祸。
速率限制可以完成一些特殊的功能需求榛做,包括但不限于服務(wù)器端垃圾郵件保護(hù)婿脸、防止api調(diào)用飽和等。
庫使用說明
構(gòu)造限流器
我們首先構(gòu)造一個(gè)限流器對(duì)象:
limiter := NewLimiter(10, 1);
這里有兩個(gè)參數(shù):
- 第一個(gè)參數(shù)是
r Limit
利赋。代表每秒可以向 Token 桶中產(chǎn)生多少 token郁妈。Limit 實(shí)際上是 float64 的別名。 - 第二個(gè)參數(shù)是
b int
趾断。b 代表 Token 桶的容量大小拒名。
上述的限流器的含義是:擁有一個(gè)容量為1的令牌桶,以每鈔10個(gè)的速度向桶中放令牌芋酌。
除了直接指定每秒產(chǎn)生的 Token 個(gè)數(shù)外增显,還可以用 Every 方法來指定向 Token 桶中放置 Token 的間隔,例如:
limiter := NewLimiter(Every(100 * time.Millisecond), 1);
以上就表示每 100ms 往桶中放一個(gè) Token脐帝。本質(zhì)上也就是一秒鐘產(chǎn)生 10 個(gè)同云。
消費(fèi)令牌Token
Limiter 提供了三類方法供用戶消費(fèi) Token,用戶可以每次消費(fèi)一個(gè) Token堵腹,也可以一次性消費(fèi)多個(gè) Token炸站。
而每種方法代表了當(dāng) Token 不足時(shí),各自不同的對(duì)應(yīng)手段疚顷。
Wait/WaitN
func (lim *Limiter) Wait(ctx context.Context) (err error)
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error)
Wait 實(shí)際上就是 WaitN(ctx,1)
旱易。
當(dāng)使用 Wait 方法消費(fèi) Token 時(shí)禁偎,如果此時(shí)桶內(nèi) Token 數(shù)組不足 (小于 N),那么 Wait 方法將會(huì)阻塞一段時(shí)間咒唆,直至 Token 滿足條件届垫。如果充足則直接返回。
這里可以看到全释,Wait 方法有一個(gè) context 參數(shù)装处。我們可以設(shè)置 context 的 Deadline 或者 Timeout,來決定此次 Wait 的最長時(shí)間浸船。
Allow/AllowN
Allow 實(shí)際上就是 AllowN(time.Now(),1)
妄迁。
AllowN 方法表示,截止到某一時(shí)刻李命,目前桶中數(shù)目是否至少為 n 個(gè)登淘,滿足則返回 true,同時(shí)從桶中消費(fèi) n 個(gè) token封字。
反之返回不消費(fèi) Token黔州,false。
通常對(duì)應(yīng)這樣的線上場(chǎng)景阔籽,如果請(qǐng)求速率過快流妻,就直接丟到某些請(qǐng)求。
Reserve/ReserveN
Reserve 相當(dāng)于 ReserveN(time.Now(), 1)
笆制。
ReserveN 的用法就相對(duì)來說復(fù)雜一些绅这,當(dāng)調(diào)用完成后,無論 Token 是否充足在辆,都會(huì)返回一個(gè) Reservation * 對(duì)象证薇。
你可以調(diào)用該對(duì)象的 Delay() 方法,該方法返回了需要等待的時(shí)間匆篓。如果等待時(shí)間為 0浑度,則說明不用等待。必須等到等待時(shí)間之后奕删,才能進(jìn)行接下來的工作俺泣。
或者,如果不想等待完残,可以調(diào)用 Cancel() 方法伏钠,該方法會(huì)將 Token 歸還。
使用一個(gè)偽代碼來舉例谨设,我們可以如何使用 Reserve 方法熟掂。
r := lim.Reserve()
//是否愿意等待
f !r.OK() {
//不愿意等待直接退出
return
}
//如果愿意等待,將等待時(shí)間拋給用戶 time.Sleep代表用戶需要等待的時(shí)間扎拣。
time.Sleep(r.Delay())
Act() // 一段時(shí)間后生成生成新的令牌赴肚,開始執(zhí)行相關(guān)邏輯
動(dòng)態(tài)調(diào)整速率
Limiter 支持可以調(diào)整速率和桶大兴囟濉:
- SetLimit(Limit) 改變放入 Token 的速率
- SetBurst(int) 改變 Token 桶大小
有了這兩個(gè)方法,可以根據(jù)現(xiàn)有環(huán)境和條件以及我們的需求誉券,動(dòng)態(tài)地改變 Token 桶大小和速率指厌。
案例1-單位時(shí)間只允許一次郵件發(fā)送操作
客戶端軟件客戶點(diǎn)擊發(fā)送郵件,如果客戶一秒鐘內(nèi)點(diǎn)擊10次踊跟,就會(huì)發(fā)送10次踩验,這明顯是不合適的。如果使用速率限制商玫,我們就可以限制一秒內(nèi)只能發(fā)送一次箕憾,實(shí)現(xiàn)方法為:
(令牌桶)容量為1,速度為每一秒生成一個(gè)令牌拳昌,這樣可以保證一秒鐘只會(huì)被執(zhí)行一次袭异,偽代碼實(shí)現(xiàn)如下
//初始化 limiter 每秒生成1個(gè)令牌,令牌桶容量為20
limiter := rate.NewLimiter(rate.Every(time.Second), 1)
//模擬單位時(shí)間執(zhí)行多次操作
for i := 0; i < 5; i++ {
if limiter.Allow() {
fmt.Println("發(fā)送郵件")
} else {
fmt.Println("請(qǐng)求多次炬藤,過濾")
}
}
if limiter.Allow() {
fmt.Println("發(fā)送郵件")
}
執(zhí)行結(jié)果
發(fā)送郵件
請(qǐng)求多次御铃,過濾
請(qǐng)求多次,過濾
請(qǐng)求多次沈矿,過濾
請(qǐng)求多次畅买,過濾
發(fā)送郵件
我們發(fā)現(xiàn),第一次執(zhí)行是可以被允許的因?yàn)榈谝淮蔚牧钆票辉试S细睡,之后的請(qǐng)求失敗是因?yàn)檫€沒有生成新的令牌,所以需要等待1秒帝火,之后又可以進(jìn)行發(fā)送郵件操作溜徙。
通過這樣一個(gè)案例,相信大家對(duì)令牌桶的實(shí)現(xiàn)場(chǎng)景有了一個(gè)基本的了解犀填。
案例2——令牌取出單個(gè)和多個(gè)
初始化令牌桶容量為20蠢壹,設(shè)置每100毫秒生成一個(gè)令牌,即1秒生產(chǎn)10個(gè)令牌九巡。編碼測(cè)試功能
//初始化 limiter 每秒10個(gè)令牌图贸,令牌桶容量為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())
第二段編碼阻塞的場(chǎng)景在于,一次性取出20個(gè)令牌給予2秒的等待時(shí)間冕广,如果有20個(gè)令牌可以取出打印成功消息疏日,如果2秒等待時(shí)間內(nèi)沒有20個(gè)令牌可以取出,程序直接退出撒汉,即失敗沟优。
參考
Golang 標(biāo)準(zhǔn)庫限流器 time/rate 使用介紹
END
歡迎關(guān)注公眾號(hào) 程序員工具集 ???? 致力于分享優(yōu)秀的開源項(xiàng)目、學(xué)習(xí)資源 睬辐、常用工具
回復(fù)關(guān)鍵詞“關(guān)注禮包”挠阁,送你一份最全的程序員技能圖譜宾肺。