亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

golang限流庫(kù)兩個(gè)大bug(半年之久無(wú)人提起)

 更新時(shí)間:2023年12月20日 10:18:43   作者:晁岳攀(鳥窩)?鳥窩聊技術(shù)  
最近我的同事在使用uber-go/ratelimit[1]這個(gè)限流庫(kù)的時(shí)候,遇到了兩個(gè)大?bug,這兩個(gè)?bug?都是在這個(gè)庫(kù)的最新版本(v0.3.0)中存在的,而這個(gè)版本從?7?月初發(fā)布都已經(jīng)過(guò)半年了,都沒(méi)人提?bug,難道大家都沒(méi)遇到過(guò)么

uber-go/ratelimit 庫(kù)

我先前都是使用juju/ratelimit[2]這個(gè)限流庫(kù)的,不過(guò)我不太喜歡這個(gè)庫(kù)的復(fù)雜的“構(gòu)造函數(shù)”,后來(lái)嘗試了uber-go/ratelimit[3]這個(gè)庫(kù)后,感覺(jué) SDK 設(shè)計(jì)比較簡(jiǎn)單,而且使用起來(lái)也不錯(cuò),就一直使用了。當(dāng)時(shí)的版本是v0.2.0,而且我也不會(huì)設(shè)置它的slack參數(shù),所以也相安無(wú)事。

最近我同事在做項(xiàng)目的時(shí)候,把這個(gè)庫(kù)更新到最新的v0.3.0,發(fā)現(xiàn)在發(fā)包一段時(shí)間后,突然限流不起作用了,發(fā)包頻率狂飆導(dǎo)致程序 panic。

通過(guò)單元測(cè)試復(fù)現(xiàn)

很容易通過(guò)下面一個(gè)單元測(cè)試復(fù)現(xiàn)這個(gè)問(wèn)題:

func TestLimiter(t *testing.T) {
 limiter := ratelimit.New(1, ratelimit.Per(time.Second), ratelimit.WithSlack(1))
 for i := 0; i < 25; i++ {
  if i == 1 {
   time.Sleep(2 * time.Second)
  }
  limiter.Take()
  fmt.Println(time.Now().Unix(), i) // burst
 }
}

slack 的判斷邏輯出現(xiàn)問(wèn)題

這個(gè)單元測(cè)試嘗試在第二個(gè)周期中不調(diào)用限流器,讓它有機(jī)會(huì)進(jìn)入 slack 判斷的邏輯。這個(gè)庫(kù)的 slack 設(shè)計(jì)的本意是在 rate 的基礎(chǔ)上留一點(diǎn)余地,不那么嚴(yán)格按照 rate 進(jìn)行限流,不過(guò)因?yàn)?code>v0.3.0代碼的問(wèn)題,導(dǎo)致 slack 的判斷邏輯出現(xiàn)了問(wèn)題:

func (t *atomicInt64Limiter) Take() time.Time {
 var (
  newTimeOfNextPermissionIssue int64
  now                          int64
 )
 for {
  now = t.clock.Now().UnixNano()
  timeOfNextPermissionIssue := atomic.LoadInt64(&t.state)
  switch {
  case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)):
   // if this is our first call or t.maxSlack == 0 we need to shrink issue time to now
   newTimeOfNextPermissionIssue = now
  case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack):
   // a lot of nanoseconds passed since the last Take call
   // we will limit max accumulated time to maxSlack
   newTimeOfNextPermissionIssue = now - int64(t.maxSlack)
  default:
   // calculate the time at which our permission was issued
   newTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest)
  }
  if atomic.CompareAndSwapInt64(&t.state, timeOfNextPermissionIssue, newTimeOfNextPermissionIssue) {
   break
  }
 }
 sleepDuration := time.Duration(newTimeOfNextPermissionIssue - now)
 if sleepDuration > 0 {
  t.clock.Sleep(sleepDuration)
  return time.Unix(0, newTimeOfNextPermissionIssue)
 }
 // return now if we don't sleep as atomicLimiter does
 return time.Unix(0, now)
}

原理分析

一旦進(jìn)入case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack):這個(gè)分支,你會(huì)發(fā)現(xiàn)后續(xù)調(diào)用Take基本都會(huì)進(jìn)入這個(gè)分支,程序不會(huì)阻塞,只要調(diào)用Take都不會(huì)阻塞??梢钥吹疆?dāng)設(shè)置 slack>0 的時(shí)候才會(huì)進(jìn)入這個(gè)分支,正好默認(rèn) slack=10。這個(gè) bug 也可以推算出來(lái)。假設(shè)當(dāng)前進(jìn)入這個(gè)分支,當(dāng)前時(shí)間是 now1,那么這次 Take 就會(huì)把newTimeOfNextPermissionIssue設(shè)置為 now1-int64(t.maxSlack)。

接下來(lái)再調(diào)用 Take,當(dāng)前時(shí)間是 now2,now2 總是會(huì)比 now1 大一點(diǎn),至少大幾納秒吧。這個(gè)時(shí)候我們計(jì)算分支的條件now-timeOfNextPermissionIssue > int64(t.maxSlack),這個(gè)條件肯定是成立的,因?yàn)?code>now2-(now1-int64(t.maxSlack)) = (now2-now1) + int64(t.maxSlack) > int64(t.maxSlack)。導(dǎo)致后續(xù)的每次 Take 都會(huì)進(jìn)入這個(gè)分支,不會(huì)阻塞,導(dǎo)致程序瘋狂發(fā)包,最終導(dǎo)致 panic。

周末的時(shí)候我給這個(gè)項(xiàng)目提了一個(gè) bug, 它的一個(gè)維護(hù)者進(jìn)行了修復(fù),不過(guò)這個(gè)項(xiàng)目主要開發(fā)者已經(jīng)對(duì)這個(gè)v0.3.0的實(shí)現(xiàn)喪失了信心,因?yàn)檫@個(gè)實(shí)現(xiàn)已經(jīng)出現(xiàn)過(guò)一次類似的 bug,被他回滾后了,后來(lái)有被修復(fù)才合進(jìn)來(lái),現(xiàn)在有出現(xiàn) bug 了。

不管作者修不修復(fù),你一定要注意,使用這個(gè)庫(kù)的v0.3.0一定小心,有可能踩到這個(gè)雷。

這個(gè)其中的一個(gè)大 bug。

其實(shí)我們對(duì) slack 的有無(wú)不是那么關(guān)心的,那么我們使用ratelimit.WithoutSlack這個(gè)選項(xiàng),把 slack 設(shè)置為 0,是不是就沒(méi)問(wèn)題了呢?

嗯,是的,不會(huì)再出現(xiàn)上面的 bug,而且在我的 mac 筆記本上跑的單元測(cè)試也每問(wèn)題,但是!但是!但是!又出現(xiàn)了另外一個(gè) bug。

我們把限流的速率修改為5000,結(jié)果在 Linux 測(cè)試機(jī)器上跑只能跑到接近2000,遠(yuǎn)遠(yuǎn)小于預(yù)期,那這還咋限流,流根本打不上去。

我的同事說(shuō)把ratelimit版本降到v0.2.0,同時(shí)不要設(shè)置slack=0可以解決這個(gè)問(wèn)題。

這就很奇怪了,經(jīng)過(guò)一番排查,發(fā)現(xiàn)問(wèn)題可能出在 Go 標(biāo)準(zhǔn)庫(kù)的time.Sleep上。

我們使用time.Sleep 休眠 50 微秒的話,在 Go 1.16 之前,Linux 機(jī)器上基本上實(shí)際會(huì)休眠 80、90 微秒,但是在 Go 1.16 之后,Linux 機(jī)器上 1 毫秒,差距巨大,在 Windows 機(jī)器上,Go 1.16 之前是 1 毫秒,之后是 14 毫秒,差距也是巨大的。我在蘋果的 MacPro M1 的機(jī)器測(cè)試,就沒(méi)有這個(gè)問(wèn)題。

這個(gè) bug 記錄在issues#44343[4], 自 2021 年 2 月提出來(lái)來(lái),已經(jīng)快三年了,這個(gè) bug 還一直沒(méi)有關(guān)閉,問(wèn)題還一直存在著,看樣子這個(gè) bug 也不是那么容易找到根因和徹底解決。

所以如果你要使用time.Sleep,請(qǐng)記得在 Linux 環(huán)境下,它的精度也就在1ms左右。所以ratelimit庫(kù)如果依賴它做 5000 的限流,如果不好好設(shè)計(jì)的話,達(dá)不到限流的效果。

總結(jié)一下

如果你使用uber-go/ratelimit[5],一定記得:

  • 使用較老的版本v0.2.0

  • 不要設(shè)置slack=0, 默認(rèn)或者設(shè)置一個(gè)非零的值

其實(shí)我從juju/ratelimit切換到uber-go/ratelimit還有一個(gè)根本的原因。juju/ratelimit是基于令牌桶的限流,而uber-go/ratelimit基于漏桶的限流,或者說(shuō)uber-go/ratelimit更像是整形(shaping),更符合我們使用的場(chǎng)景,我們想勻速的發(fā)送數(shù)據(jù)包,不希望有 Burst 或者突然的速率變化,我們的場(chǎng)景更看中的是勻速。

當(dāng)然你也可以使用juju/ratelimit[6],這是 Canonical 公司貢獻(xiàn)的一個(gè)限流庫(kù),版權(quán)是 LGPL 3.0 + 對(duì) Go 更合適的條款,這也是 Canonical 公司統(tǒng)一對(duì)它們的 Go 項(xiàng)目的授權(quán)。它是一個(gè)基于令牌的限流庫(kù),其實(shí)用起來(lái)也可以,不過(guò)已經(jīng) 4 年沒(méi)有代碼更新了。有一點(diǎn)我覺(jué)得不太爽的地方是它初始化就把桶填滿了,導(dǎo)致的結(jié)果就是可能一開始使用這個(gè)桶獲取令牌的速度超出你的預(yù)期,有可能導(dǎo)致一開始就發(fā)包速度很快,然后慢慢的才勻速,這個(gè)不是我想要的效果,但是我又每辦法修改,所以我 fork 了這個(gè)項(xiàng)目smallnest/ratelimit[7],可以在初始化限流器的時(shí)候,可以設(shè)置初始的令牌,比如將初始的令牌設(shè)置為零。

當(dāng)前 Go 官方也提供了一個(gè)擴(kuò)展庫(kù)golang.org/x/time/rate[8], 功能更強(qiáng)大,強(qiáng)大帶來(lái)的負(fù)面效果就是使用起來(lái)比較復(fù)雜,復(fù)雜帶來(lái)的效果就是可能帶來(lái)一些的潛在的錯(cuò)誤,不過(guò)在認(rèn)真評(píng)估和測(cè)試后也是可以使用的。

參考資料

[1]

uber-go/ratelimit: https://github.com/uber-go/ratelimit

[2]

juju/ratelimit: https://github.com/juju/ratelimit

[3]

uber-go/ratelimit: https://github.com/uber-go/ratelimithttps://github.com/uber-go/ratelimit

[4]

issues#44343: https://github.com/golang/go/issues/44343

[5]

uber-go/ratelimit: https://github.com/uber-go/ratelimit

[6]

juju/ratelimit: https://github.com/juju/ratelimit

[7]

smallnest/ratelimit: https://github.com/smallnest/ratelimit

[8]

golang.org/x/time/rate: https://pkg.go.dev/golang.org/x/time/rate

還有一些關(guān)注度不是那么高的第三庫(kù),還包括一些使用滑動(dòng)窗口實(shí)現(xiàn)的限流庫(kù),還有分布式的限流庫(kù),如果你想了解更多請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go?Gin框架路由相關(guān)bug分析

    Go?Gin框架路由相關(guān)bug分析

    這篇文章主要為大家介紹了Go?Gin框架路由相關(guān)bug分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • 詳解如何使用Go語(yǔ)言進(jìn)行文件監(jiān)控和通知

    詳解如何使用Go語(yǔ)言進(jìn)行文件監(jiān)控和通知

    在Go語(yǔ)言中,文件監(jiān)控通常涉及到文件系統(tǒng)事件的監(jiān)聽,文件或目錄的狀態(tài)發(fā)生變化(如創(chuàng)建、刪除、修改等)時(shí),你的程序需要得到通知,所以本文給大家介紹了如何使用Go語(yǔ)言進(jìn)行文件監(jiān)控和通知,需要的朋友可以參考下
    2024-06-06
  • gtoken替換jwt實(shí)現(xiàn)sso登錄的問(wèn)題小結(jié)

    gtoken替換jwt實(shí)現(xiàn)sso登錄的問(wèn)題小結(jié)

    這篇文章主要介紹了gtoken替換jwt實(shí)現(xiàn)sso登錄,主要介紹了替換jwt的原因分析及gtoken的優(yōu)勢(shì),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • Golang中反射的常見用法分享

    Golang中反射的常見用法分享

    本篇文章主要為大家詳細(xì)介紹一些Go語(yǔ)言中常見的反射用法,涵蓋了常見的數(shù)據(jù)類型的反射操作。文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2023-01-01
  • Go官方限流器的用法詳解

    Go官方限流器的用法詳解

    限流器是提升服務(wù)穩(wěn)定性的非常重要的組件,本文主要介紹了Go官方限流器的用法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • go env環(huán)境變量配置的使用

    go env環(huán)境變量配置的使用

    在安裝和使用Go時(shí),必須要正確地配置環(huán)境變量,本文主要介紹了go env環(huán)境變量配置的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • Go語(yǔ)言參數(shù)傳遞是傳值還是傳引用

    Go語(yǔ)言參數(shù)傳遞是傳值還是傳引用

    Go?語(yǔ)言到底是傳值(值傳遞),還是傳引用(引用傳遞)?本文就詳細(xì)介紹一下,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Go語(yǔ)言基礎(chǔ)枚舉的用法及示例詳解

    Go語(yǔ)言基礎(chǔ)枚舉的用法及示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)枚舉的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • 深入解析Go語(yǔ)言中上下文超時(shí)與子進(jìn)程管理

    深入解析Go語(yǔ)言中上下文超時(shí)與子進(jìn)程管理

    這篇文章小編將通過(guò)一個(gè)實(shí)際問(wèn)題的案例,和大家深入探討一下Go語(yǔ)言中的上下文超時(shí)和子進(jìn)程管理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • Golang基礎(chǔ)教程之字符串string實(shí)例詳解

    Golang基礎(chǔ)教程之字符串string實(shí)例詳解

    這篇文章主要給大家介紹了關(guān)于Golang基礎(chǔ)教程之字符串string的相關(guān)資料,需要的朋友可以參考下
    2022-07-07

最新評(píng)論