詳解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中按照結(jié)構(gòu)體的某個字段排序?qū)嵗a
在任何編程語言中,關(guān)乎到數(shù)據(jù)的排序都會有對應(yīng)的策略,下面這篇文章主要給大家介紹了關(guān)于golang中按照結(jié)構(gòu)體的某個字段排序的相關(guān)資料,需要的朋友可以參考下2022-05-05
Go語言關(guān)于幾種深度拷貝(deepcopy)方法的性能對比
這篇文章主要介紹了Go語言關(guān)于幾種深度拷貝(deepcopy)方法的性能對比,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
Go項目中正確升級第三方依賴實戰(zhàn)指南(使用Go?Modules)
這篇文章主要介紹了Go項目中使用Go?Modules正確升級第三方依賴的相關(guān)資料,Go Modules是Go語言官方提供的依賴管理工具,通過去中心化設(shè)計和內(nèi)置的版本控制機(jī)制,實現(xiàn)了高效、安全的依賴管理,需要的朋友可以參考下2026-06-06
使用Go語言實現(xiàn)發(fā)送HTTP請求并給GET添加參數(shù)
在開發(fā)Web應(yīng)用程序時,我們經(jīng)常需要向服務(wù)器發(fā)送HTTP請求,本文將介紹一下使用Go語言發(fā)送HTTP請求,并給GET請求添加參數(shù)的方法,感興趣的小伙伴可以了解一下2023-07-07

