摘要:一次由fork引起的時延抖動問題。
華爲雲數據庫GaussDB(for Redis) 是一款基於計算存儲分離架構,兼容Redis生態的雲原生NoSQL數據庫;它依靠共享存儲池實現了強一致,支持持久化落盤存儲,保證數據的安全可靠。其核心特色是:存算分離、強一致、低成本、超大容量。html
GaussDB(for Redis)服務團隊在支撐某客戶業務上雲的過程當中,發現一次由fork引起的時延抖動問題,本着對客戶負責任的態度,咱們詳細探究了fork這個系統調用的性能影響,而且在最新的GaussDB(for Redis)版本已解決了這個抖動問題,清零了內部的fork使用,與原生Redis相比,完全解決了fork的性能隱患。redis
某客戶業務接入GaussDB(for Redis)壓測發現,每5分鐘系統出現一次規律性的時延抖動:數據庫
下圖是從系統慢日誌中捕獲到的發生抖動的消息樣例(對敏感信息進行了遮掩):segmentfault
1)因爲故障的時間分佈很是規律,首先排除定時任務的影響,主要包括:安全
屏蔽上述2類定時任務後,抖動依然存在。數據結構
2)排除法未果後,決定回到正向定位的路上來。經過對數據訪問路徑增長分段耗時統計,最終發現抖動時刻內存操做(包括allocate、memcpy等)的耗時顯著變長;基本上長出來的時延,都是阻塞在了內存操做上。架構
(截圖爲相關日誌,單位是微秒)性能
3)既然定位到是系統級操做的抖動,那麼下一步的思路就是捕獲抖動時刻系統是否有異常。咱們採起的方法是,經過腳本定時抓取top信息,分析系統變化。運氣比較好,腳本部署後一下就抓到了一個關鍵信息:每次在抖動的時刻,系統中會出現一個frm-timer進程;該進程爲GaussDB(for Redis)進程的子進程,且爲瞬時進程,持續1-2s後退出。測試
4)爲了確認該進程的影響,咱們又抓取了perf信息,發如今該進程出現時刻,Kmalloc, memset_sse,memcopy_sse等內核系統調用增多。從上述信息推斷,frm-timer進程應該是被fork出來的,抖動源基本可鎖定在fork frm-timer這個動做上。spa
1)分析frm-timer的來歷是下一步的關鍵。由於這個標識符不在咱們的代碼中,因此就須要拉通給咱們提供類庫的兄弟部門聯合分析了。通過你們聯合排查,確認frm-timer是日誌庫liblog中的一個定時器處理線程。若是這個線程fork了一個匿名的子進程,就會複用父進程的線程名,表現爲Redis進程建立出1個名爲frm-timer的子進程的現象。
2)因爲frm-timer負責處理liblog中全部模塊的定時器任務,到底是哪一個模塊觸發了上述fork?這裏咱們採起了一個比較巧妙的方法,咱們在定時器處理邏輯中增長了一段代碼:若是處理耗時超過30ms,則調用std:: abort()退出,以生成core棧。
3)經過分析core棧,並結合代碼排查,最終確認引起抖動的代碼以下:
上述代碼是用來週期性歸檔日誌的,它每5分鐘會執行1次 system系統調用來運行相關腳本,完成歸檔日誌的操做。而Linux system系統調用的源碼以下,其實是一個先fork子進程,再調用execl的過程。
4)分析至此,咱們還須要回答最後一個問題:到底是fork致使的抖動,仍是腳本內容致使的抖動?爲此,咱們設計了一組測試用例:
最終的驗證結果:
用例1結果代表抖動和腳本內容無關;用例二、三、4的結果代表調用system引起抖動的根因是由於其中執行了fork操做;用例5的結果進一步佐證了抖動的根因就是由於fork操做。最終的故障緣由示意圖以下:
1)衆所周知,fork是Linux(嚴格說是POSIX接口)建立子進程的系統調用,歷史上看,主流觀點大多對其讚譽有加;但近年間隨着技術演進,也陸續出現了反對的聲音:有人認爲fork是上個時代遺留的產物,在現代操做系統中已通過時,有不少害處。激進的觀點甚至認爲它應該被完全棄用。(參見附錄1,2)
2)fork當前被詬病的主要問題之一是它的性能。你們對fork一般的理解是其採用copy-on-wirte寫時複製策略,所以對其的性能影響不甚敏感。但實際上,雖然fork時可共享的數據內容不須要複製,但其相關的內核數據結構(包括頁目錄、頁表、vm_area_struc等)的複製開銷也是不容忽視的。附錄一、2中的文章對fork開銷有詳細介紹,咱們這回遇到的問題也是一個鮮活的案例:對於Redis這樣的時延敏感型應用,1次fork就可能致使消息時延出現100倍的抖動,這對於應用來講無疑是不可接受的。
1)數據備份
備份時須要生成RDB文件,所以Redis須要觸發一次fork。
2)主從同步
全量複製場景(包括初次複製或其餘堆積嚴重的狀況),主節點須要產生RDB文件來加速同步,一樣須要觸發fork。
3)AOF重寫
當AOF文件較大,須要合併重寫時,也會產生一次fork。
1)業務抖動
原生Redis採用單線程架構,若是在電商大促、熱點事件等業務高峯時發生上述fork,會致使Redis阻塞,進而對業務形成雪崩的影響。
2)內存利用率只有50%
Fork時子進程須要拷貝父進程的內存空間,雖然是COW,但也要預留足夠空間以防不測,所以內存利用率只有50%,也使得成本高了一倍。
3)容量規模影響
爲減少fork的影響,生產環境上原生Redis單個進程的最大內存量,一般控制在5G之內,致使原生Redis實例的容量大大受限,沒法支撐海量數據。
注: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
本文做者:華爲雲數據庫GaussDB(for Redis)團隊