解決golang內存泄漏

最近在項目中出現golang內存溢出的問題,master剛開始運行時只有10多M,運行幾天後,居然達到了10多個G。並且到凌晨流量變少內存也沒有明顯下降,內存狀態呈現一種很不健康的曲線。golang

clipboard.png

clipboard.png

像這種狀況確定是golang內存溢出了,爲此我持續排查了兩天,終於找到問題所在,特此記錄下。web

準備工做

  • 一臺較好的環境測試機,單臺運行無污染。
  • 壓測工具,不管服務是http仍是websocket服務,都必須準備好壓測工具模擬最真實的用戶場景。
  • 將master引入net/http/pprof包,經過http訪問得到goroutine、heap信息。瀏覽器

    //引入pprof
    import _"net/http/pprof"
    //在main中加入
    go func() {
        log.Println(http.ListenAndServe("localhost:9999", nil))
    }()

    瀏覽器訪問: http://127.0.0.1:9999/debug/pprof/
    clipboard.png
    獲取goroutine信息 http://10.13.132.91:9999/debug/pprof/goroutine?debug=2
    獲取heap信息 http://10.13.132.91:9999/debug/pprof/heap?debug=2
    使用golang tool進行統計分析,go tool pprof -inuse_space http://127.0.0.1:9999/debug/pprof/heap。輸入top10能夠看出前十佔用內存狀況,這裏我是直接輸入png導出圖片來查看,以便之後比較。還有兩個參數能夠選擇,-inuse_space顧名思義是正在使用的內存,-alloc_space是已經分配的內存,本次我是一直用-inuse_space進行分析。websocket

開始進行分析

go是一門本身gc的語言,大概兩分鐘會gc一次。若是有內存泄漏,無非就是兩種狀況。數據結構

  • 有goroutine泄漏,goroutine「飛」了,zombie goroutine沒有結束,這個時候在這個goroutine上分配的內存對象將一直被這個殭屍goroutine引用着,進而致使gc沒法回收這類對象,內存泄漏。
  • 有一些全局(或者生命週期和程序自己運行週期同樣長的)的數據結構意外的掛住了本該釋放的對象,雖然goroutine已經退出了,可是這些對象並無從這類數據結構中刪除,致使對象一直被引用,沒法被回收。

排除掉goroutine泄漏

首先,我利用壓測工具對server進行100個websocket鏈接,模擬用戶瀏覽行爲,而後關閉鏈接。打開瀏覽器查看goroutine數量,發現新起的goroutine所有已經銷燬,沒有觀察到有泄漏的goroutine,所以排除此狀況。socket

肯定是全局變量無回收

排除goroutine泄漏,只能是由全局狀態變量引發的。再次用壓測工具進行壓測而後關閉,使用觀察內存狀況。使用go tool pprof -inuse_space http://127.0.0.1:9999/debug/pprof/heap輸入png導出(在這種狀況下,須要等程序gc完再導出,建議等10分鐘左右。)
clipboard.png
發現問題所在
每次都會遺留這麼大概0.5M的內存空間出來,就奇怪,明明整個goroutine退出爲何還有會內存佔用?相應的全局變量也會刪除該地方的引用。等一下,全局變量,難道是刪除的時候沒作好配對致使沒有真正刪除該引用嗎?去查了下代碼,果真是沒有刪除引用致使的,至此問題解決。
clipboard.png
這裏面有個項目的坑,上報日誌的key不是根據這個len(map)計算出,致使上報日誌的時候覺得刪除了該key。工具

後記

爲何會花了兩天時間,看起來上述流程並不複雜。測試

  • 實際上你要徹底排除掉goroutine泄漏須要花較長的時間去對比的,查看哪些goroutine是新起來沒有關閉。
  • 在使用-inuse_space或者-alloc_space分析,也是很糾結,這些看起來也並不徹底與表現對應上。實際上用-inuse_space是較爲直觀的,能夠展示出程序真正在使用的(RSS)。Go 管理內存的方式可能與你之前使用的方式不太同樣。它會在一開始就保留一大塊 VIRT,而 RSS 與實際內存用量接近。RSS 和 VIRT 之間有什麼區別呢?VIRT 或者虛擬地址空間大小是程序映射並能夠訪問的內存數量。RSS 或者常駐大小是實際使用的內存數量。所以用-inuse_space導出在png圖上的統計中,與top上的res值是大體相同。clipboard.png
  • 還有就是每次作壓測或者等待golang 徹底gc都要耗費很多時間,這樣也會排查增長難度。
相關文章
相關標籤/搜索