Node 記憶體洩漏排查案例
阿新 • • 發佈:2020-05-08
## 背景
在阿里雲上看到我運行了一段時間的程式,發現 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 裡的定