為了讓前端工作更有效率唆樊,必須徹底掌握一些必要的調(diào)試技巧宛琅。平常在開發(fā)Node應(yīng)用的過程中,最常使用的是本地調(diào)試逗旁,但是一旦你的代碼到了生產(chǎn)環(huán)境嘿辟,就必須采取其他策略進(jìn)行追蹤和解決問題。
在編程領(lǐng)域片效,有一個(gè)專門的術(shù)語“Post-mortem debugging"红伦,它的意思是在程序奔潰后再進(jìn)行的調(diào)試工作。對(duì)于Node程序來說淀衣,如果你遇到了難以重現(xiàn)昙读、線上環(huán)境無法調(diào)試等問題都可以采用這種方案進(jìn)行操作。而且線上問題一般都是比較緊急的膨桥,所以我們一般都希望能最快定位到問題發(fā)生的代碼蛮浑。
那么我們具體要怎么做呢唠叛?下面舉個(gè)簡(jiǎn)單的例子。
收集奔潰信息
假設(shè)現(xiàn)在你有這樣一段代碼:
const demo = (data) => {
const {id, profile} = person;
console.log(id);
console.log(profile.name);
}
demo({id: 1, profile: {age: 12}});
運(yùn)行后它會(huì)報(bào)錯(cuò)并退出沮稚。這時(shí)候你需要做的是收集奔潰信息并對(duì)其做分析玻墅。Core Dump就是這樣用來記錄程序運(yùn)行信息的一種工具,它包含了程序運(yùn)行過程中的內(nèi)存狀態(tài)壮虫,調(diào)用棧等,能最真實(shí)地還原當(dāng)時(shí)的“案發(fā)現(xiàn)場(chǎng)“环础。
那么囚似,在Node.js中我們?cè)趺传@得Core Dump的文件呢?
首先线得,我們先設(shè)置一下系統(tǒng)中的內(nèi)核限制:
ulimit -c unlimited
然后你需要在啟動(dòng)應(yīng)用的時(shí)候饶唤,使用--abort-on-uncaught-exception
這個(gè)flag來手動(dòng)觸發(fā)程序奔潰后寫core文件的操作:
node --abort-on-uncaught-exception app.js
這樣當(dāng)程序突然奔潰的時(shí)候,就會(huì)在linux或mac系統(tǒng)的/cores
目錄下生成類似core.81371
這樣的一個(gè)文件贯钩。這個(gè)文件就是我們用來調(diào)試調(diào)查程序奔潰的核心募狂。
如果你的程序正在執(zhí)行過程中,我們也可以手動(dòng)捕獲core dump
文件角雷,類似于實(shí)時(shí)檢查祸穷,主要用于程序假死等狀態(tài)。
手動(dòng)捕獲的話需要使用Linux系統(tǒng)自帶的 gcore 命令勺三,具體用法是找出當(dāng)前進(jìn)程的pid(這里假設(shè)是123),然后執(zhí)行命令:
gcore 123
生成對(duì)應(yīng)的core dump
文件雷滚。
另外一種方式是采用lldb調(diào)試工具,mac系統(tǒng)下使用該命令進(jìn)行安裝:
brew install --with-lldb --with-toolchain llvm
然后執(zhí)行:
lldb --attach-pid <pid> -b -o 'process save-core' "core.<pid>"'
這樣就能在不重啟程序的情況下導(dǎo)出特定進(jìn)程的core dump
文件。
調(diào)試步驟
得到具體的core dump
文件后吗坚,我們就要進(jìn)入調(diào)試分析階段了祈远。
首先,需要使用選擇順手的分析工具商源。你可以選擇mdb_v8或者llnode车份。這兩個(gè)工具用起來都差不多。
這里以llnode為例牡彻,先介紹幾個(gè)常用命令:
命令 | 意義 |
---|---|
v8 help | 查看幫助信息 |
v8 bt | get stack trace at crash 查看堆棧信息 |
v8 souce list | 顯示stack frame的源碼 |
v8 inspect <addr> | 查看對(duì)應(yīng)地址的對(duì)象內(nèi)容 |
frame select <num> | 選擇對(duì)應(yīng)的stack frame |
在分析前扫沼,先需要用llnode加載core
文件:
llnode -c /cores/core.81371
然后獲取對(duì)應(yīng)的堆棧信息:
// 查看堆棧信息
(llnode) v8 bt
// 根據(jù)堆棧信息找到可疑的地址,并查看對(duì)應(yīng)的對(duì)象內(nèi)容
(llnode) v8 inspect <address>
// 指定對(duì)應(yīng)的stack frame
(llnode) frame select 6
// 查看源碼
(llnode) v8 source list
最后通過結(jié)合堆棧信息和源碼就能找到錯(cuò)誤發(fā)生的原因了讨便。
內(nèi)存泄漏
除了程序奔潰充甚,有時(shí)候你還會(huì)發(fā)現(xiàn)應(yīng)用隨著運(yùn)行時(shí)間增長(zhǎng),速度開始變慢霸褒。這可能就是內(nèi)存泄漏搗的鬼伴找。
比如下面這段代碼:
const requests = new Map();
app.get("/", (req, res) => {
requests.set(req.id, req);
res.status(200).send("hello")
})
通常來說,內(nèi)存泄漏容易發(fā)生在閉包等場(chǎng)景下废菱。針對(duì)內(nèi)存泄漏的調(diào)試技矮,可以使用如下命令:
node --trace_gc --trace_gc_verbose app.js
啟動(dòng)應(yīng)用后抖誉,通過壓測(cè)工具運(yùn)行如下命令:
ab -k -c200 -n10000000 http://localhost:3000
可以看到隨著程序的運(yùn)行,內(nèi)存使用越來越大衰倦。
另外袒炉,我們還可以使用heap snapshot
來獲取快照信息:
process.on('SIGUSR2', () => {
const { writeHeapSnapshot } = require("v8");
console.log("Heap snapshot has written:", writeHeapSnapshot())
})
在命令行中執(zhí)行:
kill -SIGUSR2 <pid>
就能夠獲得對(duì)應(yīng)的快照文件,然后我們可以使用Chrome Devtools的Memory菜單加載對(duì)應(yīng)的快照文件進(jìn)行比對(duì)分析了樊零。
如果是開發(fā)階段我磁,你也可以直接使用調(diào)試模式啟動(dòng)應(yīng)用:
node --inspect app.js
然后使用菜單Devtools > Memory > take heap snapshot獲得快照文件。
通過比較兩個(gè)不同的內(nèi)存快照驻襟,我們可以很快找到內(nèi)存增長(zhǎng)最快的那個(gè)對(duì)象夺艰,然后進(jìn)而分析對(duì)應(yīng)的源碼就能知道問題出在了哪里。
除了采用Chrome瀏覽器沉衣,在linux主機(jī)上郁副,我們還能使用萬能的llnode調(diào)試器進(jìn)行內(nèi)存泄漏的分析。原理大致相同豌习,也是通過分析core 文件存谎,然后安裝對(duì)象大小排序,針對(duì)可疑的對(duì)象進(jìn)行源碼查看肥隆。
(llnode) v8 findjsobjects
(llnode) v8 findjsinstances -d <Object>
(llnode) v8 inspect -m <address》
(llnode) v8 findrefs <address>
其它策略
另外一種收集報(bào)告的策略是使用參數(shù)既荚,適用于13.0以上版本:
node \
--experimental-report \
--diagnostic-report-uncaught-exception \
--diagnostic-report-on-fatalerror \
app.js
這樣在程序奔潰的時(shí)候,就能夠獲取到對(duì)應(yīng)的報(bào)告巷屿。你還可以通過代碼顯式控制報(bào)告的輸出文件名等:
process.report.writeReport('./foo.json');
更多說明可以參考官方文檔: https://nodejs.org/api/report.html
——--轉(zhuǎn)載請(qǐng)注明出處--———
最后,歡迎大家關(guān)注我的公眾號(hào)嘱巾,一起學(xué)習(xí)交流憨琳。
參考資料
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