線上環境竟然遇到了內存泄露,通過 3 天的摸索,算是解決了:javascript
更換 pm2 版本,由 v3.5.1 降爲 v3.4.1
對於這個結論仍是不太滿意,算是歪打正着。不過仍是記錄下這幾天積累的經驗,說不定對正在看此文的你會有幫助。html
有幸,我此次有充足的時間去排查,沒有其餘事情干擾。但線上出現內存泄漏,解決起來比業務代碼 bug 更難解決,那種頭皮發麻的難受。緣由以下:前端
無論有沒有精力 or 能力,我認爲 解決內存泄漏的最佳實踐是:積極的心態 + 冷靜的問題定位。(沒錯,這是對目前沒有解決問題的你說的)java
能夠看下這篇文章 Finding And Fixing Node.js Memory Leaks: A Practical Guidenode
Anyway,其實無論什麼緣由,主要仍是 各類引用 得不到 V8 GC 的釋放。扔一段很經典的代碼:python
function calc(data) { return Math.round((data / 1024 / 1024) * 100) / 100 + " MB"; } function logger() { let mem = process.memoryUsage(); console.log(new Date(), "memory now:", calc(mem.rss)); } var theThing = null; var replaceThing = function() { logger(); var originalThing = theThing; var unused = function foo() { if (originalThing) { console.log("未被調用,但 originalThing 有個 someMethod 的引用"); } }; theThing = { longStr: new Array(1000000).join("*"), someMethod: function() { console.log("沒作任何事情,但我是閉包"); } }; console.log("parse"); }; setInterval(replaceThing, 10);
執行沒多久,就從 20M 飈到 幾百 M。究其緣由,仍是由於閉包引用沒有及時被銷燬。linux
具體緣由以下:c++
雖然 unused 沒有被調用,可是其中包含 originalThing 並指向 theThing ,theThing 在定義時有個 someMethod 方法,其就是個閉包(能夠訪問到 originalThing) ,該閉包在 unused 中因爲引用了 originalThing 一直沒有被釋放。git
若是你對這塊有查詢過相關資料, heapdump 這模塊應該頻繁出現,這裏說下如何使用它來監控項目的內存狀況。固然還有 memwatch ...github
npm install heapdump -S
若是你足夠幸運,確定會出現以下問題:
error: #error This version of node/NAN/v8 requires a C++11 compiler
原版本太低,須要更新 linux 系統的 gcc 等相關庫。
參考以下安裝說明:
# 安裝 repo 倉庫 wget http://people.centos.org/tru/devtools-2/devtools-2.repo mv devtools-2.repo /etc/yum.repos.d # 安裝新庫 yum install devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++ # 備份原庫 mv /usr/bin/gcc /usr/bin/gcc-4.4.7 mv /usr/bin/g++ /usr/bin/g++-4.4.7 mv /usr/bin/c++ /usr/bin/c++-4.4.7 # 建立快捷方式,將新庫鏈到須要目錄 ln -s /opt/rh/devtoolset-2/root/usr/bin/gcc /usr/bin/gcc ln -s /opt/rh/devtoolset-2/root/usr/bin/c++ /usr/bin/c++ ln -s /opt/rh/devtoolset-2/root/usr/bin/g++ /usr/bin/g++ # 檢查版本,肯定生效 gcc --version
固然系統是 window 可能還會有更坑的問題:安裝 node-gyp、python 出錯。
推薦以下 npm 模塊:
windows-build-tools
一鍵安裝相關組件依賴(咱們只要靜靜的等待,由於時間有些久)。他會幫你安裝 window 對應的 NET Framework,python 這些插件。
npm install --global --production windows-build-tools
線上很簡單的作了一個控制檯輸出,用於定位問題:
var heapdump = require("heapdump"); let startMem = process.memoryUsage(); function calc(data) { return Math.round((data / 1024 / 1024) * 10000) / 10000 + " MB"; } // 使用的是 koa router.all("/foo", async (ctx, next) => { let mem = process.memoryUsage(); logger.debug("memory before", calc(startMem.rss), "memory now:", calc(mem.rss), "diff increase", calc(mem.rss - startMem.rss)); // ... });
這樣就能實時看到系統的內存消耗(對比剛啓動時):
2019-09-06 16:30 +08:00: [2019-09-06T16:30:06.700] [DEBUG] transfer - memory before 55.5898 MB memory now: 95.1484 MB diff increase 39.5586 MB 2019-09-06 16:30 +08:00: [2019-09-06T16:30:07.724] [DEBUG] transfer - memory before 56.2148 MB memory now: 69.8438 MB diff increase 13.6289 MB 2019-09-06 16:30 +08:00: [2019-09-06T16:30:10.406] [DEBUG] transfer - memory before 56.2148 MB memory now: 70.5977 MB diff increase 14.3828 MB 2019-09-06 16:30 +08:00: [2019-09-06T16:30:11.018] [DEBUG] transfer - memory before 55.5898 MB memory now: 95.4219 MB diff increase 39.832 MB 2019-09-06 16:30 +08:00: [2019-09-06T16:30:12.827] [DEBUG] transfer - memory before 55.5898 MB memory now: 95.6797 MB diff increase 40.0898 MB 2019-09-06 16:30 +08:00: [2019-09-06T16:30:12.952] [DEBUG] transfer - memory before 55.5898 MB memory now: 94.9688 MB diff increase 39.3789 MB
添加個快照路由,在按照須要抓取內存此刻使用狀況:
router.all("/snapshot", async (ctx, next) => { heapdump.writeSnapshot("./dump-" + Date.now() + ".heapsnapshot", function(err) { if (err) console.error(err); }); });
導入到 chrome 的 profile 面板中,對比先後兩個文件的變化,定位問題
一切順利就能很快定位到問題代碼。但實際要更困難,更摸不着頭腦。
若是按照上述的「檢查」操做仍是沒有定位到問題點,可能這段會對你有所幫助。
首先要知道本身着手的項目的用途,所用技術,它對你排查問題更有指向意義。
好比:此項目是基於 node 的中間層服務,對 api 接口進行轉換。用於由後端 api 服務的「升級」平滑各客戶端的發版時差。(服務可能在 api 調用上有性能瓶頸?)
技術棧:sequelize + koa + pm2 (熟悉項目的主要框架,從大技術方向着手)
有幸有個 項目 B 和此項目相似,技術上略有差別。
綜上所述,就猜想了幾個可能的 內存泄露 緣由(附參考文章):
代碼問題
代碼邏輯全局緩存問題
訪問量(項目 A 高於 項目 B)
對數據庫的查詢的衝擊(sequelize)
日誌讀寫堆積(log4js)
第三方依賴
pm2
可能緣由 | 測試方式 | 驗證結果 | 備註 |
---|---|---|---|
代碼問題 | ab 壓測 | ok | 但需增長重視 |
sequlize 版本問題 | ab 壓測 | ok | 暫不嘗試。目前使用 4.42.0,線上沒法承擔更換版本的風險 |
Ladash _.template | ab 壓測 | ok | 保持現狀 |
log4js 有背壓問題 | ab 壓測 | ok | pm2 & log4js 使用不夠友好,在有替代方案前(好比 winston),保持現狀 |
pm2 版本問題 | 線上下降版本 3.5.1 -> 3.4.1 | 待驗證 | 項目 B 使用 3.4.1 |
這兩天從線上狀況上看,修改 pm2 版本後,內存泄漏獲得控制。下面由此結論反推驗證步驟:
TIMERWRAP 是 Node 裏 Timer 相關定義,猜想是否有定時器在有規律的無限刷新佔用性能。
metrics 心跳檢測,是否有 setTimeout 之類的代碼?
查看線上項目相關代碼,的確有這段問題代碼:
對於爲什麼這段 if/else 會形成內存泄漏,有空再研究下。估計相似 dom 的 event 綁定沒有解除所致。
我只是知識點的「加工者」, 更多內容請查閱原文連接 , 同時感謝原做者的付出:
若是你以爲這篇文章對你有幫助, 請點個贊或者分享給更多的道友。
也能夠掃碼關注個人 微信訂閱號 - [ 前端雨爸 ], 第一時間收到技術文章 , 工做之餘我會持續輸出
最後感謝閱讀, 大家的支持是我寫做的最大動力