背景
在阿里雲上看到我運行了一段時間的程序,發現 memory 一項基本是在穩步提升,就知道有內存泄漏的情況出現。如下圖
近三日從 35% 升到 40%,緩慢而堅定的提升。
代碼
排查此問題需要分析其堆內存快照,當然我們不能直接使用線上機器調試。不幸的是測服機器在內網,和阿里雲聯不通,alinode 發揮不了作用。但所幸的是 V8 引擎提供了內部接口可以直接把堆中的JS對象導出來供開發者分析。我們采用heapdump這個模塊,執行如下命令安裝
$ npm install heapdump --save
"heapdump": "^0.3.15",
執行如下
const heapdump = require('heapdump');
heapdump.writeSnapshot(`./${Date.now()}.heapsnapshot`);
生成的文件如下
$ ll -lh
-rw-rw-r-- 1 souche souche 38M Nov 19 19:00 1574161221512.heapsnapshot
總之我在測服上定時每 2 小時打印堆棧快照。
總之,你可以使用 scp 命令把測服的代碼導出到本地
# 傳遞單個文件
$ scp 【服務器用戶名】@【服務器地址】:【服務器上存放文件的路徑】【本地文件的路徑】
# 例如
$ scp souche@172.11.xxx.xxx:/home/souche/app/egg-test/current/1574161221512.heapsnapshot /Users/dasouche/workspace/sc-node
# 傳遞文件夾
scp -r 【服務器用戶名】@【服務器地址】:【服務器上存放文件的路徑】【本地文件的路徑】
分析步驟
打開 chrome-控制台-Memory-load
加載完后得到
簡而言之,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執行。
(constructor) 增長排第二,其中也見到不少 schedule,那我們可以確認就是 noticeJob.ts 這個定時器的問題。
本項目使用了 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 里的定時腳本