爲了讓前端工作更有效率,必須徹底掌握一些必要的調試技巧。平常在開發Node應用的過程中,最常使用的是本地調試,但是一旦你的代碼到了生產環境,就必須採取其他策略進行追蹤和解決問題。
在編程領域,有一個專門的術語“Post-mortem debugging",它的意思是在程序奔潰後再進行的調試工作。對於Node程序來說,如果你遇到了難以重現、線上環境無法調試等問題都可以採用這種方案進行操作。而且線上問題一般都是比較緊急的,所以我們一般都希望能最快定位到問題發生的代碼。
那麼我們具體要怎麼做呢?下面舉個簡單的例子。
收集奔潰信息
假設現在你有這樣一段代碼:
const demo = (data) => {
const {id, profile} = person;
console.log(id);
console.log(profile.name);
}
demo({id: 1, profile: {age: 12}});
運行後它會報錯並退出。這時候你需要做的是收集奔潰信息並對其做分析。Core Dump就是這樣用來記錄程序運行信息的一種工具,它包含了程序運行過程中的內存狀態,調用棧等,能最真實地還原當時的“案發現場“。
那麼,在Node.js中我們怎麼獲得Core Dump的文件呢?
首先,我們先設置一下系統中的內核限制:
ulimit -c unlimited
然後你需要在啓動應用的時候,使用--abort-on-uncaught-exception
這個flag來手動觸發程序奔潰後寫core文件的操作:
node --abort-on-uncaught-exception app.js
這樣當程序突然奔潰的時候,就會在linux或mac系統的/cores
目錄下生成類似core.81371
這樣的一個文件。這個文件就是我們用來調試調查程序奔潰的核心。
如果你的程序正在執行過程中,我們也可以手動捕獲core dump
文件,類似於實時檢查,主要用於程序假死等狀態。
手動捕獲的話需要使用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 <addr> | 查看對應地址的對象內容 |
frame select <num> | 選擇對應的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
可以看到隨着程序的運行,內存使用越來越大。
另外,我們還可以使用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獲得快照文件。
通過比較兩個不同的內存快照,我們可以很快找到內存增長最快的那個對象,然後進而分析對應的源碼就能知道問題出在了哪裏。
除了採用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');
更多說明可以參考官方文檔: https://nodejs.org/api/report.html
——--轉載請註明出處--———
最後,歡迎大家關注我的公衆號,一起學習交流。
參考資料
https://medium.com/netflix-techblog/debugging-node-js-in-production-75901bb10f2d
https://en.wikipedia.org/wiki/Debugging
https://www.bookstack.cn/read/node-in-debugging/2.1gcorellnode.md
https://github.com/bnoordhuis/node-heapdump