PuGo 一次記憶體泄露的調優

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

我剛剛寫好新的部落格程式 Pugo,歡迎試用和體驗。這兩天我把個站 fuxiaohei.me 遷移到新的部落格程式。但是,經過一天的運行,發現記憶體從啟動的 14MB 上升到了 228 MB。顯然程式發生記憶體泄露,所以也開始以下調優過程。

PPROF

pprof 是 Golang 內建的調試工具,有很多可用的工具。pprof 的調試方式有代碼的方式和 HTTP 方式。其中 HTTP 調試比較方便,加入很簡單的代碼:

import _ "net/http/pprof" // pprof 的 http 路由註冊在內建路由上go func() {    http.ListenAndServe("0.0.0.0:6060", nil) // 啟動預設的 http 服務,可以使用內建的路由}()

訪問 http://localhost:6060/debug/pprof/ 就可以查看 pprof 提供的資訊。分析記憶體使用量,可以關注 heap 上分配的變數都是哪些內容,訪問 http://localhost:6060/debug/pprof/heap?debug=1 可以看到的資料:

來自代碼 github.com/syndtr/goleveldb/leveldb/memdb.New 的對象在 heap 上最多。 Pugo 的資料庫底層是 基於 goleveldb 儲存的 tidb 資料庫。 goleveldb 有類似於 leveldb 的行為,就是半記憶體半儲存的資料分布。因而,有比較大量的記憶體對象是正常現象。但是使用 go tool 的時候發現了別的問題:

go tool pprof http://localhost:6060/debug/pprof/heap

go tool 暫存下當時的 heap 快照用於分析。同時進入了 pprof 工具,用命令:

top -10

展示佔用最多的 10 個對象堆

reflect.Value.call 是 heap 上最多的調用,呵呵。問題落在標準庫上,可能就是 golang 標準庫的問題。我本機還是 Go 1.5版本。試著更新了一下 Go 1.5.1 後,發現 heap 上的資料分布沒有什麼變化。那就不是標準庫的問題。

深入分析

既然不是標準庫的問題,就是調用reflect.Value.call的上級出現問題。用命令產生 svg 過程圖到瀏覽器:

web

時序圖中明顯有問題的部分:

發現 tango.(*Context).Next 是調度的上級。但是 Next() 方法源碼中沒有 reflect 的調用過程,不夠明確。用另一個命令輔助:

peek reflect.Value.Call

有圖:

可以看到上下文方法 tango.(*Context).Invoke,代碼中發現:

if ctx.action != nil {    var ret []reflect.Value    switch fn := ctx.route.raw.(type) {    case func(*Context):        fn(ctx)    case func(*http.Request, http.ResponseWriter):        fn(ctx.req, ctx.ResponseWriter)    case func():        fn()    case func(*http.Request):        fn(ctx.req)    case func(http.ResponseWriter):        fn(ctx.ResponseWriter)    default:        ret = ctx.route.method.Call(ctx.callArgs) // 調用 reflect.Value.Call 的地方    }    if len(ret) == 1 {        ctx.Result = ret[0].Interface()    } else if len(ret) == 2 {        if code, ok := ret[0].Interface().(int); ok {            ctx.Result = &StatusResult{code, ret[1].Interface()}        }    }    // not route matched} else {    if !ctx.Written() {        ctx.NotFound()    }}

把這個位置反饋給tango的作者 lunny 後,最終定位的問題在 router 池的構造方法:

func newPool(size int, tp reflect.Type) *pool {    return &pool{        size: size,        cur:  0,        pool: reflect.MakeSlice(reflect.SliceOf(tp), size, size), // 這個地方申請了大記憶體        tp:   reflect.SliceOf(tp),        stp:  tp,    }}

reflect.MakeSlicesize 預設值是 800, 也就是創造了存有一個長度800的slice的pool,記憶體一直在不停增長。然後 pool 中存有的 reflect.Value 一直被調用,所以 heap 可以看到調度資訊。修改 size 預設值到 10 左右,一切就正常啦。

總結

Golang 本身提供的 profile 工具很好,可以提供很多的資訊。然後,我經過對代碼的分析和追蹤,發現問題所在。調試和最佳化是工作中經常遇到的事情。每一次分析過程都為自己積累了思考的方式和修改的經驗。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.