最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解go-zero如何實(shí)現(xiàn)令牌桶限流

 更新時(shí)間:2023年08月10日 08:22:12   作者:AlwaysBeta  
令牌桶算法既能夠?qū)⑺械恼埱笃骄植嫉綍r(shí)間區(qū)間內(nèi),又能接受服務(wù)器能夠承受范圍內(nèi)的突發(fā)請求,因此是目前使用較為廣泛的一種限流算法,本文就來看看go-zero如何實(shí)現(xiàn)令牌桶限流的吧

上一篇文章介紹了 如何實(shí)現(xiàn)計(jì)數(shù)器限流。主要有兩種實(shí)現(xiàn)方式,分別是固定窗口和滑動(dòng)窗口,并且分析了 go-zero 采用固定窗口方式實(shí)現(xiàn)的源碼。

但是采用固定窗口實(shí)現(xiàn)的限流器會(huì)有兩個(gè)問題:

  • 會(huì)出現(xiàn)請求量超出限制值兩倍的情況
  • 無法很好處理流量突增問題

這篇文章來介紹一下令牌桶算法,可以很好解決以上兩個(gè)問題。

工作原理

算法概念如下:

  • 令牌以固定速率生成;
  • 生成的令牌放入令牌桶中存放,如果令牌桶滿了則多余的令牌會(huì)直接丟棄,當(dāng)請求到達(dá)時(shí),會(huì)嘗試從令牌桶中取令牌,取到了令牌的請求可以執(zhí)行;
  • 如果桶空了,那么嘗試取令牌的請求會(huì)被直接丟棄。

令牌桶算法既能夠?qū)⑺械恼埱笃骄植嫉綍r(shí)間區(qū)間內(nèi),又能接受服務(wù)器能夠承受范圍內(nèi)的突發(fā)請求,因此是目前使用較為廣泛的一種限流算法。

源碼實(shí)現(xiàn)

源碼分析我們還是以 go-zero 項(xiàng)目為例,首先來看生成令牌的部分,依然是使用 Redis 來實(shí)現(xiàn)。

//?core/limit/tokenlimit.go
//?生成?token?速率
script?=?`local?rate?=?tonumber(ARGV[1])
//?通容量
local?capacity?=?tonumber(ARGV[2])
//?當(dāng)前時(shí)間戳
local?now?=?tonumber(ARGV[3])
//?請求數(shù)量
local?requested?=?tonumber(ARGV[4])
//?需要多少秒才能把桶填滿
local?fill_time?=?capacity/rate
//?向下取整,ttl?為填滿時(shí)間?2?倍
local?ttl?=?math.floor(fill_time*2)
//?當(dāng)前桶剩余容量,如果為?nil,說明第一次使用,賦值為桶最大容量
local?last_tokens?=?tonumber(redis.call("get",?KEYS[1]))
if?last_tokens?==?nil?then
????last_tokens?=?capacity
end
//?上次請求時(shí)間戳,如果為?nil?則賦值?0
local?last_refreshed?=?tonumber(redis.call("get",?KEYS[2]))
if?last_refreshed?==?nil?then
????last_refreshed?=?0
end
//?距離上一次請求的時(shí)間跨度
local?delta?=?math.max(0,?now-last_refreshed)
//?距離上一次請求的時(shí)間跨度能生成的?token?數(shù)量和桶內(nèi)剩余?token?數(shù)量的和
//?與桶容量比較,取二者的小值
local?filled_tokens?=?math.min(capacity,?last_tokens+(delta*rate))
//?判斷請求數(shù)量和桶內(nèi)?token?數(shù)量的大小
local?allowed?=?filled_tokens?>=?requested
//?被請求消耗掉之后,更新剩余?token?數(shù)量
local?new_tokens?=?filled_tokens
if?allowed?then
????new_tokens?=?filled_tokens?-?requested
end
//?更新?redis?token
redis.call("setex",?KEYS[1],?ttl,?new_tokens)
//?更新?redis?刷新時(shí)間
redis.call("setex",?KEYS[2],?ttl,?now)
return?allowed`

Redis 中主要保存兩個(gè) key,分別是 token 數(shù)量和刷新時(shí)間。

核心思想就是比較兩次請求時(shí)間間隔內(nèi)生成的 token 數(shù)量 + 桶內(nèi)剩余 token 數(shù)量,和請求量之間的大小,如果滿足則允許,否則則不允許。

限流器初始化:

//?A?TokenLimiter?controls?how?frequently?events?are?allowed?to?happen?with?in?one?second.
type?TokenLimiter?struct?{
????//?生成?token?速率
????rate???????????int
????//?桶容量
????burst??????????int
????store??????????*redis.Redis
????//?桶?key
????tokenKey???????string
????//?桶刷新時(shí)間?key
????timestampKey???string
????rescueLock?????sync.Mutex
????//?redis?健康標(biāo)識(shí)
????redisAlive?????uint32
????//?redis?健康監(jiān)控啟動(dòng)狀態(tài)
????monitorStarted?bool
????//?內(nèi)置單機(jī)限流器
????rescueLimiter??*xrate.Limiter
}
//?NewTokenLimiter?returns?a?new?TokenLimiter?that?allows?events?up?to?rate?and?permits
//?bursts?of?at?most?burst?tokens.
func?NewTokenLimiter(rate,?burst?int,?store?*redis.Redis,?key?string)?*TokenLimiter?{
????tokenKey?:=?fmt.Sprintf(tokenFormat,?key)
????timestampKey?:=?fmt.Sprintf(timestampFormat,?key)
????return?&TokenLimiter{
????????rate:??????????rate,
????????burst:?????????burst,
????????store:?????????store,
????????tokenKey:??????tokenKey,
????????timestampKey:??timestampKey,
????????redisAlive:????1,
????????rescueLimiter:?xrate.NewLimiter(xrate.Every(time.Second/time.Duration(rate)),?burst),
????}
}

其中有一個(gè)變量 rescueLimiter,這是一個(gè)進(jìn)程內(nèi)的限流器。如果 Redis 發(fā)生故障了,那么就使用這個(gè),算是一個(gè)保障,盡量避免系統(tǒng)被突發(fā)流量拖垮。

提供了四個(gè)可調(diào)用方法:

//?Allow?is?shorthand?for?AllowN(time.Now(),?1).
func?(lim?*TokenLimiter)?Allow()?bool?{
????return?lim.AllowN(time.Now(),?1)
}
//?AllowCtx?is?shorthand?for?AllowNCtx(ctx,time.Now(),?1)?with?incoming?context.
func?(lim?*TokenLimiter)?AllowCtx(ctx?context.Context)?bool?{
????return?lim.AllowNCtx(ctx,?time.Now(),?1)
}
//?AllowN?reports?whether?n?events?may?happen?at?time?now.
//?Use?this?method?if?you?intend?to?drop?/?skip?events?that?exceed?the?rate.
//?Otherwise,?use?Reserve?or?Wait.
func?(lim?*TokenLimiter)?AllowN(now?time.Time,?n?int)?bool?{
????return?lim.reserveN(context.Background(),?now,?n)
}
//?AllowNCtx?reports?whether?n?events?may?happen?at?time?now?with?incoming?context.
//?Use?this?method?if?you?intend?to?drop?/?skip?events?that?exceed?the?rate.
//?Otherwise,?use?Reserve?or?Wait.
func?(lim?*TokenLimiter)?AllowNCtx(ctx?context.Context,?now?time.Time,?n?int)?bool?{
????return?lim.reserveN(ctx,?now,?n)
}

最終調(diào)用的都是 reverveN 方法:

func?(lim?*TokenLimiter)?reserveN(ctx?context.Context,?now?time.Time,?n?int)?bool?{
????//?判斷?Redis?健康狀態(tài),如果?Redis?故障,則使用進(jìn)程內(nèi)限流器
????if?atomic.LoadUint32(&lim.redisAlive)?==?0?{
????????return?lim.rescueLimiter.AllowN(now,?n)
????}
????//?執(zhí)行限流腳本
????resp,?err?:=?lim.store.EvalCtx(ctx,
????????script,
????????[]string{
????????????lim.tokenKey,
????????????lim.timestampKey,
????????},
????????[]string{
????????????strconv.Itoa(lim.rate),
????????????strconv.Itoa(lim.burst),
????????????strconv.FormatInt(now.Unix(),?10),
????????????strconv.Itoa(n),
????????})
????//?redis?allowed?==?false
????//?Lua?boolean?false?->?r?Nil?bulk?reply
????if?err?==?redis.Nil?{
????????return?false
????}
????if?errors.Is(err,?context.DeadlineExceeded)?||?errors.Is(err,?context.Canceled)?{
????????logx.Errorf("fail?to?use?rate?limiter:?%s",?err)
????????return?false
????}
????if?err?!=?nil?{
????????logx.Errorf("fail?to?use?rate?limiter:?%s,?use?in-process?limiter?for?rescue",?err)
????????//?如果有異常的話,會(huì)啟動(dòng)進(jìn)程內(nèi)限流
????????lim.startMonitor()
????????return?lim.rescueLimiter.AllowN(now,?n)
????}
????code,?ok?:=?resp.(int64)
????if?!ok?{
????????logx.Errorf("fail?to?eval?redis?script:?%v,?use?in-process?limiter?for?rescue",?resp)
????????lim.startMonitor()
????????return?lim.rescueLimiter.AllowN(now,?n)
????}
????//?redis?allowed?==?true
????//?Lua?boolean?true?->?r?integer?reply?with?value?of?1
????return?code?==?1
}

最后看一下進(jìn)程內(nèi)限流的啟動(dòng)與恢復(fù):

func?(lim?*TokenLimiter)?startMonitor()?{
????lim.rescueLock.Lock()
????defer?lim.rescueLock.Unlock()
????//?需要加鎖保護(hù),如果程序已經(jīng)啟動(dòng)了,直接返回,不要重復(fù)啟動(dòng)
????if?lim.monitorStarted?{
????????return
????}
????lim.monitorStarted?=?true
????atomic.StoreUint32(&lim.redisAlive,?0)
????go?lim.waitForRedis()
}
func?(lim?*TokenLimiter)?waitForRedis()?{
????ticker?:=?time.NewTicker(pingInterval)
????//?更新監(jiān)控進(jìn)程的狀態(tài)
????defer?func()?{
????????ticker.Stop()
????????lim.rescueLock.Lock()
????????lim.monitorStarted?=?false
????????lim.rescueLock.Unlock()
????}()
????for?range?ticker.C?{
????????//?對(duì)?redis?進(jìn)行健康監(jiān)測,如果?redis?服務(wù)恢復(fù)了
????????//?則更新?redisAlive?標(biāo)識(shí),并退出?goroutine
????????if?lim.store.Ping()?{
????????????atomic.StoreUint32(&lim.redisAlive,?1)
????????????return
????????}
????}
}

以上就是詳解go-zero如何實(shí)現(xiàn)令牌桶限流的詳細(xì)內(nèi)容,更多關(guān)于go-zero令牌桶限流的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang?websocket協(xié)議使用淺析

    Golang?websocket協(xié)議使用淺析

    這篇文章主要介紹了Golang?websocket協(xié)議的使用,WebSocket是一種新型的網(wǎng)絡(luò)通信協(xié)議,可以在Web應(yīng)用程序中實(shí)現(xiàn)雙向通信,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05
  • GoLang實(shí)現(xiàn)Viper庫的封裝流程詳解

    GoLang實(shí)現(xiàn)Viper庫的封裝流程詳解

    Viper是一個(gè)用于Go語言應(yīng)用程序的配置管理庫,它提供了一種簡單而靈活的方式來處理應(yīng)用程序的配置,支持多種格式的配置文件,這篇文章主要介紹了GoLang封裝Viper庫的流程,感興趣的同學(xué)可以參考下文
    2023-05-05
  • 深入了解Go語言中context的用法

    深入了解Go語言中context的用法

    這篇文章主要為大家詳細(xì)介紹了Go語言中context用法的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-07-07
  • Go語言服務(wù)器開發(fā)實(shí)現(xiàn)最簡單HTTP的GET與POST接口

    Go語言服務(wù)器開發(fā)實(shí)現(xiàn)最簡單HTTP的GET與POST接口

    這篇文章主要介紹了Go語言服務(wù)器開發(fā)實(shí)現(xiàn)最簡單HTTP的GET與POST接口,實(shí)例分析了Go語言http包的使用技巧,需要的朋友可以參考下
    2015-02-02
  • golang time包下定時(shí)器的實(shí)現(xiàn)方法

    golang time包下定時(shí)器的實(shí)現(xiàn)方法

    定時(shí)器的實(shí)現(xiàn)大家應(yīng)該都遇到過,最近在學(xué)習(xí)golang,所以下面這篇文章主要給大家介紹了關(guān)于golang time包下定時(shí)器的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-12-12
  • Golang Gob編碼(gob包的使用詳解)

    Golang Gob編碼(gob包的使用詳解)

    這篇文章主要介紹了Golang Gob編碼(gob包的使用詳解),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Go的固定時(shí)長定時(shí)器和周期性時(shí)長定時(shí)器

    Go的固定時(shí)長定時(shí)器和周期性時(shí)長定時(shí)器

    本文主要介紹了Go的固定時(shí)長定時(shí)器和周期性時(shí)長定時(shí)器,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Golang實(shí)現(xiàn)不被復(fù)制的結(jié)構(gòu)體的方法

    Golang實(shí)現(xiàn)不被復(fù)制的結(jié)構(gòu)體的方法

    sync包中的許多結(jié)構(gòu)都是不允許拷貝的,因?yàn)樗鼈冏陨泶鎯?chǔ)了一些狀態(tài)(比如等待者的數(shù)量),如果你嘗試復(fù)制這些結(jié)構(gòu)體,就會(huì)在你的?IDE中看到警告,那這是怎么實(shí)現(xiàn)的呢,下文就來和大家詳細(xì)講講
    2023-03-03
  • go benchmark 基準(zhǔn)測試詳解

    go benchmark 基準(zhǔn)測試詳解

    這篇文章主要介紹了go benchmark 基準(zhǔn)測試詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Go字典使用詳解

    Go字典使用詳解

    今天和大家一起學(xué)習(xí)Go語言的字典。Go語言的字典又稱為map,一種使用廣泛的數(shù)據(jù)結(jié)構(gòu)。它是擁有key/value對(duì)元素的「無序集合」,而且在集合中key必須是唯一的
    2022-11-11

最新評(píng)論

新安县| 兰州市| 勃利县| 福贡县| 靖宇县| 庆元县| 沅江市| 琼结县| 蒲城县| 玛曲县| 玛多县| 明星| 广东省| 波密县| 莫力| 南充市| 金堂县| 叙永县| 新干县| 常熟市| 阳曲县| 稻城县| 勃利县| 崇明县| 遂平县| 滨海县| 渭源县| 灵宝市| 八宿县| 威宁| 大兴区| 沙坪坝区| 内黄县| 兴仁县| 榆社县| 栾城县| 包头市| 厦门市| 化州市| 高清| 和田市|