一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

華為雲開發者社群發表於2020-12-23
摘要:一次由fork引發的時延抖動問題。

背景介紹

華為雲資料庫GaussDB(for Redis) 是一款基於計算儲存分離架構,相容Redis生態的雲原生NoSQL資料庫;它依靠共享儲存池實現了強一致,支援持久化落盤儲存,保證資料的安全可靠。其核心特點是:存算分離、強一致、低成本、超大容量。

GaussDB(for Redis)服務團隊在支撐某客戶業務上雲的過程中,發現一次由fork引發的時延抖動問題,本著對客戶負責任的態度,我們詳細探究了fork這個系統呼叫的效能影響,並且在最新的GaussDB(for Redis)版本已解決了這個抖動問題,清零了內部的fork使用,與原生Redis相比,徹底解決了fork的效能隱患。

問題焦點

  1. 華為雲GaussDB(for Redis) 服務在某客戶上雲線調測過程中發現,系統上量後規律性的出現每5分鐘1次的時延抖動問題。
  2. 華為雲GaussDB(for Redis)團隊經過攻關,最終確認抖動原因是fork導致並解決了這個問題。而fork是開源Redis的一個重要依賴,希望通過本文的分享,能夠幫助大家在使用開源Redis的時候,充分認識fork的影響,從而選擇更優的方案。

問題現象

某客戶業務接入GaussDB(for Redis)壓測發現,每5分鐘系統出現一次規律性的時延抖動:

  1. 正常情況訊息時延在1-3ms,抖動時刻時延達到300ms左右。
  2. 通常是壓測一段時間後開始出現抖動;抖動一旦出現後就非常規律的保持在每5分鐘1次;每次抖動的持續時長在10ms以內。

下圖是從系統慢日誌中捕獲到的發生抖動的訊息樣例(對敏感資訊進行了遮掩):

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

問題分析

1.排查抖動源:

1)由於故障的時間分佈非常規律,首先排除定時任務的影響,主要包括:

  • agent:和管控對接的週期性統計資訊上報任務
  • 核心:執行引擎(Redis協議解析)和儲存引擎(rocksdb)的週期性操作(包括rocskdb統計,wal清理等)

遮蔽上述2類定時任務後,抖動依然存在。

2)排除法未果後,決定回到正向定位的路上來。通過對資料訪問路徑增加分段耗時統計,最終發現抖動時刻記憶體操作(包括allocate、memcpy等)的耗時顯著變長;基本上長出來的時延,都是阻塞在了記憶體操作上。

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

(截圖為相關日誌,單位是微秒)

3)既然定位到是系統級操作的抖動,那麼下一步的思路就是捕獲抖動時刻系統是否有異常。我們採取的方法是,通過指令碼定時抓取top資訊,分析系統變化。運氣比較好,指令碼部署後一下就抓到了一個關鍵資訊:每次在抖動的時刻,系統中會出現一個frm-timer程式;該程式為GaussDB(for Redis)程式的子程式,且為瞬時程式,持續1-2s後退出。

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

4)為了確認該程式的影響,我們又抓取了perf資訊,發現在該程式出現時刻,Kmalloc, memset_sse,memcopy_sse等核心系統呼叫增多。從上述資訊推斷,frm-timer程式應該是被fork出來的,抖動源基本可鎖定在fork frm-timer這個動作上。

2.確定引發抖動的程式碼:

1)分析frm-timer的來歷是下一步的關鍵。因為這個識別符號不在我們的程式碼中,所以就需要拉通給我們提供類庫的兄弟部門聯合分析了。經過大家聯合排查,確認frm-timer是日誌庫liblog中的一個定時器處理執行緒。如果這個執行緒fork了一個匿名的子程式,就會複用父程式的執行緒名,表現為Redis程式建立出1個名為frm-timer的子程式的現象。

2)由於frm-timer負責處理liblog中所有模組的定時器任務,究竟是哪個模組觸發了上述fork?這裡我們採取了一個比較巧妙的方法,我們在定時器處理邏輯中增加了一段程式碼:如果處理耗時超過30ms,則呼叫std:: abort()退出,以生成core棧。

3)通過分析core棧,並結合程式碼排查,最終確認引發抖動的程式碼如下:

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

上述程式碼是用來週期性歸檔日誌的,它每5分鐘會執行1次 system系統呼叫來執行相關指令碼,完成歸檔日誌的操作。而Linux system系統呼叫的原始碼如下,實際上是一個先fork子程式,再呼叫execl的過程。

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

4)分析至此,我們還需要回答最後一個問題:究竟是fork導致的抖動,還是指令碼內容導致的抖動?為此,我們設計了一組測試用例:

  • 用例1:將指令碼內容改為最簡單的echo操作
  • 用例2:在Redis程式裡模擬1個類似frm-timer的執行緒,通過命令觸發該執行緒執行fork操作
  • 用例3:在Redis程式裡模擬1個類似frm-timer的執行緒,通過命令觸發該執行緒執行先fork,再excel的操作
  • 用例4:在Redis程式裡模擬1個類似frm-timer的執行緒,通過命令觸發該執行緒執行system的操作
  • 用例5:在Redis程式裡模擬1個類似frm-timer的執行緒,通過命令觸發該執行緒執行先vfork,再excel的操作

最終的驗證結果:

  • 用例1:有抖動。
  • 用例2:有抖動。
  • 用例3:有抖動。
  • 用例4:有抖動。
  • 用例5:無抖動。

用例1結果表明抖動和指令碼內容無關;用例2、3、4的結果表明呼叫system引發抖動的根因是因為其中執行了fork操作;用例5的結果進一步佐證了抖動的根因就是因為fork操作。最終的故障原因示意圖如下:

一場由fork引發的超時,讓我們重新探討了Redis的抖動問題

3.進一步探究fork的影響:

1)眾所周知,fork是Linux(嚴格說是POSIX介面)建立子程式的系統呼叫,歷史上看,主流觀點大多對其讚譽有加;但近年間隨著技術演進,也陸續出現了反對的聲音:有人認為fork是上個時代遺留的產物,在現代作業系統中已經過時,有很多害處。激進的觀點甚至認為它應該被徹底棄用。(參見附錄1,2)

2)fork當前被詬病的主要問題之一是它的效能。大家對fork通常的理解是其採用copy-on-wirte寫時複製策略,因此對其的效能影響不甚敏感。但實際上,雖然fork時可共享的資料內容不需要複製,但其相關的核心資料結構(包括頁目錄、頁表、vm_area_struc等)的複製開銷也是不容忽視的。附錄1、2中的文章對fork開銷有詳細介紹,我們這回遇到的問題也是一個鮮活的案例:對於Redis這樣的時延敏感型應用,1次fork就可能導致訊息時延出現100倍的抖動,這對於應用來說無疑是不可接受的。

4.原生Redis的fork問題:

4.1 原生Redis同樣被fork問題困擾(參見附錄3,4,5),具體包括如下場景:

1)資料備份

備份時需要生成RDB檔案,因此Redis需要觸發一次fork。

2)主從同步

全量複製場景(包括初次複製或其他堆積嚴重的情況),主節點需要產生RDB檔案來加速同步,同樣需要觸發fork。

3)AOF重寫

當AOF檔案較大,需要合併重寫時,也會產生一次fork。

4.2 上述fork問題對原生Redis的影響如下:

1)業務抖動

原生Redis採用單執行緒架構,如果在電商大促、熱點事件等業務高峰時發生上述fork,會導致Redis阻塞,進而對業務造成雪崩的影響。

2)記憶體利用率只有50%

Fork時子程式需要拷貝父程式的記憶體空間,雖然是COW,但也要預留足夠空間以防不測,因此記憶體利用率只有50%,也使得成本高了一倍。

3)容量規模影響

為減小fork的影響,生產環境上原生Redis單個程式的最大記憶體量,通常控制在5G以內,導致原生Redis例項的容量大大受限,無法支撐海量資料。

解決方法

  1. 修改日誌庫liblog中的週期性歸檔邏輯,不再fork子程式。
  2. 系統排查並整改GaussDB(for Redis)程式碼(包括使用的類庫程式碼)中的fork呼叫。
  3. 最終排查結果,實際只有本次的這個問題點涉及fork。當前修改後即可確保GaussDB(for Redis)的時延保持穩定,不再受fork效能影響。

注:GaussDB(for Redis)由華為雲基於存算分離架構自主開發,因此不存在原生Redis的fork呼叫的場景。

總結

本文通過分析GaussDB(for Redis)的一次由fork引發的時延抖動問題,探究了fork這個系統呼叫的效能影響。最新的GaussDB(for Redis)版本已解決了這個抖動問題,並清零了內部的fork使用,與原生Redis相比,徹底解決了fork的效能隱患。希望通過這個問題的分析,能夠帶給大家一些啟發,方便大家更好的選型。

附:

1.[是時候淘汰對作業系統的 fork() 呼叫了]

https://www.infoq.cn/article/BYGiWI-fxHTNvSohEUNW

2.[Linux fork那些隱藏的開銷]

https://www.mdeditor.tw/pl/29L0

3.[Redis官方文件]

https://redis.io/topics/latency

4.[Redis的一些坑]

https://www.jianshu.com/p/03df6fd516eb

5.[Redis 常見問題之-fork操作]

https://blog.csdn.net/longgeqiaojie304/article/details/89407214

6.[GaussDB(for Redis)官網連結]

https://www.huaweicloud.com/product/gaussdbforredis.html

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章