欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang限流庫兩個大bug(半年之久無人提起)

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

uber-go/ratelimit 庫

我先前都是使用juju/ratelimit[2]這個限流庫的,不過我不太喜歡這個庫的復雜的“構造函數(shù)”,后來嘗試了uber-go/ratelimit[3]這個庫后,感覺 SDK 設計比較簡單,而且使用起來也不錯,就一直使用了。當時的版本是v0.2.0,而且我也不會設置它的slack參數(shù),所以也相安無事。

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

通過單元測試復現(xiàn)

很容易通過下面一個單元測試復現(xià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)問題

這個單元測試嘗試在第二個周期中不調用限流器,讓它有機會進入 slack 判斷的邏輯。這個庫的 slack 設計的本意是在 rate 的基礎上留一點余地,不那么嚴格按照 rate 進行限流,不過因為v0.3.0代碼的問題,導致 slack 的判斷邏輯出現(xià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)
}

原理分析

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

接下來再調用 Take,當前時間是 now2,now2 總是會比 now1 大一點,至少大幾納秒吧。這個時候我們計算分支的條件now-timeOfNextPermissionIssue > int64(t.maxSlack),這個條件肯定是成立的,因為now2-(now1-int64(t.maxSlack)) = (now2-now1) + int64(t.maxSlack) > int64(t.maxSlack)。導致后續(xù)的每次 Take 都會進入這個分支,不會阻塞,導致程序瘋狂發(fā)包,最終導致 panic。

周末的時候我給這個項目提了一個 bug, 它的一個維護者進行了修復,不過這個項目主要開發(fā)者已經(jīng)對這個v0.3.0的實現(xiàn)喪失了信心,因為這個實現(xiàn)已經(jīng)出現(xiàn)過一次類似的 bug,被他回滾后了,后來有被修復才合進來,現(xiàn)在有出現(xiàn) bug 了。

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

這個其中的一個大 bug。

其實我們對 slack 的有無不是那么關心的,那么我們使用ratelimit.WithoutSlack這個選項,把 slack 設置為 0,是不是就沒問題了呢?

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

我們把限流的速率修改為5000,結果在 Linux 測試機器上跑只能跑到接近2000,遠遠小于預期,那這還咋限流,流根本打不上去。

我的同事說把ratelimit版本降到v0.2.0,同時不要設置slack=0可以解決這個問題。

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

我們使用time.Sleep 休眠 50 微秒的話,在 Go 1.16 之前,Linux 機器上基本上實際會休眠 80、90 微秒,但是在 Go 1.16 之后,Linux 機器上 1 毫秒,差距巨大,在 Windows 機器上,Go 1.16 之前是 1 毫秒,之后是 14 毫秒,差距也是巨大的。我在蘋果的 MacPro M1 的機器測試,就沒有這個問題。

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

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

總結一下

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

  • 使用較老的版本v0.2.0

  • 不要設置slack=0, 默認或者設置一個非零的值

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

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

當前 Go 官方也提供了一個擴展庫golang.org/x/time/rate[8], 功能更強大,強大帶來的負面效果就是使用起來比較復雜,復雜帶來的效果就是可能帶來一些的潛在的錯誤,不過在認真評估和測試后也是可以使用的。

參考資料

[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

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

相關文章

  • Go?Gin框架路由相關bug分析

    Go?Gin框架路由相關bug分析

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

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

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

    gtoken替換jwt實現(xiàn)sso登錄的問題小結

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

    Golang中反射的常見用法分享

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

    Go官方限流器的用法詳解

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

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

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

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

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

    Go語言基礎枚舉的用法及示例詳解

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

    深入解析Go語言中上下文超時與子進程管理

    這篇文章小編將通過一個實際問題的案例,和大家深入探討一下Go語言中的上下文超時和子進程管理,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-10-10
  • Golang基礎教程之字符串string實例詳解

    Golang基礎教程之字符串string實例詳解

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

最新評論