記錄一次線上服務OOM(內(nèi)存溢出)排查
當前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
OOMOOM是"Out of Memory"的縮寫,意為內(nèi)存不足或內(nèi)存溢出。在計算機科學中,當一個程序嘗試使用比系統(tǒng)可用內(nèi)存更多的內(nèi)存時,就會發(fā)生OOM錯誤。這種情況通常發(fā)生在程序試圖分配內(nèi)存空間,但操作系統(tǒng)無法滿足請求,因為它已經(jīng)沒有足夠的空閑內(nèi)存來提供給該程序。 老實說,在我印象里OOM問題只存在于網(wǎng)上案例中,練習編碼時常兩年半,還是第一次遇到。不過既然遇到了,那就要盡快排查問題并解決掉,不然真要和群里大哥說的一樣:要領盒飯了。 問題下午兩點新版本上線,其中一個消費者服務的內(nèi)存增長速度異常迅速,在短短五分鐘內(nèi)就用完了2G內(nèi)存并自動重啟了pod,之后又在五分鐘內(nèi)OOM了,在四十分鐘內(nèi)服務的pod已經(jīng)重啟了八十幾次,要知道我們之前這個消費者服務正常運行時候只用了不到500M。 分析首先進行初步分析,這是一個消費者服務并且新版本的需求中并沒有新增消費topic,并且業(yè)務量也沒有大的波動,不存在是業(yè)務訪問量驟增導致OOM,所以極大概率會是代碼問題。當然,每一個版本的新代碼都非常多,需求也比較龐雜,直接去看代碼肯定是不行的,這時候就要麻煩部門的運維大佬了,讓他給我們dump一下,給出一個內(nèi)存溢出時的性能記錄文件,通過這個文件可以分析內(nèi)存分配、線程創(chuàng)建、CPU使用、阻塞、程序詳細跟蹤信息等。 我這里使用的Go語言開發(fā),一般用pprof文件進行分析,運維給出的文件有以下6個:
內(nèi)存OOM,那最重要的當然是mem文件,也就是內(nèi)存分配剖析數(shù)據(jù),不過很不幸,服務重啟速度太快了,運維大佬dump的時候正好處于服務剛重啟的時候,所以mem文件中顯示的內(nèi)存才占用不到20M,并且占比上也沒看出有什么問題。想讓運維再幫忙dump一下內(nèi)存快要OOM的時候,但是為了線上服務的穩(wěn)定性版本已經(jīng)回退了,無法重新dump,只能從其他幾個文件中查找問題了。 除了內(nèi)存占用分析,在性能問題分析中CPU占用分析也是極為重要的一環(huán),這一查看就有意思了,CPU總的使用率雖然不高,但是這個占比就比較奇怪了。第一占比的 之后繼續(xù)查看互斥鎖的情況,其實這個文件在目前這種情況下排查的價值已經(jīng)不大了,因為出現(xiàn)問題的是內(nèi)存溢出而不是CPU占用率,并且CPU占用率確實不是很高,而且Go中是有檢索死鎖的機制,大部分死鎖是能夠被Go發(fā)現(xiàn)并報一個 接下來查看阻塞操作的分析情況,從解析結(jié)果中可以看出,select的阻塞時間遙遙領先,select出現(xiàn)這種情況只會是存在case但是沒有default的時候,當所有case不符合的時候,負責這個select的goroutine會阻塞住直到存在符合的case出現(xiàn)才會喚醒繼續(xù)走下去,當時我看到這我滿腦子問號,誰家好人select不加default??? 再查看線程創(chuàng)建情況,由于pod剛啟動不久,所以這個文件也看不出什么東西,很正常的線程創(chuàng)建。 看到這里還是沒能定位到問題所在,但是別急,我們還有最重要的文件還沒看,那就是trace文件,它可以記錄程序執(zhí)行的詳細跟蹤信息,包括函數(shù)調(diào)用、Goroutine 的創(chuàng)建和調(diào)度,使用go自帶的pprof分析工具打開trace文件
會出現(xiàn)以下本地頁面: 在Goroutine分析中,可以鎖定真正的問題所在了,在go-zero的core包下的collection文件在不到一秒內(nèi)創(chuàng)建了兩萬多的Goroutine,雖然兩萬多數(shù)量不多,但是這個速度十分異常,最重要的是這個定時輪就很奇怪,這個項目中根本沒有定時任務,接下來就很容易查詢了,只要查找這次提交的代碼中哪里使用到了collection包。 經(jīng)過一番全局搜索后,最終確定了問題代碼:
在新上線的版本中只有這一處用到了collection包,原本這里的意思是將建立一個緩存放到上下文中去傳遞,但是乍一看我沒有看出有什么問題,過期時間也設置了,按照我原有理解過期時間到了就會自動釋放掉,為什么還是會內(nèi)存溢出了?但是我忽然意識到應該不是緩存引發(fā)的內(nèi)存溢出,可能是協(xié)程過多引發(fā)的內(nèi)存溢出,因為一個初始協(xié)程是2KB左右,如果數(shù)量過多也會造成內(nèi)存不夠。 為了探究根本原因,我點進了collection包的源碼進行查看,在其中 之后點進去這個方法進一步查看,可以看到這個定時輪的結(jié)構(gòu)體,里面包含了四個channel以及一些其他數(shù)據(jù)結(jié)構(gòu),粗略估計這一個 但是這個定時輪為什么會創(chuàng)建那么多呢?為什么不會關閉,按理說go-zero的源碼不應該會有這么大的漏洞,繼續(xù)查看這個定時輪的 答案是會在程序停止運行的時候進行調(diào)用。所以如果程序仍在運行,就會有無限制的協(xié)程創(chuàng)建定時輪,這時候定時輪因為無法關閉所以協(xié)程也不會進行銷毀,有點類似于守護線程,所以在協(xié)程無限制的創(chuàng)建下最終導致了線上內(nèi)存OOM了。 解決那是不是說明go-zero的這塊源碼存在問題?其實不是的,是我們使用方法錯誤,正確的使用方法不應該將緩存創(chuàng)建在上下文中,而應該創(chuàng)建一個全局緩存,讓所有的上下文都公用這一個緩存,這樣就不會發(fā)生定時輪無限創(chuàng)建的問題。 后續(xù)將這塊緩存放到了全局中,之后再重新發(fā)布觀察了一小時左右,服務內(nèi)存穩(wěn)定在了500M以下,與以往服務消耗內(nèi)存幾乎一致。 歷時兩小時戰(zhàn)斗,終于免于“領盒飯”了。 下班! ?轉(zhuǎn)自https://www.cnblogs.com/lemondu/p/18652339 該文章在 2025/1/10 8:56:26 編輯過 |
關鍵字查詢
相關文章
正在查詢... |