如何对Node应用"死后验尸"

为了让前端工作更有效率,必须彻底掌握一些必要的调试技巧。平常在开发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
draggingScreenshot.png

这样当程序突然奔溃的时候,就会在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
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');

更多说明可以参考官方文档: https://nodejs.org/api/report.html

——--转载请注明出处--———

微信扫描二维码,关注我的公众号.jpg

最后,欢迎大家关注我的公众号,一起学习交流。

参考资料

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 很多Node.js初学者都会有这样的疑惑,Node.js到底是单线程的还是多线程的?通过本章的学习,能够让读者较为...
    越努力越幸运_952c阅读 3,624评论 4 36
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,068评论 1 32
  • 之前初步了解过Windows 下强大的调试工具WinDbg,也简单的整理了一个初级的文章《使用WinDbg、Map...
    SunnyZhang的IT世界阅读 3,605评论 0 1
  • 昨天的温度不算太高也不低,温差大中午太阳撩人,收费亭恨不能吸收所有光和热,仅有一平米的空间还要和电脑亲密接...
    芙蕖沉香阅读 166评论 0 1
  • 今日体验,今天晚上公司组织培训轮胎,每一次培训都能学到一些东西,这也是精保的项目,每天都在干的,了解每一个项目产品...
    王全峰阅读 80评论 0 0