詳解go-zero如何實(shí)現(xiàn)計(jì)數(shù)器限流
上一篇文章 go-zero 是如何做路由管理的? 介紹了路由管理,這篇文章來說說限流,主要介紹計(jì)數(shù)器限流算法,具體的代碼實(shí)現(xiàn),我們還是來分析微服務(wù)框架 go-zero 的源碼。
在微服務(wù)架構(gòu)中,一個(gè)服務(wù)可能需要頻繁地與其他服務(wù)交互,而過多的請求可能導(dǎo)致性能下降或系統(tǒng)崩潰。為了確保系統(tǒng)的穩(wěn)定性和高可用性,限流算法應(yīng)運(yùn)而生。
限流算法允許在給定時(shí)間段內(nèi),對服務(wù)的請求流量進(jìn)行控制和調(diào)整,以防止資源耗盡和服務(wù)過載。
計(jì)數(shù)器限流算法主要有兩種實(shí)現(xiàn)方式,分別是:
- 固定窗口計(jì)數(shù)器
- 滑動窗口計(jì)數(shù)器
下面分別來介紹。
固定窗口計(jì)數(shù)器
算法概念如下:
- 將時(shí)間劃分為多個(gè)窗口;
- 在每個(gè)窗口內(nèi)每有一次請求就將計(jì)數(shù)器加一;
- 如果計(jì)數(shù)器超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄當(dāng)時(shí)間到達(dá)下一個(gè)窗口時(shí),計(jì)數(shù)器重置。
固定窗口計(jì)數(shù)器是最為簡單的算法,但這個(gè)算法有時(shí)會讓通過請求量允許為限制的兩倍。
考慮如下情況:限制 1 秒內(nèi)最多通過 5 個(gè)請求,在第一個(gè)窗口的最后半秒內(nèi)通過了 5 個(gè)請求,第二個(gè)窗口的前半秒內(nèi)又通過了 5 個(gè)請求。這樣看來就是在 1 秒內(nèi)通過了 10 個(gè)請求。
滑動窗口計(jì)數(shù)器
算法概念如下:
- 將時(shí)間劃分為多個(gè)區(qū)間;
- 在每個(gè)區(qū)間內(nèi)每有一次請求就將計(jì)數(shù)器加一維持一個(gè)時(shí)間窗口,占據(jù)多個(gè)區(qū)間;
- 每經(jīng)過一個(gè)區(qū)間的時(shí)間,則拋棄最老的一個(gè)區(qū)間,并納入最新的一個(gè)區(qū)間;
- 如果當(dāng)前窗口內(nèi)區(qū)間的請求計(jì)數(shù)總和超過了限制數(shù)量,則本窗口內(nèi)所有的請求都被丟棄。
滑動窗口計(jì)數(shù)器是通過將窗口再細(xì)分,并且按照時(shí)間滑動,這種算法避免了固定窗口計(jì)數(shù)器帶來的雙倍突發(fā)請求,但時(shí)間區(qū)間的精度越高,算法所需的空間容量就越大。
go-zero 實(shí)現(xiàn)
go-zero 實(shí)現(xiàn)的是固定窗口的方式,計(jì)算一段時(shí)間內(nèi)對同一個(gè)資源的訪問次數(shù),如果超過指定的 limit
,則拒絕訪問。當(dāng)然如果在一段時(shí)間內(nèi)訪問不同的資源,每一個(gè)資源訪問量都不超過 limit
,此種情況是不會拒絕的。
而在一個(gè)分布式系統(tǒng)中,存在多個(gè)微服務(wù)提供服務(wù)。所以當(dāng)瞬間的流量同時(shí)訪問同一個(gè)資源,如何讓計(jì)數(shù)器在分布式系統(tǒng)中正常計(jì)數(shù)?
這里要解決的一個(gè)主要問題就是計(jì)算的原子性,保證多個(gè)計(jì)算都能得到正確結(jié)果。
通過以下兩個(gè)方面來解決:
- 使用 redis 的
incrby
做資源訪問計(jì)數(shù) - 采用 lua script 做整個(gè)窗口計(jì)算,保證計(jì)算的原子性
接下來先看一下 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
命令來實(shí)現(xiàn),第一次請求需要給 key 加上一個(gè)過期時(shí)間,到達(dá)過期時(shí)間之后,key 過期被清楚,重新計(jì)數(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如何實(shí)現(xiàn)計(jì)數(shù)器限流的文章就介紹到這了,更多相關(guān)go-zero計(jì)數(shù)器限流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言實(shí)現(xiàn)并發(fā)控制的常見方式詳解
這篇文章主要為大家詳細(xì)介紹了Go語言實(shí)現(xiàn)并發(fā)控制的幾種常見方式,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-03-03Golang?channel關(guān)閉后是否可以讀取剩余的數(shù)據(jù)詳解
這篇文章主要介紹了Golang?channel關(guān)閉后是否可以讀取剩余的數(shù)據(jù),文章通過一個(gè)測試?yán)咏o大家詳細(xì)的介紹了是否可以讀取剩余的數(shù)據(jù),需要的朋友可以參考下2023-09-09基于go實(shí)例網(wǎng)絡(luò)存儲協(xié)議詳解
這篇文章主要為大家介紹了基于go實(shí)例網(wǎng)絡(luò)存儲協(xié)議詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Go語言實(shí)現(xiàn)自動填寫古詩詞實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Go語言實(shí)現(xiàn)自動填寫古詩詞的相關(guān)資料,這是最近在項(xiàng)目中遇到的一個(gè)需求,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03基于Go語言構(gòu)建RESTful API服務(wù)
在實(shí)際開發(fā)項(xiàng)目中,你編寫的服務(wù)可以被其他服務(wù)使用,這樣就組成了微服務(wù)的架構(gòu);也可以被前端調(diào)用,這樣就可以前后端分離。那么,本文主要介紹什么是 RESTful API,以及 Go 語言是如何玩轉(zhuǎn) RESTful API 的2021-07-07Go使用TimerController解決timer過多的問題
多路復(fù)用,實(shí)際上Go底層也是一種多路復(fù)用的思想去實(shí)現(xiàn)的timer,但是它是底層的timer,我們需要解決的問題就過多的timer問題!本文給大家介紹了Go使用TimerController解決timer過多的問題,需要的朋友可以參考下2024-12-12