Node 內存泄漏排查案例

背景

在阿里雲上看到我運行了一段時間的程序,發現 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 [email protected]:/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 裏的定時腳本

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