網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
限流器是提升服務(wù)穩(wěn)定性的非常重要的組件,可以用來(lái)限制請(qǐng)求速率,保護(hù)服務(wù),以免服務(wù)過(guò)載。限流器的實(shí)現(xiàn)方法有很多種,常見(jiàn)的限流算法有固定窗口、滑動(dòng)窗口、漏桶、令牌桶
簡(jiǎn)單來(lái)說(shuō),令牌桶就是想象有一個(gè)固定大小的桶,系統(tǒng)會(huì)以恒定速率向桶中放 Token,桶滿則暫時(shí)不放。在請(qǐng)求比較的少的時(shí)候桶可以先"攢"一些Token,應(yīng)對(duì)突發(fā)的流量,如果桶中有剩余 Token 就可以一直取。如果沒(méi)有剩余 Token,則需要等到桶中被放置了 Token 才行。
有的同學(xué)在看明白令牌桶的原理后就非常想去自己實(shí)現(xiàn)一個(gè)限流器應(yīng)用到自己的項(xiàng)目里,em... 怎么說(shuō)呢,造個(gè)輪子確實(shí)有利于自己水平提高,不過(guò)要是應(yīng)用到商用項(xiàng)目里的話其實(shí)大可不必自己去造輪子,Golang官方已經(jīng)替我們?cè)旌幂喿永?......~!
Golang 官方提供的擴(kuò)展庫(kù)里就自帶了限流算法的實(shí)現(xiàn),即 golang.org/x/time/rate
。該限流器也是基于 Token Bucket(令牌桶) 實(shí)現(xiàn)的。
限流器的內(nèi)部結(jié)構(gòu)
time/rate
包的Limiter
類型對(duì)限流器進(jìn)行了定義,所有限流功能都是通過(guò)基于Limiter
類型實(shí)現(xiàn)的,其內(nèi)部結(jié)構(gòu)如下:
type Limiter struct { mu sync.Mutex limit Limit burst int // 令牌桶的大小 tokens float64 last time.Time // 上次更新tokens的時(shí)間 lastEvent time.Time // 上次發(fā)生限速器事件的時(shí)間(通過(guò)或者限制都是限速器事件) }
其主要字段的作用是:
limit:
limit
字段表示往桶里放Token的速率,它的類型是Limit,是int64的類型別名。設(shè)置limit
時(shí)既可以用數(shù)字指定每秒向桶中放多少個(gè)Token,也可以指定向桶中放Token的時(shí)間間隔,其實(shí)指定了每秒放Token的個(gè)數(shù)后就能計(jì)算出放每個(gè)Token的時(shí)間間隔了。burst: 令牌桶的大小。
tokens: 桶中的令牌。
last: 上次往桶中放 Token 的時(shí)間。
lastEvent:上次發(fā)生限速器事件的時(shí)間(通過(guò)或者限制都是限速器事件)
可以看到在 timer/rate
的限流器實(shí)現(xiàn)中,并沒(méi)有單獨(dú)維護(hù)一個(gè) Timer 和隊(duì)列去真的每隔一段時(shí)間向桶中放令牌,而是僅僅通過(guò)計(jì)數(shù)的方式表示桶中剩余的令牌。每次消費(fèi)取 Token 之前會(huì)先根據(jù)上次更新令牌數(shù)的時(shí)間差更新桶中Token數(shù)。
大概了解了time/rate
限流器的內(nèi)部實(shí)現(xiàn)后,下面的內(nèi)容我們會(huì)集中介紹下該組件的具體使用方法:
構(gòu)造限流器
我們可以使用以下方法構(gòu)造一個(gè)限流器對(duì)象:
limiter := rate.NewLimiter(10, 100);
這里有兩個(gè)參數(shù):
第一個(gè)參數(shù)是
r Limit
,設(shè)置的是限流器Limiter的limit
字段,代表每秒可以向 Token 桶中產(chǎn)生多少 token。Limit 實(shí)際上是 float64 的別名。第二個(gè)參數(shù)是
b int
,b 代表 Token 桶的容量大小,也就是設(shè)置的限流器 Limiter 的burst
字段。
那么,對(duì)于以上例子來(lái)說(shuō),其構(gòu)造出的限流器的令牌桶大小為 100, 以每秒 10 個(gè) Token 的速率向桶中放置 Token。
除了給r Limit
參數(shù)直接指定每秒產(chǎn)生的 Token 個(gè)數(shù)外,還可以用 Every 方法來(lái)指定向桶中放置 Token 的間隔,例如:
limit := rate.Every(100 * time.Millisecond); limiter := rate.NewLimiter(limit, 100);
以上就表示每 100ms 往桶中放一個(gè) Token。本質(zhì)上也是一秒鐘往桶里放 10 個(gè)。
使用限流器
Limiter 提供了三類方法供程序消費(fèi) Token,可以每次消費(fèi)一個(gè) Token,也可以一次性消費(fèi)多個(gè) Token。每種方法代表了當(dāng) Token 不足時(shí),各自不同的對(duì)應(yīng)手段,可以阻塞等待桶中Token補(bǔ)充,也可以直接返回取Token失敗。
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,來(lái)決定此次 Wait 的最長(zhǎng)時(shí)間。
// 一直等到獲取到桶中的令牌 err := limiter.Wait(context.Background()) if err != nil { fmt.Println("Error: ", err) } // 設(shè)置一秒的等待超時(shí)時(shí)間 ctx, _ := context.WithTimeout(context.Background(), time.Second * 1) err := limiter.Wait(ctx) if err != nil { fmt.Println("Error: ", err) }
Allow/AllowN
func (lim *Limiter) Allow() bool func (lim *Limiter) AllowN(now time.Time, n int) bool
Allow 實(shí)際上就是對(duì) AllowN(time.Now(),1)
進(jìn)行簡(jiǎn)化的函數(shù)。
AllowN 方法表示,截止到某一時(shí)刻,目前桶中數(shù)目是否至少為 n 個(gè),滿足則返回 true,同時(shí)從桶中消費(fèi) n 個(gè) token。反之不消費(fèi)桶中的Token,返回false。
對(duì)應(yīng)線上的使用場(chǎng)景是,如果請(qǐng)求速率超過(guò)限制,就直接丟棄超頻后的請(qǐng)求。
if limiter.AllowN(time.Now(), 2) { fmt.Println("event allowed") } else { fmt.Println("event not allowed") }
Reserve/ReserveN
func (lim *Limiter) Reserve() *Reservation func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation
Reserve 相當(dāng)于 ReserveN(time.Now(), 1)
。
ReserveN 的用法就相對(duì)來(lái)說(shuō)復(fù)雜一些,當(dāng)調(diào)用完成后,無(wú)論 Token 是否充足,都會(huì)返回一個(gè) *Reservation
對(duì)象。你可以調(diào)用該對(duì)象的Delay()
方法,該方法返回的參數(shù)類型為time.Duration
,反映了需要等待的時(shí)間,必須等到等待時(shí)間之后,才能進(jìn)行接下來(lái)的工作。如果不想等待,可以調(diào)用Cancel()
方法,該方法會(huì)將 Token 歸還。
舉一個(gè)簡(jiǎn)單的例子,我們可以這么使用 Reserve 方法。
r := limiter.Reserve() f !r.OK() { // Not allowed to act! Did you remember to set lim.burst to be > 0 ? return } time.Sleep(r.Delay()) Act() // 執(zhí)行相關(guān)邏輯
動(dòng)態(tài)調(diào)整速率和桶大小
Limiter 支持創(chuàng)建后動(dòng)態(tài)調(diào)整速率和桶大小:
SetLimit(Limit) 改變放入 Token 的速率
SetBurst(int) 改變 Token 桶大小
有了這兩個(gè)方法,可以根據(jù)現(xiàn)有環(huán)境和條件以及我們的需求,動(dòng)態(tài)地改變 Token 桶大小和速率。
總結(jié)
今天我們總結(jié)了 Golang 官方限流器的使用方法,它是一種令牌桶算實(shí)現(xiàn)的限流器。其中 Wait/WaitN,Allow/AllowN 這兩組方法在平時(shí)用的比較多,前者是消費(fèi)Token時(shí)如果桶中Token不足可以讓程序等待桶中新Token的放入(最好設(shè)置上等待時(shí)長(zhǎng))后者則是在桶中的Token不足時(shí)選擇直接丟棄請(qǐng)求。
除了Golang官方提供的限流器實(shí)現(xiàn),Uber公司開(kāi)源的限流器uber-go/ratelimit
也是一個(gè)很好的選擇,與Golang官方限流器不同的是Uber的限流器是通過(guò)漏桶算法實(shí)現(xiàn)的,不過(guò)對(duì)傳統(tǒng)的漏桶算法進(jìn)行了改良,有興趣的同學(xué)可以自行去體驗(yàn)一下。
原文鏈接:https://juejin.cn/post/7083811372420562951
相關(guān)推薦
- 2023-06-21 python相對(duì)包導(dǎo)入報(bào)“Attempted?relative?import?in?non-pack
- 2023-01-01 MongoDB?Shell常用基本操作命令詳解_MongoDB
- 2022-12-24 MobPush?for?Flutter集成準(zhǔn)備_IOS
- 2024-02-17 開(kāi)發(fā)中SpringBoot項(xiàng)目jar包過(guò)大的解決辦法
- 2022-04-24 python使用技巧-文件讀寫_python
- 2022-10-19 Docker鏡像與容器的導(dǎo)入導(dǎo)出以及常用命令總結(jié)_docker
- 2022-12-01 Golang打印復(fù)雜結(jié)構(gòu)體兩種方法詳解_Golang
- 2022-08-01 C++簡(jiǎn)單又輕松的講解類和對(duì)象中友元函數(shù)_C 語(yǔ)言
- 最近更新
-
- 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)證過(guò)濾器
- 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)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支