如何对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/…

相关文章
相关标签/搜索