go pprof:一次成功的定位與失敗的復現

背景: 一次大幾萬人的線上搶購活動,突然出現了問題,頁面半天打不開,打開了半天下不了單,cpu漲了又跌跌了又漲,而內存使用又穩如老狗!不要慌,按照套路去分析問題,一切都不是問題!

閱讀此文你將收穫:

  • 分析問題的一個思路!
  • 學會使用pprof定位問題。
  • 解決問題的一個思路!
大綱:
  1. 我是如何定位問題的
  2. 如何通過pprof精準定位
  3. 通過pprof來定位代碼
  4. 我是如何trouble shooting的

一. 我是如何思考問題的

“活動掛了,下不了單!”,隨着一聲淒涼的慘叫,辦公室大門被運營人員打開,於是活動團隊開始了緊張的bug定位過程。通過一段時間的代碼查看未能定位問題,重啓也沒法解決。

通過finalshell上的機器使用率顯示,我們發現了一個有趣的現象,CPU的使用率從30%漲到60%再漲到99%,然後又從10%開始一路往上漲,如此往復,但是內存的使用率卻一動不動,非常穩定。

爲什麼CPU會暴漲

CPU爲什麼這麼奇怪?CPU是幹什麼的?
CPU是負責計算的!

那麼我們來猜測一下導致cpu暴漲的原因:

  1. 是某段代碼涉及計算量過大?
  2. 是小對象太多?導致GC壓力過大?

然後導致cpu資源佔用過高,在高併發環境下請求積壓越來越多?處理不了?

有了初步推測,下一步就該用出golang性能分析大殺器—pprof!

二. 如何用proof精準定位

很多小夥伴擔心線上使用pprof會影響性能,擔心安全問題。這個在我看來利大於弊,當服務出現問題的時候,資源佔用多一點點與能夠解決問題相比微不足道,當服務沒有問題的時候使用pprof那更沒有問題了~

在這裏要推薦來自鵝廠大佬陳一梟在深圳gopher meetup上的分享:

《Go性能優化之路》點擊進入GitHub下載PDF文檔與示例demo
重點如下:
基準—benchmark的使用
分析工具:GODEBUG
分析工具:go tool pprof
分析工具:go tool trace
PS:已經與鳥哥溝通過,獲得使用許可

2.1 CPU Profile的使用

1.先看Graph圖

點擊view,選中graph
該圖展示了函數邏輯調用樹,框越紅,越大表示消耗越多!

直接將圖縮到最小
在該步驟中,我們直接將graph圖縮到整個屏幕可見,哪裏紅線明顯,哪裏框框最大,一目瞭然

通過縮略圖我們標記了四個消耗量大的點位。我們再繼續看放大圖。

標記點1

標記2和標記3

標記4

選擇sample採樣

2.再看flame圖

flame圖SAMPLE中的cpu

  • 火焰圖中的X軸表示CPU耗時,越寬佔用時間越多
  • Y軸表示函數棧調用深度,尖刺越高表示函數棧調用越深

flame選中samples
我們可以看到其實採樣SAMPLE中選擇cpu或者samples都差不多,消耗越大的地方cpu佔用越高,採樣點也是越集中在這裏!

3.再看TOP

VIEW中選擇Top

  • Flat:函數自身運行耗時
  • Flat%:函數自身耗時比例
  • Sum%:指的就是每一行的flat%與上面所有行的flat%總和
  • Cum:當前函數加上它之上的調用運行總耗時
  • Cum%:當前函數加上它之上的調用運行總耗時比例

**舉例說明:**函數b由三部分組成:調用函數c、自己直接處理一些事情、調用函數d,其中調用函數c耗時1秒,自己直接處理事情耗時3秒,調用函數d耗時2秒,那麼函數b的flat耗時就是3秒,cum耗時就是6秒。

// 該示例在文末參考列表的博客中
func b() {
    c() // takes 1s
    do something directly // takes 3s
    d() // takes 2s
}
4.看看內存Profile

Graph_inuse_object

  • alloc_objects:收集自程序啓動以來,累計的分配對象數
  • alloc_space:收集自程序啓動以來,累計的分配空間
  • inuse_objects:收集實時的正在使用的分配對象數
  • inuse_space:收集實時的正在使用的分配空間

如圖顯示這兩個地方使用對象最多,分別佔比53.10%與26.63%,二者相加等於79.73%。
GC收集的就是內存中的小對象,而這裏我們所見的UnmarshalJSON與json compact所產生的對象佔了80%,這裏可以列入優化點!

三.通過pprof的定位來追代碼

通過pprof中CPU與內存的Graph、Flame Graph和Top,我們基本清楚了程序性能消耗大戶就在json.Unmarshal這一塊。下面我們通過針對第一個標記點的分析,來示例如何查找問題代碼的。
問題代碼函數調用鏈
從上圖可以分析出來是api.GetGiftCategoryDetails這個方法消耗了太多性能,因爲往下走就是redis的HGetObject和json的Unmarshal方法,這些方法屬於不可控方法,不能直接對其進行修改,所以定位就定位在api.GetGiftCategoryDetails這個方法上!

func GetGiftCategoryDetails上圖爲pprof中標記1的主要方法,pprof cpu顯示,該方法消耗了大量的CPU時間。
該方法被調用的時候會判斷in.Giftcategoryid 是否有值,有值則從redis中取出數據。
進入HGetObject方法,如下圖:

HGetObject
繼續進入decode方法!

decode
在該方法中我們看到了p.Option.Unmarshal,這個跟我們在pprof中看到的json Unmarshal是什麼關係呢?
進入p.Option.Unmarshal中查看。

Unmarshal

到這裏就明白了默認使用的是json.Unmarshal反序列化方法

那麼我們從pprof中所觀察到的一切都能夠串聯起來了,整個邏輯流程如下:
調用流程

文章看到這裏,在回頭看看pprof的CPU還有其他的各種截圖,結合代碼,整個流程清晰明瞭,就是從redis中取出數據的時候進行的json.Unmarshal損耗CPU性能太多!

四. 我是如何trouble shooting的

既然我們知道了是json反序列化的問題導致這次線上事故的產生,那麼這個問題我們該如何解決呢?
小case

這個很容易想到,既然標準庫中的json序列化效率不高,咱們換個高效率的不就行了嗎?例如:https://github.com/json-iterator/go

但是,換了高效的json序列化包,那麼效率到底能夠提升多少呢?30%?50%,100%,三倍?五倍?十倍?···

我的看法是:脫離業務談技術的都是耍流氓!

事情並不簡單

在不清楚業務的情況下,任何解決方案都只是猜測而已,因爲最高效的手段永遠都是從業務上去解決,然後再是技術手段。

通過與活動團隊溝通,瞭解到業務邏輯如下:

  1. 近百萬用戶分爲三個類別。
  2. 每個類別用戶進入都會取出不同的商品列表。
  3. 商品列表存redis中。
  4. 每次從redis中取下來後反序列化返回給用戶端。

那麼看完了整個業務流程,應該怎麼去做呢?
咱們不妨從下面兩個角度想一想:

  • 技術手段
  • 業務手段

幾萬個用戶幾乎同時取redis中取三種相同的臃腫的數據,然後還需要經過json反序列化去消耗大量的CPU,這樣做是否合理?

如果你覺得這樣不合理,那咱們換一個思路:
如果我們將這三類商品列表放在全局變量中,每次來了直接從全局變量中獲取這個方法怎麼樣?

來,咱們算一算兩種方式的開銷如何:

  1. redis走網絡開銷至少ms級,走內存ns級,這裏省了有沒有十萬或者八萬倍
  2. 不需要經過json序列化···掐指一算,省了···(不好意思,程序就卡死在這裏,這裏還有算的必要嗎?)

我們反思覆盤一下,要是我們不考慮業務直接換json庫換上目前性能最高的json庫,那麼下次活動結果會如何?(心裏知道就行了)

總結:

1.談一談基礎

起碼得知道CPU是計算資源,查看CPU使用率和負載,當CPU使用率低,負載高是個什麼情況。

又例如服務OOM了得考慮是不是內存泄漏了,當內存泄漏的時候,操作系統殺的一般是佔用內存最大的而不是泄露的···

2.瞭解分析工具的使用

常用的性能分析工具要掌握,pprof肯定不用說,還有一些Linux命令例如top,uptime,還有查看TCP連接數的等等命令。

3.該如何解決問題

首先得分析問題,是CPU問題還是內存問題,又或者是網絡問題。當三者都沒問題的時候,請你壓一壓是不是自己程序性能有問題···

當能夠充分定位問題的時候,首先得梳理清楚業務流程,因爲一般我們用的包或者標準庫,亦或是框架,他們的性能相差其實也沒有大到很離譜,除非你故意挑個玩具代碼來應用到生產環境。

先確認業務流程和程序處理上已經沒有優化的空間,請再考慮尋找一個高效的庫,或者自己去實現一些代碼優化措施!

PS:該業務不是我負責的,純屬同事之間友情互助,幫忙查找問題。至於後來我也模擬過同樣的數據,利用time.sleep(5ms),然後反序列化,但是並未出現CPU使用率波浪式呈現。太遺憾了,要是有知道的大佬還望不吝賜教!

感謝 阿郎,孫偉,陳一梟等大佬提供的幫助

參考資料與推薦閱讀文章:
滴滴實戰分享:通過 profiling 定位 golang 性能問題 - 內存篇
Go語言性能優化工具pprof文本輸出的含義
linux下查看cpu負載及分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章