詳解go-zero如何實現(xiàn)計數(shù)器限流
上一篇文章 go-zero 是如何做路由管理的? 介紹了路由管理,這篇文章來說說限流,主要介紹計數(shù)器限流算法,具體的代碼實現(xiàn),我們還是來分析微服務(wù)框架 go-zero 的源碼。
在微服務(wù)架構(gòu)中,一個服務(wù)可能需要頻繁地與其他服務(wù)交互,而過多的請求可能導(dǎo)致性能下降或系統(tǒng)崩潰。為了確保系統(tǒng)的穩(wěn)定性和高可用性,限流算法應(yīng)運(yùn)而生。
限流算法允許在給定時間段內(nèi),對服務(wù)的請求流量進(jìn)行控制和調(diào)整,以防止資源耗盡和服務(wù)過載。
計數(shù)器限流算法主要有兩種實現(xiàn)方式,分別是:
- 固定窗口計數(shù)器
- 滑動窗口計數(shù)器
下面分別來介紹。
固定窗口計數(shù)器
算法概念如下:
- 將時間劃分為多個窗口;
- 在每個窗口內(nèi)每有一次請求就將計數(shù)器加一;
- 如果計數(shù)器超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄當(dāng)時間到達(dá)下一個窗口時,計數(shù)器重置。

固定窗口計數(shù)器是最為簡單的算法,但這個算法有時會讓通過請求量允許為限制的兩倍。

考慮如下情況:限制 1 秒內(nèi)最多通過 5 個請求,在第一個窗口的最后半秒內(nèi)通過了 5 個請求,第二個窗口的前半秒內(nèi)又通過了 5 個請求。這樣看來就是在 1 秒內(nèi)通過了 10 個請求。
滑動窗口計數(shù)器
算法概念如下:
- 將時間劃分為多個區(qū)間;
- 在每個區(qū)間內(nèi)每有一次請求就將計數(shù)器加一維持一個時間窗口,占據(jù)多個區(qū)間;
- 每經(jīng)過一個區(qū)間的時間,則拋棄最老的一個區(qū)間,并納入最新的一個區(qū)間;
- 如果當(dāng)前窗口內(nèi)區(qū)間的請求計數(shù)總和超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄。

滑動窗口計數(shù)器是通過將窗口再細(xì)分,并且按照時間滑動,這種算法避免了固定窗口計數(shù)器帶來的雙倍突發(fā)請求,但時間區(qū)間的精度越高,算法所需的空間容量就越大。
go-zero 實現(xiàn)
go-zero 實現(xiàn)的是固定窗口的方式,計算一段時間內(nèi)對同一個資源的訪問次數(shù),如果超過指定的 limit,則拒絕訪問。當(dāng)然如果在一段時間內(nèi)訪問不同的資源,每一個資源訪問量都不超過 limit,此種情況是不會拒絕的。
而在一個分布式系統(tǒng)中,存在多個微服務(wù)提供服務(wù)。所以當(dāng)瞬間的流量同時訪問同一個資源,如何讓計數(shù)器在分布式系統(tǒng)中正常計數(shù)?
這里要解決的一個主要問題就是計算的原子性,保證多個計算都能得到正確結(jié)果。
通過以下兩個方面來解決:
- 使用 redis 的
incrby做資源訪問計數(shù) - 采用 lua script 做整個窗口計算,保證計算的原子性
接下來先看一下 lua script 的源碼:
//?core/limit/periodlimit.go
const?periodScript?=?`local?limit?=?tonumber(ARGV[1])
local?window?=?tonumber(ARGV[2])
local?current?=?redis.call("INCRBY",?KEYS[1],?1)
if?current?==?1?then
????redis.call("expire",?KEYS[1],?window)
end
if?current?<?limit?then
????return?1
elseif?current?==?limit?then
????return?2
else
????return?0
end`主要就是使用 INCRBY 命令來實現(xiàn),第一次請求需要給 key 加上一個過期時間,到達(dá)過期時間之后,key 過期被清楚,重新計數(shù)。
限流器初始化:
type?(
????//?PeriodOption?defines?the?method?to?customize?a?PeriodLimit.
????PeriodOption?func(l?*PeriodLimit)
????//?A?PeriodLimit?is?used?to?limit?requests?during?a?period?of?time.
????PeriodLimit?struct?{
????????period?????int??//?窗口大小,單位?s
????????quota??????int??//?請求上限
????????limitStore?*redis.Redis
????????keyPrefix??string???//?key?前綴
????????align??????bool
????}
)
//?NewPeriodLimit?returns?a?PeriodLimit?with?given?parameters.
func?NewPeriodLimit(period,?quota?int,?limitStore?*redis.Redis,?keyPrefix?string,
????opts?...PeriodOption)?*PeriodLimit?{
????limiter?:=?&PeriodLimit{
????????period:?????period,
????????quota:??????quota,
????????limitStore:?limitStore,
????????keyPrefix:??keyPrefix,
????}
????for?_,?opt?:=?range?opts?{
????????opt(limiter)
????}
????return?limiter
}調(diào)用限流:
//?key?就是需要被限制的資源標(biāo)識
func?(h?*PeriodLimit)?Take(key?string)?(int,?error)?{
????return?h.TakeCtx(context.Background(),?key)
}
//?TakeCtx?requests?a?permit?with?context,?it?returns?the?permit?state.
func?(h?*PeriodLimit)?TakeCtx(ctx?context.Context,?key?string)?(int,?error)?{
????resp,?err?:=?h.limitStore.EvalCtx(ctx,?periodScript,?[]string{h.keyPrefix?+?key},?[]string{
????????strconv.Itoa(h.quota),
????????strconv.Itoa(h.calcExpireSeconds()),
????})
????if?err?!=?nil?{
????????return?Unknown,?err
????}
????code,?ok?:=?resp.(int64)
????if?!ok?{
????????return?Unknown,?ErrUnknownCode
????}
????switch?code?{
????case?internalOverQuota:?//?超過上限
????????return?OverQuota,?nil
????case?internalAllowed:???//?未超過,允許訪問
????????return?Allowed,?nil
????case?internalHitQuota:??//?正好達(dá)到限流上限
????????return?HitQuota,?nil
????default:
????????return?Unknown,?ErrUnknownCode
????}
}到此這篇關(guān)于詳解go-zero如何實現(xiàn)計數(shù)器限流的文章就介紹到這了,更多相關(guān)go-zero計數(shù)器限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang將多路復(fù)異步io轉(zhuǎn)成阻塞io的方法詳解
常見的IO模型有阻塞、非阻塞、IO多路復(fù)用,異,下面這篇文章主要給大家介紹了關(guān)于golang將多路復(fù)異步io轉(zhuǎn)成阻塞io的方法,文中給出了詳細(xì)的示例代碼,需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
深入理解Golang中的Protocol Buffers及其應(yīng)用
本篇文章將深入探討 Go 語言中使用 Protobuf 的基礎(chǔ)知識、常見應(yīng)用以及最佳實踐,希望能幫大家了解如何在項目中高效利用 Protobuf2024-11-11
如何通過Golang的container/list實現(xiàn)LRU緩存算法
文章介紹了Go語言中container/list包實現(xiàn)的雙向鏈表,并探討了如何使用鏈表實現(xiàn)LRU緩存,LRU緩存通過維護(hù)一個雙向鏈表來管理數(shù)據(jù),確保在插入和刪除操作時能夠以O(shè)(1)的平均時間復(fù)雜度運(yùn)行,提供了鏈表的操作和使用場景,并附帶了實現(xiàn)LRU緩存的代碼示例,感興趣的朋友一起看看吧2025-03-03
golang 實現(xiàn)比特幣內(nèi)核之處理橢圓曲線中的天文數(shù)字
比特幣密碼學(xué)中涉及到的大數(shù)運(yùn)算超出常規(guī)整數(shù)范圍,需使用golang的big包進(jìn)行處理,通過使用big.Int類型,能有效避免整數(shù)溢出,并保持邏輯正確性,測試展示了在不同質(zhì)數(shù)模下的運(yùn)算結(jié)果,驗證了邏輯的準(zhǔn)確性,此外,探討了費馬小定理在有限字段除法運(yùn)算中的應(yīng)用2024-11-11

