在將監控日志的服務獨立部署后,還是發現CPU會在不特定時間段(例如21~22、23~02等)飆到70%,內存也是一路飆升不會下降,明顯是出現了內存泄漏。
需要進一步做優化,於是開通了阿里雲的 Node.js 性能平台。
一、Node.js性能平台
要使用此工具需要在自己的服務器中安裝些組件的,具體步驟參考官網說明,公司運維操作起來蠻快的,下圖是平台中的數據趨勢。
點擊堆快照,就會生成一個*.heapsnapshot文件,通過該文件就能查看內存的分布和使用情況,點擊下圖中的轉儲就能查看分析了。
但是我怎么點,每次都是失敗,后面找了阿里雲的技術人員,他說是因為文件太大,下載的時候總是會斷開,無奈,只能在服務器上手動下載,然后在本地Chrome中加載了。
二、分析
在該平台上下載了堆快照(*.heapsnapshot文件),在Chrome的Memory選項卡中載入,可以看到下圖內容。
1)任務隊列(Kue.js)
翻看其中的幾列,發現內存中滯留了很多隊列任務的數據,於是鎖定內存暴漲與隊列有關。
然后開始查代碼,並且在本地做了調試,發現在任務完成后沒有將其標記為成功,因為聲明的那個改變狀態的函數沒有被執行。
只有標記成功的任務才會被自動清除,由於狀態沒有更新,導致滯留在內存中,從而使得內存一直在漲而不會降。
一頓操作猛如虎,但是最后發布上去后,內存並沒有降下來,依然在增長中,說明不是這個問題。
在創建隊列任務時會打條日志,然后在完成任務后,會再打一條日志,發現一分鍾內會創建大約4、5百個任務,但是完成的任務只有200個,甚至更少。
也就是出隊的速度沒有入隊快,隊列來不及處理任務。如此下去的話,就會將任務堆積在一起。
馬上為隊列處理的方法加了個並發的參數,再用LoadTest模擬並發,效果非常理想,任務有條不紊地被處理了,於是發布了代碼。
若要結束並發測試,mac電腦可執行命令 kill -USR2 36155,其中 36155 是端口號。
但高興的還是太早,雖然為隊列加了並發的設置,但滯留的任務並沒有減少,猜想可能是任務中的邏輯阻塞了任務的完成,繼續將耗時邏輯注釋掉,內存並沒有如預期那樣降下來。
再次分析,感覺是上面配置的並發沒有生效,很奇怪,查看Kue.js源碼也沒看出個所以然來。
只能另辟蹊徑了,也就是多創建幾種類型,但處理的邏輯是一樣的,以此來彌補任務隊列的吞吐量。
for (let i = 1; i <= 3; i++) { const taskName = "handleMonitor" + i; queue.process(taskName, (job, done) => { services.common .handleMonitor(job.data.monitor) .then(() => { done(); }) .catch((err) => { done(err); }); }); }
查看日志,發現隊列的入和出已經平衡,但是內存仍然會升,沒有降的趨勢。
2)繼續分析
再次觀察堆快照,我一度懷疑是 Sequelize 、KOA 或 Node.js 8.0版本的問題,翻來覆去的查,雖然的確看到了內存泄漏的蛛絲馬跡,但仍然沒有起色。
后面將兩份堆快照做對比,在查看增長的數據時,發現我請求的 ma.gif 路徑中的變量不會釋放,存在着一個閉包,八成是這個原因導致內存一直漲。
於是仔細查看代碼,將最可疑的一句代碼注釋掉,如下所示,省略了其他邏輯,就放出了關鍵的那句代碼,為外部的 queue 對象反復注冊了一個error事件。
import queue from "../util/queue"; router.get("/ma.gif", async (ctx) => { queue.on('error', function( err ) { logger.trace('handleMonitor queue error', err); }); });
沒想到內存一下子平穩了,沒有出現暴增的情況。一波多折后發現,原來是自己寫的代碼不對導致內存的泄漏。
參考資料: