使用多年的 go pprof 檢查記憶體洩漏的方法居然是錯的?!

originator發表於2020-04-05

最近在做一個 Redis 的 Proxy 的專案,其中利用 Redis 6.0 新加的 tracking 功能實現客戶端快取的功能,可以為某些特定的 redis 使用場景提高吞吐和延遲。

當然,cache 的實現也是有代價的。首先,cache 的大小不能無限制的大,否則總有一點會把記憶體撐爆的;其次,cache 的淘汰演算法有多種方式,LRU、LFU 等等,具體可以參考 Cache replacement policies,不同的場景下各種淘汰演算法的效果是不一樣的;第三,對於大併發情況實現 cache 是有代價的,因為併發情況下對 cache 的訪問需要加鎖,而加鎖就意味著有效能的損失。

我在實現這個 cache 的過程中稍微偷了一下懶, 想盡量的減少鎖的 scope,結果導致記憶體洩漏的問題。本來 cache 佔用的最大記憶體我設定為 10GB, 結果過了個週末發現程式已經佔用了 80GB 的記憶體了。

當然本文不是要介紹這個專案的記憶體洩漏原因,而是介紹一下 Go pprof 工具查詢記憶體洩漏的一個不太常用的方法。

檢查 Go 程式記憶體的使用情況最常用的就是 Go 標準庫自帶的 pprof 庫了,可以通過 http 暴露出這個 profile, 然後通過go tool pprof或者pprof工具命令列/web方式檢視。

比如下面的命令, 可以獲取伺服器http://ip:port的堆資訊,並且在本機 9090 埠啟動一個伺服器展示堆的資訊。

go tool pprof -http :9090 http://ip:port/debug/pprof/heap

在堆資訊中你可以檢視分配的堆的大小和物件數量,或者當前沒有釋放的佔用的堆的大小和物件數量。

正常情況下使用這個方式就可以比較直觀的看到哪一段程式碼分配的記憶體比較多,然後確定那裡容易產生記憶體洩漏。

但是, 分配堆記憶體比較多的地方並不一定產生記憶體洩漏,只能說明這個地方"曾經/正在"分配的堆記憶體比較大,或者分配的堆記憶體比較頻繁俄安,這些分配的記憶體可能在之後就回收掉了。

像 Java 的一些 profiler 工具一樣, pprof 也可以比較兩個時間點的分配的記憶體的差值,通過比較差值,就容易看到哪些地方產生的記憶體"殘留"的比較多,沒有被記憶體釋放,極有可能是記憶體洩漏的點。

你可以通過下面的方式產生兩個時間點的堆的 profile,之後使用 pprof 工具進行分析。

  1. 首先確保你已經配置了 pprof 的 http 路徑, 可以訪問http://ip:port/debug/pprof/檢視 (如果你沒有修改預設的 pprof 路徑)
  2. 匯出時間點 1 的堆的profile: curl -s http://127.0.0.1:8080/debug/pprof/heap > base.heap, 我們把它作為基準點
  3. 喝杯茶,等待一段時間後匯出時間點 2 的堆的profile: curl -s http://127.0.0.1:8080/debug/pprof/heap > current.heap
  4. 現在你就可以比較這兩個時間點的堆的差異了: go tool pprof --base base.heap current.heap

操作和正常的go tool pprof操作一樣, 比如使用 top 檢視使用堆記憶體最多的幾處地方的記憶體增刪情況:

使用web命令會生成一個 SVG 檔案,可能你需要使用瀏覽器開啟它。

或者你直接使用命令開啟 web 介面: go tool pprof --http :9090 --base base.heap current.heap

原文地址:https://colobu.com/2019/08/20/use-pprof-to-compare-go-memory-usage/

更多原創文章乾貨分享,請關注公眾號
  • 使用多年的 go pprof 檢查記憶體洩漏的方法居然是錯的?!
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章