網(wǎng)站首頁 編程語言 正文
前言
在翻Golang官方庫的過程中,發(fā)現(xiàn)一個(gè)有趣的庫golang.org/x/time ,里面只有一個(gè)類rate,研究了一下發(fā)現(xiàn)它是一個(gè)限流器,實(shí)現(xiàn)了很多的功能,當(dāng)然它的核心原理并不復(fù)雜,也就是令牌桶算法。
令牌桶算法的原理是:令牌桶會(huì)不斷地把令牌添加到桶里,而請求會(huì)從桶中獲取令牌,只有擁有令牌地請求才能被接受。因?yàn)橥爸锌梢蕴崆氨A粢恍┝钆疲运试S一定地突發(fā)流量通過。
例子
下面是限流算法常見的寫法,首先判斷是否有令牌,如果有就通過,否則直接失敗。
package main import ( "fmt" "time" "golang.org/x/time/rate" ) func main() { // 每0.1秒生成一個(gè)令牌,也就是一秒10個(gè)令牌,最大保留令牌上限10 l := rate.NewLimiter(rate.Every(time.Second/10), 10) for i := 0; i < 10; i++ { go func(i int) { for { // 判斷是否有令牌,如果有就輸出 if l.Allow() { fmt.Printf("allow %d\n", i) } // 每0.5秒請求一次 time.Sleep(time.Second / 2) } }(i) } time.Sleep(time.Second * 10) }
上面的rate.Every(time.Second/10)會(huì)返回一個(gè)Limit類型,代表每秒生成多少個(gè)令牌。
這個(gè)庫還提供了另外一種寫法,等待直到有令牌為止(或超時(shí)):
func main() { l := rate.NewLimiter(rate.Every(time.Second/10), 100) for i := 0; i < 10; i++ { go func(i int) { for { // 等待直到有令牌 if err := l.Wait(context.TODO()); err != nil { } else { fmt.Printf("allow %d\n", i) } time.Sleep(time.Second / 2) } }(i) } time.Sleep(time.Second * 10) }
這樣在某些場景下我們可以讓請求等待一會(huì),而不是直接失敗。
還有一個(gè)更加特殊的請求令牌方式,也就是先預(yù)留令牌,到指定時(shí)間不再需要去獲取令牌,直接執(zhí)行操作即可:
func main() { l := rate.NewLimiter(rate.Every(time.Second/10), 10) for i := 0; i < 10; i++ { go func(i int) { for { // 先預(yù)留令牌 if r := l.Reserve(); r.OK() { // 休眠直到令牌生效 time.Sleep(r.Delay()) fmt.Printf("allow %d\n", i) } time.Sleep(time.Second / 2) } }(i) } time.Sleep(time.Second * 10) }
當(dāng)然,如果預(yù)留的令牌不想使用了,也可以使用r.Cancel()歸還已預(yù)留的令牌。
上面的Allow()、Wait()、Reserve()都是一次消耗一個(gè)令牌,其實(shí)都有對應(yīng)的AllowN()、WaitN()、ReserveN()方法,一次消耗N個(gè)令牌,這樣就可以根據(jù)任務(wù)消耗的資源靈活的消耗令牌。
實(shí)現(xiàn)
不管我們從Allow()、Wait()還是Reserve()進(jìn)去,最終都會(huì)進(jìn)入到reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation 方法:
首先在進(jìn)入方法的時(shí)候,會(huì)先處理兩種特殊情況:
// 如果無限生成令牌,則直接返回 if lim.limit == Inf { return Reservation{ ok: true, lim: lim, tokens: n, timeToAct: now, } // 如果不會(huì)生成令牌,則在初始令牌里面拿,直到拿完為止 } else if lim.limit == 0 { var ok bool if lim.burst >= n { ok = true lim.burst -= n } return Reservation{ ok: ok, lim: lim, tokens: lim.burst, timeToAct: now, } }
然后重新計(jì)算當(dāng)前有多少令牌,減去要消耗的令牌:
// 計(jì)算當(dāng)前有多少令牌 now, last, tokens := lim.advance(now) // 減去要消耗的N個(gè)令牌 tokens -= float64(n) // 如果剩余令牌為負(fù)數(shù),那么計(jì)算一下要等待多久才能拿到令牌 var waitDuration time.Duration if tokens < 0 { waitDuration = lim.limit.durationFromTokens(-tokens) } // 判斷請求是否成功,maxFutureReserve代表最大可以等待的時(shí)間,也就是請求能否接收拿到令牌需要等待的時(shí)間 ok := n <= lim.burst && waitDuration <= maxFutureReserve
余下的代碼就是更新限流器的狀態(tài)。
小結(jié)
可以看到這個(gè)令牌桶限流器實(shí)現(xiàn)的功能非常的豐富,如果需要令牌桶限流器,可以優(yōu)先考慮使用這個(gè)實(shí)現(xiàn)。
原文鏈接:https://juejin.cn/post/7130890097947312158
相關(guān)推薦
- 2022-06-16 Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過程解析示例_Golang
- 2023-01-09 Pyscript使用本地Pyodide配置步驟_python
- 2023-02-01 Python?asyncore?socket客戶端開發(fā)基本使用教程_python
- 2022-05-10 創(chuàng)建springboot過濾器
- 2022-05-21 python?判斷文件或文件夾是否存在_python
- 2022-05-28 關(guān)于docker?compose安裝redis集群的問題(集群擴(kuò)容、集群收縮)_docker
- 2022-09-25 創(chuàng)建的對象如何在堆區(qū)分配內(nèi)存
- 2023-04-02 GoLang調(diào)用鏈可視化go-callvis使用介紹_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支