Node.js 应用故障排查手册 —— Node.js 性能平台使用指南

楔子

前一节中咱们借助于 Chrome devtools 实现了对线上 Node.js 应用的 CPU/Memory 问题的排查定位,可是在实际生产实践中,你们会发现 Chrome devtools 更加偏向本地开发模式,由于显然 Chrome devtools 不会负责去生成分析问题所须要的 Dump 文件,这意味着开发者还得额外在线上项目中设置好 v8-profiler 和 heapdump 这样的工具,而且经过额外实现的服务来可以去对线上运行的项目进行实时的状态导出。html

加上实际上预备章中除了 CPU/Memory 的问题,咱们还会遇到一些须要分析错误日志、磁盘和核心转储文件等才能定位问题的情况,所以在这些场景下,仅仅靠 Chrome devtools 显然会有一些力不从心。正是为了解决广大 Node.js 开发者的这些痛点,咱们在这里推荐你们在使用 Node.js 性能平台,即原来的 AliNode,它已经在阿里巴巴集团内部承载了几乎全部的 Node.js 应用线上运行监控和问题排查,所以你们能够放心在生产环境部署使用。node

本节将从 Node.js 性能平台 的设计架构、核心能力以及最佳实践等角度,帮助开发者更好地使用这一工具来解决前面提到的异常指标分析和线上 Node.js 应用故障定位。git

本书首发在 Github,仓库地址:https://github.com/aliyun-node/Node.js-Troubleshooting-Guide,云栖社区会同步更新。github

架构

Node.js 性能平台其实简单的说由三部分组成:云控制台 + AliNode runtime + Agenthub,以下图所示:npm

具体的部署步骤能够查看官方文档:自助式部署 Runtime。借助于 Node.js 性能平台的整套解决方案,咱们能够很方便地实现预备章节中提到的绝大部分异常指标的告警分析的能力。在生产实践过程当中,实际上在笔者看来,Node.js 性能平台解决方案其实仅仅是提供了三个最核心却也是最有效的能力:数组

  • 异常指标告警,即预备节中一些异常指标出现异常时能经过短信/钉钉通知到开发者
  • 导出线上 Node.js 应用状态,包括但不限于即前面 Chrome devtools 一节中提到的 CPU/Memory 状态导出
  • 在线分析结果和更好的 UI 展现,定制化解析应用导出状态和展现,更符合国内开发者习惯

换言之,Node.js 性能平台做为一个产品自己功能也在不断迭代新增修改中,可是以上的三个核心能力必定是第一优先级保障的,其它边边角角的功能则相对来讲响应优先级没有那么高。服务器

实际上咱们也理解做为使用平台的开发者但愿能在一个地方看到 Node.js 线上应用从底层到业务层的全部细节,然而我我的感受不一样的工具都有应该有其核心的能力输出,不少时候不断作加法容易让产品自己定位模糊化以及泛而不精,Node.js 性能平台实际上始终在致力于让本来线上黑盒的运行时状态,能更加直观地反馈给开发者,让 Node.js 应用的开发者面对一些偏向底层的线上疑难杂症可以再也不无所适从。架构

最佳实践

I.配置合适的告警

线上应用的告警其实是一种自我发现问题的保护机制,若是没有告警能力,那每次都会等到问题暴露到用户侧致使其反馈才能发现问题,这显然对用户体验很是的不友好。ide

所以部署完成一个项目后,开发者首先须要去配置合适的告警,而在咱们的生产实践中,线上问题经过错误日志、Node.js 进程 CPU/Memory 的分析、核心转储(Core dump)的分析以及磁盘分析可以得出结论,所以咱们须要的基本的告警策略也是源自以上五个部分。幸运的是平台已经给咱们预设好了这些告警,你们只须要选择一下便可完整这里的告警配置,以下图所示:函数

在 Node.js 性能平台 的告警页面上有 快速添加规则,点开选中后会自动生成告警规则的阈值表达式模板和报警说明模板,咱们能够按照项目实际监控需求进行修改,好比想要对 Node.js 进程的堆内存进行监控,能够选中 Memory 预警 选项,以下图所示:

此时点击 添加报警项 即完整了对进程堆内存的告警,而且将出现告警时须要点击 通知设置->添加到联系人列表 来添加的联系人加入此条规则,以下图所示:

那么在例子中的这条默认的规则里,当咱们的 Node.js 进程分配的堆内存超过堆上线的 80%(默认 64 位机器上堆上限是 1.4G)时会触发短信通知到配置绑定到此条规则的联系人。

实际上快速添加规则列表中给你们提供的是最多见的一些预配置好的告警策略,若是这些尚不能知足你的需求,更多定制化的自定义的服务告警策略配置方法能够看官方文档 报警设置。而且除了短信告警,也支持钉钉机器人推送告警消息到群,方便群发感知线上 Node.js 应用态势。

II. 按照告警类型进行分析

按照 I 节中配置完成合适的告警规则后,那么当收到告警短信时就能够按照策略类型进行对应的分析了。本节将按照预备节中比较常见的五大类问题来逐一讲解。

a. 磁盘监控

这个是比较好处理的问题,在快速添加的规则里实际上咱们会在服务器的磁盘使用超过 85% 时进行告警,那么收到磁盘告警后,能够链接到服务器,使用以下命令查看那个目录占用比较高:

sudo du -h --max-depth=1 /

找到占比比较高的目录和文件后,看看是否须要备份后删除来释放出磁盘空间。

b. 错误日志

收到特定的错误日志告警后,只须要去对应的项目的 Node.js 性能平台 控制台找到问题 实例 去查看其 异常日志 便可,以下图所示:

这里会按照错误类型进行规整,你们能够结合展现的错误栈信息来进行对应的问题定位。注意这里的错误日志文件须要你在部署 Agenthub 的时候写入配置文件,详细能够参见文档 配置和启动 Agenthub 一节中的 详细配置 内容。

c. 进程 CPU 高

终于到了前一节中借助 v8-profiler 导出 CPU Profile 文件再使用 Chrome devtools 进行分析的异常类型了。那么在 Node.js 性能平台 的整套解决方案下,咱们并不须要额外的去依赖相似 v8-profiler 这样的第三方库来实现进程状态的导出,与此相对的,当咱们收到 Node.js 应用进程的 CPU 超过咱们设置的阈值告警时,咱们只须要在控制台对应的 实例 点击 CPU Profile 按钮便可:

默认会给抓取的进程生成 3 分钟的 CPU Profile 文件,等到结束后生成的文件会显示在 文件 页面:

此时点击 转储 便可上传到云端以供在线分析展现了,以下图所示:

这里能够看到有两个 分析 按钮,其实第二个下标带有 (devtools) 的分析按钮实际上就是前一节中提到的 Chrome devtools 分析,这里再也不重复讲解了,若是有遗忘的同窗能够再去回顾下本大章前一节的内容。咱们重点看下第一个 AliNode 定制的分析,点击第一个分析按钮后,能够在新页面看到以下所示内容:

这里其实也是火焰图,但与 Chrome devtools 提供的火焰图不同的地方在于,这里是将抓取的 3 分钟内的 JS 函数执行进行了聚合展现出来的火焰图,在一些存在屡次执行同一个函数(可能每次执行很是短)的状况下,聚合后的火焰图能够很方便的帮助咱们找到代码的执行瓶颈来进行对应的优化。

值得一提的是,若是你使用的 AliNode runtime 版本在 v3.11.4 或者 v4.2.1 以上(包含这两个版本)的话,当你的应用出现类死循环问题,好比因为异常的用户参数致使的正则回溯(即执行完这个正则要十几年,相似于 Node.js 进程发生了死循环)这类问题时,能够经过抓取 CPU Profile 文件来很方便地定位到问题代码,详细信息有兴趣的同窗能够看下 Node.js 性能平台支持死循环和正则攻击定位

d. 内存泄漏

与 CPU 高的问题同样,当咱们收到 Node.js 应用进程的堆内存占据堆上限的比率超过咱们设置的阈值时,咱们也不须要相似 heapdump 这样的第三方模块来导出堆快照进行分析,咱们仍是在控制台对应的 实例 点击 堆快照 按钮便可生成对应 Node.js 进程的堆快照:

生成的堆快照文件一样会显示在 文件 列表页面,点击 转储 将堆快照上传至云端以供接下来的分析:

与上面同样,下标带有 (devtools) 的分析按钮仍是前一节中提到的 Chrome devtools 分析,这里仍是着重解析下 AliNode 定制的第一个分析按钮,点击后新页面以下图所示:

首先解释下上面的总览栏目的内容信息:

  • 文件大小: 堆快照文件自己的大小
  • Shallow Szie 总大小: 回顾下上一节中的内容,GC 根的 Retained Size 大小其实就是堆大小,也等于堆上分配的全部对象的 Shallow Size 总大小,所以这里其实就是已使用的堆空间
  • 对象个数: 当前堆上分配的 Heap Object 总个数
  • 对象边个数: 这个稍微抽象一些,假如 Object A.b 指向另外一个 Object B,咱们则认为表示指向关系的 b 属性就是一条边
  • GC Roots 个数: V8 引擎实现的堆分配,其实并非咱们以前为了帮助你们理解简化的只有一个 GC 根的状况,在实际的运行模型下,堆空间内存在许多的 GC 根,这里是真实的 GC 根的个数

这部分的信息旨在给你们一个概览,部分信息须要深刻解读堆快照才能完全理解,若是你实在没法理解其中的几个概览指标信息,其实也无伤大雅,由于这并不影响咱们定位问题代码。

简单了解了概览信息的含义后,接着咱们来看对于定位 Node.js 应用代码段很是重要的信息,第一个是默认展现的 可疑点 信息,上图中的内容表示 @15249 这个对象占据了堆空间 97.41% 的内存,那么它极可能就是一个泄漏对象,这里又存在两种可能:

  • 此对象自己应该被释放可是却没有释放,形成堆空间占用如此大
  • 此对象的某些属性应该被释放可是却没有释放,形成表象是此对象占据大量的堆空间

要判断是哪种状况,以及追踪到对应的代码段,咱们须要点击图中的 簇视图 连接进行进一步观察:

这里继续解释下什么是簇视图,簇视图其实是支配树的一个别名,也就是这个视图下咱们看到的正是前面一节中提到的从可疑泄漏对象出发的支配树视图,它的好处是,在这个视图下,父节点的 Retained Size 能够直接由其子节点的 Retained Size 累加后再加上父节点自身的 Shallow Size 获得,换言之,在这个视图下咱们层层展开便可以看到可疑泄漏对象的内存究竟被哪些子节点占用了。

而且结合前一节的支配树描述,咱们能够知道支配树下的父子节点关系,并不必定是真正的堆上空间内的对象父子关系,可是对于那些支配树下父子关系在真正的堆空间内也存在父子节点关系的簇节点,咱们将真正的  也用浅紫色标识出来,这部分的  信息对于咱们映射到真正的代码段很是有帮助。在这个简单的例子中,咱们能够很清晰的看到可疑泄漏对象 @15249 其实是下属的 test-alinode.js 中存在一个 array 变量,其中存储了四个 45.78 兆的数组致使的问题,这样就找到了问题代码能够进行后续优化。

而在实际生产环境的堆快照分析下,不少状况下簇视图下的父子关系在真实的堆空间中并不存在,那么就不会有这些紫色的边信息展现,这时候咱们想要知道可疑泄漏对象如何经过 JavaScript 生成的对象间引用关系引用到后面真正占据掉堆空间的对象(好比上图中的 40 多兆的 Array 对象),咱们能够点击 可疑节点自身的地址连接 :

这样就进入到以此对象为起点的堆空间内真正的对象引用关系视图 Search 视图

这个视图由于反映的是堆空间内各个 Heap Object 之间真正的引用链接关系,所以父对象的 Retained Size 并不能直接由子节点的 Retained Size 累加获取,如上图红框内的内容,显然这里的三个子节点 Retained Size 累加已经超过 100%,这也是 Search 视图和簇视图很大的不一样点。借助于 Search 视图,咱们能够根据其内反映出来的对象和边之间的关系来定位可疑泄漏对象具体是在咱们的 JavaScript 代码中的哪一部分生成。

其实看到这边,一些读者应该意识到了这里的 Search 视图实际上对应的就是前一节中提到的 Chrome devtools 的 Containment 视图,只不过这里的起始点是咱们选中的对象自己罢了。

最后就是须要提一下 Retainers 视图,它和前一节中提到的 Chrome devtools 中解析堆快照展现结果里面的 Retainers 含义是一致的,它表示对象的父引用关系链,咱们能够来看下:

这里 globa@1279 对象的 clearImmediate 属性指向 timers.js()@15325,而 timers.js()@15325 的 context 属性指向了可疑的泄漏对象 system / Context@15249。

在绝大部分的状况下,经过结合 Search 视图 和 Retainers 视图 咱们能够定位到指定对象在 JavaScript 代码中的生成位置,而 簇视图 下咱们又能够比较方便的知道堆空间被哪些对象占据掉了,那么综合这两部分的信息,咱们就能够实现对线上内存泄漏的问题进行分析和代码定位了。

e. 出现核心转储

最后就是收到服务器生成核心转储文件(Core dump 文件)的告警了,这表示咱们的进程已经出现了预期以外的 Crash,若是你的 Agenthub 配置正常的话,在 文件 -> Coredump 文件 页面会自动将生成的核心转储文件信息展现出来:

和以前的步骤相似,咱们想要看到服务端分析和结果展现,首先须要将服务器上生成的核心转储文件转储到云端,可是与以前的 CPU Profile 和堆快照的转储不同的地方在于,核心转储文件的分析须要咱们提供对应 Node.js 进程的启动执行文件,即 AliNode runtime 文件,这里简化处理为只须要设置 Runtime 版本便可:

点击 设置 runtime 版本 便可进行设置,格式为 alinode-v{x}.{y}.{z} 的形式,好比 alinode-v3.11.5,版本会进行校验,请务必填写你的应用真实在使用的 AliNode runtime 版本。版本填写完成后,咱们就能够点击 转储 按钮进行文件转储到云端的操做了:

显然对于核心转储文件来讲,Chrome devtools 是没有提供解析功能的,因此这里只有一个 AliNode 定制的分析按钮,点击这个 分析 按钮,便可以看到结果:

这里第一栏的概览信息看文字描述就能理解其含义,因此这里就再也不多作解释了,咱们来看下比较重要的默认视图 BackTrace 信息视图,此视图下展现的其实是 Node.js 应用在 Crash 时刻的线程信息,许多开发者认为 Node.js 是单线程的运行模型,其实这句话也不是彻底错误,更准确的说法是 单主 JavaScript 工做线程,由于实际上 Node.js 还会开启一些后台线程来处理诸如 GC 里的部分任务。

绝大部分的状况下,应用的 Crash 都是由 JavaScript 工做线程引起的,所以咱们须要关注的也仅仅是这个线程,这里显然 BackTrace 信息视图中将 JavaScript 工做线程作了标红和置顶处理,展开后能够看到 Node.js 应用 Crash 那一刻的错误堆栈信息:

由于就算在 JavaScript 的工做线程中,也会存在 Native C/C++ 代码的穿透,可是在问题排查中咱们每每只须要去看一样标红的 JavaScript 栈信息便可,像在这个简单的例子中,显然 Crash 是由于视图去启动一个不存在的 JS 文件致使的。

值得一提的是,核心转储文件的分析功能很是的强大,由于在预备节中咱们提到其生成的途径除了 Node.js 应用 Crash 的时候由系统内核控制输出外,还能够由 gcore 这样的命令手动强制输出,而本小节咱们又看到核心转储文件的分析实际上能够看到此刻的 JavaScript 栈信息以及其入参,结合这两点,咱们能够在线上出现 CPU Profile 一节中提到的类死循环问题时直接采用 gcore 生成核心转储文件,而后上传至平台云端进行分析,这样不只能够看到咱们的 Node.js 应用是阻塞在哪一行的 JavaScript 代码,甚至引起阻塞的参数咱们也能完整获取到,这对本地复现定位问题的帮助无疑是无比巨大的。

结尾

本节其实给你们介绍了 Node.js 性能平台 的整套面向 Node.js 应用开发的监控、告警、分析和定位问题的解决方案的架构和最佳实践,但愿能让你们对平台的能力和如何更好地结合自身项目进行使用有一个总体的理解。

限于篇幅,最佳实践中的 CPU Profile、堆快照和核心转储文件的分析例子都很是的简单,这部分的内容也更多的是旨在帮助你们理解平台提供的工具如何使用以及其分析结果展现的指标含义,那么本书的第三节中,咱们会经过一些实际的生产遇到的案例问题借助于 Node.js 性能平台 提供的上述工具分析过程,来帮助你们更好的理解这部分信息,也但愿你们在读完这些内容后能有所收获,能对 Node.js 应用在生产中的使用更有信心。



本文做者:奕钧

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索