web前端 - nodejs 容器异常排查方法(CPU & 内存)

541 阅读4分钟

nodejs 容器异常排查(CPU & 内存)

痛点分析

目前维护的某线上服务,主动上报+平台监控的体系较为完善,能覆盖90%以上的问题排查需求。

当服务出现出现CPU高负载或内存异常时能及时告警,具体定位时发现较为困难,往往只能通过测试环境设法复现,无现场CPU日志or快照可以分析。

CPU & 内存问题分析

分析CPU异常时一般用火焰图(www.brendangregg.com/flamegraphs…)、分析内存问题时通常是对比前后2个时间点的内存快照。

1.社区常用的工具与解决方案

工具用法简述备注
node-inspectorcommand-line启动node服务时加上–inspect参数,可以通过chrome工具中js profiler一栏直接查看和导出CPU情况、通过memory一栏采集内存快照。适用于本地开发,正式环境需要修改启动命令(涉及流水线自定义),以此方式启动node服务会增加日常开销
v8-profilernpmgithub.com/node-inspec…
官方依赖、引用后提供api可以导出CPU快照和内存快照
已经不维护, node12以上无法使用
v8-profiler-nextnpmgithub.com/hyj1991/v8-…
用法与v8-profiler类似
个人维护,目前已经支持到node19
perfcommand-lineperf.wiki.kernel.org/index.php/M…
linux内置工具,登录容器后手动使用perf工具分析查看cpu/内存情况。
登录容器使用,使用起来很麻烦

2.Node.inspector模块

阅读官方文档后发现Node中的Inspector模块可以创建session,发送消息给底层,同时官方文档中提到了如何使用该功能创建cpu和内存快照(nodejs.org/dist/latest…),在社区搜索方案时很少见到提及这一功能。

const inspector = require('node:inspector');
const fs = require('node:fs');
const session = new inspector.Session();
session.connect();


// 获取cpu快照
// 需要发送2个指令,开始Profiler.start,结束Profiler.stop,采集这段时间内的cpu信息
session.post('Profiler.enable', () => {
  session.post('Profiler.start', () => {
    // Invoke business logic under measurement here...

    // some time later...
    // 合理可以设置个timer,比如30s后发送Profiler.stop,这样获取30s内的CPU快照片
    
    settimeout(function() {
       
       session.post('Profiler.stop', (err, { profile }) => {
          // Write profile to disk, upload, etc.
          if (!err) {
            fs.writeFileSync('./profile.cpuprofile', JSON.stringify(profile));
          }
        });
    
    }, 30 * 1000);
  });
});

// 获取内存快照
const fd = fs.openSync('./profile.heapsnapshot', 'w');

session.connect();

session.on('HeapProfiler.addHeapSnapshotChunk', (m) => {
  fs.writeSync(fd, m.params.chunk);
});

const result = await session.post('HeapProfiler.takeHeapSnapshot', null);
console.log('HeapProfiler.takeHeapSnapshot done:', result);
session.disconnect();x
fs.closeSync(fd); 

亲自实验后发现node官方提供的这一方案可行,于是有以下方案定位线上容器异常:

1.在node服务中添加一个模块用于导出cpu和内存快照,通过接口调用。示例:git.woa.com/ott-nodejs/…

2.通过容器配置决定功能1是否开启。(常态开启的话对磁盘空间消耗很快,还需要额外维护日志文件新增和删除)

3.如果直接访问2的容器,本地生成cpu/内存快照文件。

4.使用chrome等工具分析快照。

分析工具

1.火焰图分析工具

可以导出chome或者用第三方工具转svg,这边推荐一个网站www.speedscope.app/直接可视化查看。

三种模式:timeorder按时间查看调用栈,left heavy将CPU占比高的调用从左至右排列,sandwich表格方式查看总体占用情况。

具体案例:

异常描述
node服务(express)某接口访问数短时间内翻了3倍,耗时明显增加,且cpu占有率急剧上升。

火焰图分析
image.png

1.使用timerorder模式观察:每次请求都会调用一个叫parserOnBody的方法处理请求报文(body-parser中间件),而部分请求的parserOnBody耗时很长(大于1s) 2.使用left heavy模式观察,parserOnBody方法占比最高,超过70%。

由于nodeJS是单线模型,一个请求的parserOnBody占用主线程时间过长,会阻塞其他请求,这样本来其他应该快速的响应的请求的响应时间也会被拖慢。

问题分析
该接口接受接收一个文件,排查后发现是调用方的异常,导致不仅请求量增加,部分文件大小也超出了限制(接口业务逻辑对文件大小做了限制,但是 express处理body会在具体的业务逻辑之前的中间件)。

解决建议
需要在具体的业务逻辑代码前对问题请求(文件过大)做拦截,可以是框架层面(需要保证在body-parser前,不过这需要知道上传文件的大小),或者做上层保护,在接入层就对请求做限制。

2.内存快找分析工具

导入chrome,分析前后两次快照内存变化即可。使用chrome分析内存的方法参照:developer.chrome.com/docs/devtoo…