1. 程式人生 > >Node 記憶體洩漏排查案例

Node 記憶體洩漏排查案例

## 背景 在阿里雲上看到我運行了一段時間的程式,發現 memory 一項基本是在穩步提升,就知道有記憶體洩漏的情況出現。如下圖 ![](https://img2020.cnblogs.com/blog/625864/202005/625864-20200507223540106-1525574753.png) 近三日從 35% 升到 40%,緩慢而堅定的提升。 ## 程式碼 排查此問題需要分析其堆記憶體快照,當然我們不能直接使用線上機器除錯。不幸的是測服機器在內網,和阿里雲聯不通,alinode 發揮不了作用。但所幸的是 V8 引擎提供了內部介面可以直接把堆中的JS物件匯出來供開發者分析。我們採用heapdump這個模組,執行如下命令安裝 ```bash $ npm install heapdump --save "heapdump": "^0.3.15", ``` 執行如下 ```js const heapdump = require('heapdump'); heapdump.writeSnapshot(`./${Date.now()}.heapsnapshot`); ``` 生成的檔案如下 ```bash $ ll -lh -rw-rw-r-- 1 souche souche 38M Nov 19 19:00 1574161221512.heapsnapshot ``` 總之我在測服上定時每 2 小時列印堆疊快照。 總之,你可以使用 scp 命令把測服的程式碼匯出到本地 ```bash # 傳遞單個檔案 $ scp 【伺服器使用者名稱】@【伺服器地址】:【伺服器上存放檔案的路徑】【本地檔案的路徑】 # 例如 $ scp [email protected]:/home/souche/app/egg-test/current/1574161221512.heapsnapshot /Users/dasouche/workspace/sc-node # 傳遞資料夾 scp -r 【伺服器使用者名稱】@【伺服器地址】:【伺服器上存放檔案的路徑】【本地檔案的路徑】 ``` ## 分析步驟 開啟 chrome-控制檯-Memory-load ![](https://img2020.cnblogs.com/blog/625864/202005/625864-20200507223550492-1441536808.png) 載入完後得到 ![](https://img2020.cnblogs.com/blog/625864/202005/625864-20200507223600979-26027339.png) 簡而言之,Shallow Size 就是物件自身被建立時所需要記憶體的大小,Retained Size 就是當把物件從支配樹上拿掉,物件和它的下級節點一共能釋放的記憶體大小。 其術語簡介可參見:https://developers.google.com/web/tools/chrome-devtools/memory-problems/memory-101 ## 分析過程 從線上機器匯出兩個堆檔案,一個是10月30日列印的,一個是11月4日列印的,其記憶體上升了 100+ MB。 比對兩個堆,把第二個堆檔案的 Summary 切換成 Comparison,並按 Delta 倒敘排,發現增長最快的是 (concatenated string) 。其中有很多連線字串,其中有大量的sql語句,並且有大量的schedule執行。 ![](https://img2020.cnblogs.com/blog/625864/202005/625864-20200507223610155-501717881.png) (constructor) 增長排第二,其中也見到不少 schedule,那我們可以確認就是 noticeJob.ts 這個定時器的問題。 ![](https://img2020.cnblogs.com/blog/625864/202005/625864-20200507223618131-1810305002.png) 本專案使用了 egg 作為框架,schedule 就是指定時觸發的邏輯。聯絡程式碼我們發現在一個 5 秒觸發一次的 schedule 裡,裡面不停的觸發佇列的 process 監聽事件,猜測是 Queue.process 監聽事件越綁越多的毛病,也導致裡面的邏輯越觸發越多。 這其實就是佇列繫結監聽事件的誤用了。 ``` // app/schedule/noticeJob.ts 'use strict'; import { Context } from 'egg'; import * as kue from 'kue'; module.exports = { schedule: { disable: false, // 每五秒觸發一次 cron: '*/5 * * * * *', immediate: true, type: 'worker', }, async task(ctx: Context) { const Queue = ctx.app.kue; Queue.process('noticeCalling', async function(job, done) { const { uid, rid, subId } = job.data; await ctx.service.message.noticedCalling(uid, rid); // done(); }); }, }; ``` 我們在測服註釋掉這段定時器後,每隔一小時列印一次(因為測服無法連阿里雲),觀察一天,記憶體沒有上升趨勢,這很好。 ``` -rw-rw-r-- 1 souche souche 38M Nov 24 11:24 1574565877609.heapsnapshot -rw-rw-r-- 1 souche souche 37M Nov 24 12:24 1574569477611.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 13:24 1574573077611.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 14:24 1574576677613.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 15:24 1574580277614.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 16:24 1574583877614.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 17:24 1574587477616.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 18:24 1574591077616.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 19:24 1574594677616.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 20:24 1574598277618.heapsnapshot -rw-rw-r-- 1 souche souche 37M Nov 24 21:24 1574601877620.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 22:24 1574605477621.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 24 23:24 1574609077622.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 00:24 1574612677622.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 01:24 1574616277622.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 02:24 1574619877623.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 03:24 1574623477624.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 04:24 1574627077626.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 05:24 1574630677627.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 06:24 1574634277627.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 07:24 1574637877628.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 08:24 1574641477629.heapsnapshot -rw-rw-r-- 1 souche souche 38M Nov 25 09:24 1574645077630.heapsnapshot -rw-rw-r-- 1 souche souche 39M Nov 25 10:24 1574648677630.heapsnapshot -rw-rw-r-- 1 souche souche 39M Nov 25 11:24 1574652277632.heapsnapshot ``` ## 解決方法 最後就在 app.ts 設定這個 process 的監聽,移除 schedule 裡的定