如何對Node應用"死後驗屍"

爲了讓前端工做更有效率,必須完全掌握一些必要的調試技巧。日常在開發Node應用的過程當中,最常使用的是本地調試,可是一旦你的代碼到了生產環境,就必須採起其餘策略進行追蹤和解決問題。javascript

在編程領域,有一個專門的術語「Post-mortem debugging",它的意思是在程序奔潰後再進行的調試工做。對於Node程序來講,若是你遇到了難以重現、線上環境沒法調試等問題均可以採用這種方案進行操做。並且線上問題通常都是比較緊急的,因此咱們通常都但願能最快定位到問題發生的代碼。html

那麼咱們具體要怎麼作呢?下面舉個簡單的例子。前端

收集奔潰信息

假設如今你有這樣一段代碼:java

const demo = (data) => {
        const {id, profile} = person;
        console.log(id);
        console.log(profile.name);
}

demo({id: 1, profile: {age: 12}});
複製代碼

運行後它會報錯並退出。這時候你須要作的是收集奔潰信息並對其作分析。Core Dump就是這樣用來記錄程序運行信息的一種工具,它包含了程序運行過程當中的內存狀態,調用棧等,能最真實地還原當時的「案發現場「。node

那麼,在Node.js中咱們怎麼得到Core Dump的文件呢?linux

首先,咱們先設置一下系統中的內核限制:git

ulimit -c unlimited
複製代碼

而後你須要在啓動應用的時候,使用--abort-on-uncaught-exception這個flag來手動觸發程序奔潰後寫core文件的操做:github

node --abort-on-uncaught-exception app.js
複製代碼

draggingScreenshot.png

這樣當程序忽然奔潰的時候,就會在linux或mac系統的/cores目錄下生成相似core.81371這樣的一個文件。這個文件就是咱們用來調試調查程序奔潰的核心。編程

若是你的程序正在執行過程當中,咱們也能夠手動捕獲core dump文件,相似於實時檢查,主要用於程序假死等狀態。json

手動捕獲的話須要使用Linux系統自帶的 gcore 命令,具體用法是找出當前進程的pid(這裏假設是123),而後執行命令:

gcore 123
複製代碼

生成對應的core dump文件。

另一種方式是採用lldb調試工具,mac系統下使用該命令進行安裝:

brew install --with-lldb --with-toolchain llvm
複製代碼

而後執行:

lldb --attach-pid <pid> -b -o 'process save-core' "core.<pid>"' 複製代碼

這樣就能在不重啓程序的狀況下導出特定進程的core dump文件。

調試步驟

獲得具體的core dump文件後,咱們就要進入調試分析階段了。

首先,須要使用選擇順手的分析工具。你能夠選擇mdb_v8或者llnode。這兩個工具用起來都差很少。

這裏以llnode爲例,先介紹幾個經常使用命令:

命令 意義
v8 help 查看幫助信息
v8 bt get stack trace at crash 查看堆棧信息
v8 souce list 顯示stack frame的源碼
v8 inspect 查看對應地址的對象內容
frame select 選擇對應的stack frame

在分析前,先須要用llnode加載core文件:

llnode -c /cores/core.81371
複製代碼

而後獲取對應的堆棧信息:

// 查看堆棧信息
(llnode) v8 bt
// 根據堆棧信息找到可疑的地址,並查看對應的對象內容
(llnode) v8 inspect <address>
// 指定對應的stack frame
(llnode) frame select 6
// 查看源碼
(llnode) v8 source list
複製代碼

最後經過結合堆棧信息和源碼就能找到錯誤發生的緣由了。

內存泄漏

除了程序奔潰,有時候你還會發現應用隨着運行時間增加,速度開始變慢。這可能就是內存泄漏搗的鬼。

好比下面這段代碼:

const requests = new Map();

app.get("/", (req, res) => {
  requests.set(req.id, req);
  res.status(200).send("hello")
})
複製代碼

一般來講,內存泄漏容易發生在閉包等場景下。針對內存泄漏的調試,可使用以下命令:

node --trace_gc --trace_gc_verbose app.js
複製代碼

啓動應用後,經過壓測工具運行以下命令:

ab -k -c200 -n10000000 http://localhost:3000
複製代碼

draggingScreenshot.png

能夠看到隨着程序的運行,內存使用愈來愈大。

另外,咱們還可使用heap snapshot來獲取快照信息:

process.on('SIGUSR2', () => {
	const { writeHeapSnapshot } = require("v8");
	
	console.log("Heap snapshot has written:", writeHeapSnapshot())
})
複製代碼

在命令行中執行:

kill -SIGUSR2 <pid>
複製代碼

就可以得到對應的快照文件,而後咱們可使用Chrome Devtools的Memory菜單加載對應的快照文件進行比對分析了。

若是是開發階段,你也能夠直接使用調試模式啓動應用:

node --inspect app.js
複製代碼

而後使用菜單Devtools > Memory > take heap snapshot得到快照文件。

draggingScreenshot.png

經過比較兩個不一樣的內存快照,咱們能夠很快找到內存增加最快的那個對象,而後進而分析對應的源碼就能知道問題出在了哪裏。

除了採用Chrome瀏覽器,在linux主機上,咱們還能使用萬能的llnode調試器進行內存泄漏的分析。原理大體相同,也是經過分析core 文件,而後安裝對象大小排序,針對可疑的對象進行源碼查看。

(llnode) v8 findjsobjects
(llnode) v8 findjsinstances -d <Object>
(llnode) v8 inspect -m <address》
(llnode) v8 findrefs <address>
複製代碼

其它策略

另一種收集報告的策略是使用參數,適用於13.0以上版本:

node \
	--experimental-report \
	--diagnostic-report-uncaught-exception \
	--diagnostic-report-on-fatalerror \
	app.js
複製代碼

這樣在程序奔潰的時候,就可以獲取到對應的報告。你還能夠經過代碼顯式控制報告的輸出文件名等:

process.report.writeReport('./foo.json');

複製代碼

更多說明能夠參考官方文檔: nodejs.org/api/report.…

——轉載請註明出處———

微信掃描二維碼,關注個人公衆號

最後,歡迎你們關注個人公衆號,一塊兒學習交流。

參考資料

medium.com/netflix-tec… en.wikipedia.org/wiki/Debugg… www.bookstack.cn/read/node-i… github.com/bnoordhuis/…

相關文章
相關標籤/搜索