記一次內存泄漏問題的排查經歷

原文鏈接:https://mp.weixin.qq.com/s?__biz=Mzg4NzEyNDQxNw==&mid=2247483912&idx=1&sn=cf4b5c3566a2bcf95c8758faf9dbecf2&chksm=cf8e7aa6f8f9f3b01923c29e5a71cebe2f6bfa0825b5555f98cea34f1e7ddcda69b37b42b8aa&mpshare=1&scene=23&srcid=&sharer_sharetime=1571052685625&sharer_s

源寶導讀:隨着系統越來越龐大,越來越複雜,疑難雜症問題也越來越多。本文將介紹明源研發協同平臺團隊針對一次內存泄露問題的排查過程和技術探索。

一、背景

    內存泄漏,一個說大不大說下不小的瑕疵。作爲開發者,我們都很清楚內存泄漏是我們代碼問題導致的。但是話說回來,泄漏後果會很嚴重嘛?這不好說,如果對服務器內存的影響只有幾個百分點,又或者對應用沒有什麼致命影響,那麼修補內存泄漏就像雞肋一樣,“食之無味,棄之可惜”。反之內存泄漏也可能導致服務不可用,嚴重影響用戶體驗。

二、問題

    19年年初,協同平臺後臺服務開始間歇性非正常重啓,每次重啓耗時2-5分鐘,重啓期間服務不可用;並且事發前用戶觸發的部分任務沒有重試機制,一些中間狀態的數據需要人工修復。

    初步排查是程序池內存溢出引起的IIS自動回收,監控看板顯示服務器內存從50%瞬間漲到100%,十幾分鍾之後觸發回收,內存才徹底降下來。

    此後重啓事件陸續不斷,間隔從數小時到數天不等,每次重啓前毫無徵兆,沒有規律可循,下圖是服務的內存走勢圖。

三、事件分析

    通常內存泄漏最常規的做法是抓取Dump包,然後分析Dump包中的堆棧數據。但這次內存泄漏並非緩慢上升,而是瞬間將內存吃滿,沒有多餘的硬件資源供抓包工具使用。更重要的是這個後臺服務職責過重,也非常重要,屬於一個在線系統,隨便都有用戶訪問,抓包工具在進行抓包過程中會中斷進程,影響服務使用。所以抓取Dump包的方案被暫時擱置。

    在翻遍所有日誌沒有找到蛛絲馬跡之後,決定採用分治策略排除法,先將事故影響的範圍慢慢縮小。

四、垂直拆分

    前文提到後臺服務職責過重,需要進行拆分,這個是年初團隊達成的共識,方案是先將後臺作業任務獨立出來,此次事件是個契機。新增一個調度服務,將現有定時作業和主動長時作業兩塊邏輯包含進去。拆分之後,主服務恢復正常,重啓問題轉移至新增的調度服務,風險隨之降級,雖然問題沒有解決,但是兩個服務的事故級別是不一樣的,主服務恢復正常後,至少不會對用戶產生直接影響。

    雖然是一次投鼠忌器的嘗試,好在結果符合預期,滿足分治的需求,現在已經可以確定,元兇肯定就在調度服務的代碼中。

五、監控埋點

    現在範圍縮小了許多,定時作業和主動長時作業兩大類,其中的子任務加起來不超過20個,將所有任務的開始和結束各記一次埋點。隨後觀察重啓發作時間點與兩大類後臺作業的開始時間進行匹配,所有的任務都可以正常的開始,並正常的結束,沒有一類任務的執行時間與重啓時間點完全匹配,後臺任務的嫌疑全部排出。

六、動態抓包

    排查陷入了僵局,再次觀察監控看板,分析現有數據:調度服務的進程內存,正常值在400m-700m之間波動,出現異常時內存在2分鐘內,由正常值上升至最高值,最高值接近系統總內存,這個與最初的症狀有所不同,起初內存是90度直線瞬間吃滿,現在是在2分鐘內45度吃滿內存。有了2分鐘的緩衝期,可能與內存擴容有關,也可能與垂直拆分有關,原因不得而知,但這一點變化使得Dump抓包成爲可能。

    這裏推薦使用ProcDump,ProcDump是一個輕量級的Sysinternal團隊開發的命令行工具,它的主要目的是監控應用程序的CPU內存異常動向,並在此異常時生成crash dump文件,供研發人員和管理員確定問題發生的原因。你還可以把它作爲生成dump的工具使用在其他的腳本中。

    根據調度服務內存正常值的區間範圍,內存閾值設置爲1.5G,超過1.5g認爲內存異常。按照ProcDump官網的介紹,ProcDump可以在閾值條件(內存、CPU、異常)到達時,自動觸發Dump抓取,但是我部署在服務器ProcDump在一定時間後會出現假死現象,不能後臺作業,也不知道下一次重啓什麼時候來臨。無奈臨時編寫一個小工具,需求很簡單,實時監控進程內存,10s輪詢一次,超過閾值執行“procdump.exe -ma dotnet.exe“命令抓取Dump,下面是關鍵代碼:

七、內存分析

    在小工具完成的第二天便成功捕捉到了傳說的“黑匣子”,如空難事故中一樣,它記錄了程序在宕機前的性能指標。

    堆中數據顯示,兩個字符串數組佔據了大部分內存空間,進一步跟蹤到是一個zip包解壓函數,在讀取文件流時,while循環的退出條件判斷有誤,引起了死循環,無論系統內存有多大,都可以在短時間將服務器內存吃滿。代碼並非每次都會進來,具有一定的隨機性,取決於用戶的操作,所以重啓沒有規律。

    這段業務屬於長時作業任務,在上面第五步的監控埋點中,記錄了定時作業和主動長時作業兩大類的執行結果,唯獨這個被動長時作業任務成了漏網之魚,沒有納入監控體系。

八、總結

    雖然修復這個Bug的工作量很小,但Bug持續時間長,影響較大。事後團隊做了兩點改進措施,一是研發流程中加入代碼掃描工具,將這類“低級錯誤”攔截在開發階段,避免提交到測試環境和生產環境;二是繼續完善了現有監控數據,將被動長時作業任務的執行情況,展示在監控看板上,如果某個任務執行失敗,或者只有開始沒有結束,監控看板會及時發送提醒,爲排查類似問題提供線索,快速定位類似問題。

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