上一篇文章 go-zero 是如何做路由管理的? 介紹了路由管理,這篇文章來(lái)說(shuō)說(shuō)限流,主要介紹計(jì)數(shù)器限流算法,具體的代碼實(shí)現(xiàn),我們還是來(lái)分析微服務(wù)框架 go-zero 的源碼。
在微服務(wù)架構(gòu)中,一個(gè)服務(wù)可能需要頻繁地與其他服務(wù)交互,而過(guò)多的請(qǐng)求可能導(dǎo)致性能下降或系統(tǒng)崩潰。為了確保系統(tǒng)的穩(wěn)定性和高可用性,限流算法應(yīng)運(yùn)而生。
限流算法允許在給定時(shí)間段內(nèi),對(duì)服務(wù)的請(qǐng)求流量進(jìn)行控制和調(diào)整,以防止資源耗盡和服務(wù)過(guò)載。
計(jì)數(shù)器限流算法主要有兩種實(shí)現(xiàn)方式,分別是:
下面分別來(lái)介紹。
算法概念如下:
圖片
固定窗口計(jì)數(shù)器是最為簡(jiǎn)單的算法,但這個(gè)算法有時(shí)會(huì)讓通過(guò)請(qǐng)求量允許為限制的兩倍。
圖片
考慮如下情況:限制 1 秒內(nèi)最多通過(guò) 5 個(gè)請(qǐng)求,在第一個(gè)窗口的最后半秒內(nèi)通過(guò)了 5 個(gè)請(qǐng)求,第二個(gè)窗口的前半秒內(nèi)又通過(guò)了 5 個(gè)請(qǐng)求。這樣看來(lái)就是在 1 秒內(nèi)通過(guò)了 10 個(gè)請(qǐng)求。
算法概念如下:
圖片
滑動(dòng)窗口計(jì)數(shù)器是通過(guò)將窗口再細(xì)分,并且按照時(shí)間滑動(dòng),這種算法避免了固定窗口計(jì)數(shù)器帶來(lái)的雙倍突發(fā)請(qǐng)求,但時(shí)間區(qū)間的精度越高,算法所需的空間容量就越大。
go-zero 實(shí)現(xiàn)的是固定窗口的方式,計(jì)算一段時(shí)間內(nèi)對(duì)同一個(gè)資源的訪(fǎng)問(wèn)次數(shù),如果超過(guò)指定的 limit,則拒絕訪(fǎng)問(wèn)。當(dāng)然如果在一段時(shí)間內(nèi)訪(fǎng)問(wèn)不同的資源,每一個(gè)資源訪(fǎng)問(wèn)量都不超過(guò) limit,此種情況是不會(huì)拒絕的。
而在一個(gè)分布式系統(tǒng)中,存在多個(gè)微服務(wù)提供服務(wù)。所以當(dāng)瞬間的流量同時(shí)訪(fǎng)問(wèn)同一個(gè)資源,如何讓計(jì)數(shù)器在分布式系統(tǒng)中正常計(jì)數(shù)?
這里要解決的一個(gè)主要問(wèn)題就是計(jì)算的原子性,保證多個(gè)計(jì)算都能得到正確結(jié)果。
通過(guò)以下兩個(gè)方面來(lái)解決:
接下來(lái)先看一下 lua script 的源碼:
// core/limit/periodlimit.goconst 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)endif current < limit then return 1elseif current == limit then return 2else return 0end`
主要就是使用 INCRBY 命令來(lái)實(shí)現(xiàn),第一次請(qǐng)求需要給 key 加上一個(gè)過(guò)期時(shí)間,到達(dá)過(guò)期時(shí)間之后,key 過(guò)期被清楚,重新計(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 // 請(qǐng)求上限 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)識(shí)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: // 超過(guò)上限 return OverQuota, nil case internalAllowed: // 未超過(guò),允許訪(fǎng)問(wèn) return Allowed, nil case internalHitQuota: // 正好達(dá)到限流上限 return HitQuota, nil default: return Unknown, ErrUnknownCode }}
上文已經(jīng)介紹了,固定時(shí)間窗口會(huì)有臨界突發(fā)問(wèn)題,并不是那么嚴(yán)謹(jǐn),下篇文章我們來(lái)介紹令牌桶限流。
以上就是本文的全部?jī)?nèi)容,如果覺(jué)得還不錯(cuò)的話(huà)歡迎點(diǎn)贊,轉(zhuǎn)發(fā)和關(guān)注,感謝支持。
參考文章:
本文鏈接:http://www.www897cc.com/showinfo-26-5165-0.html如何實(shí)現(xiàn)計(jì)數(shù)器限流?
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com