Nodejs一次CPU占用飙升问题分析

背景

一台服务器用pm2管理的多个服务,在流量聚集的时候偶现 cpu占用过高 导致响应超时。由于是接手维护的项目,对项目里面所有的调用不是很熟悉,根据当时的日志排查了几个接口没找到原因。

本文主要是记录一下处理步骤以及中间遇到的一些问题。

结论

先说结论:由于部分阻塞计算,在高并发时的接口拥堵导致 cpu占用 飙升;

对异步编程来说,优点是高并发,但这个前提是非阻塞,当系统中有复杂计算(e.g. 业务系统处理大量计算,上传文件等),高并发时很容易出问题。

对有需要复杂计算的情况,除了基本逻辑的处理,需要考虑:

1、多开几个服务(e.g. pm2的cluster模式);

2、通过另外的 进程/线程 来处理复杂逻辑,不阻塞主进程;

问题排查

1、根据当时时间段分析日志,将一些可能导致cpu占用的接口在测试环境进行并发测试;

这个事情很费时间,虽然能分析解决了一些接口有的性能问题,但我们在测试环境没有环境模拟出来这种高并发的情况,主要问题还在;

2、我们的cpu占用表现是瞬间飙升,然后很快降下来,但在高并发的时候出现过两三次一直降不下来的情况;

阿里云服务器的 cpu资源占用 监控,绝大部分情况下无法监控到cpu资源占用的数据,因为他是1分钟采集一次占用数据的。

我们需要一个更精细的监控工具,暂时是自己写一个简单的监控去查看分析cpu占用情况;

3、多服务器负载,这个是最后方案。

但由于我们的服务有一个重要业务是通过 websocket 通信的,当没有做好 websocket消息分发 的时候进行多台服务器负载,总会有一些消息发送异常的情况。

cpu profile

因为实在没办法从日志分析到是哪个 接口/方法 导致的cpu占用过高。

只能将 cpu profile(用的是v8-profiler-next) 放到线上进行记录,一开始我很担心这样会占用更多资源。但在测试环境测试了几次并发感觉还好(在我当前项目情况下,基本看不出来影响,实际上可能需要根据项目的并发程度考虑)。

1、代码主要逻辑是,每五分钟记录一次,一次记录四分钟 cpu调用过程:

javascript 复制代码
cron.schedule('*/5 * * * *', () => {
  const now = new Date();
  const pad = (n) => (n < 10 ? '0' : '') + n; // 补零函数
  const profileName = `cpu-profile-${pid}-${String(now.getFullYear()).slice(2)}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}`;
  const profileFilePath = path.join(profileDir, `${profileName}.cpuprofile`);
  console.log(`[profile] Starting CPU profiling: ${profileFilePath}`);

  // 启动 CPU Profile 记录
  startProfiling(profileName);
  // 记录 4 分钟
  setTimeout(() => {
    // 停止 CPU Profile 记录
    const profile = stopProfiling(profileName);

    // 保存 profile 数据到文件
    fs.writeFileSync(profileFilePath, JSON.stringify(profile));
    console.log(`[profile] CPU profile saved at: ${profileFilePath}`);
  }, 240000);
});

2、分析工具,我原本打算用chrome的dev-tools进行分析,但这个记录的文件无法解析,所以只能通过 speedscope 进行分析。

其实有好几个地方有问题,我举其中一个例子说明:

这是一个上传文件的的方法,可以看到这个服务阻塞了主进程接近3s的时间,后面堆积的任务就会导致cpu瞬间的飙升:

cpu占用分析工具

javascript 复制代码
const pm2 = require('pm2');
const fs = require('fs');

// 定义阈值
const cpuThreshold = 20;  // CPU 占用超过 50% 时记录日志
const reloadThreshold = 95; // CPU 占用超过 95% 时重启进程

// 日志文件路径
const logFilePath = './cpuUsage.log';

// 启动 PM2 并获取所有进程的 CPU 使用情况
pm2.connect((err) => {
  if (err) {
    console.error('PM2 connection error:', err);
    return;
  }

  // 定时检查 PM2 中所有进程的 CPU 使用情况
  setInterval(() => {
    pm2.list((err, processList) => {
      if (err) {
        console.error('Error getting process list:', err);
        return;
      }

      processList.forEach((process) => {
        const pid = process.pid;
        const cpuUsage = process.monit.cpu;  // 获取进程的 CPU 占用
        const name = process.name;

        // 如果 CPU 占用超过阈值,记录到日志文件中
        if (cpuUsage > cpuThreshold) {
          const logMessage = `${new Date().toISOString()} - PID: ${pid}, Name: ${name}, CPU Usage: ${cpuUsage}%\n`;
          fs.appendFileSync(logFilePath, logMessage);  // 将日志写入文件
          console.log(logMessage);  // 输出到控制台
        }

        // 如果 CPU 占用超过重启阈值,执行重启,考虑
     });
    });
  }, 5000); // 每 10 秒检查一次
});

cpu profile

通过插件 v8-profiler-next 提供的 startProfiling, stopProfiling 接口每五分钟记录一次数据

问题记录

1、v8-profiler-next记录的文件格式无法在dev-tools上进行查看,只能通过 speedscope来进行分析;

相关推荐
防火墙在线3 小时前
前后端通信加解密(Web Crypto API )
前端·vue.js·网络协议·node.js·express
水冗水孚8 小时前
效能工具(九)之编写nodejs脚本使用get-video-duration批量读取视频时长,并生成sql语句修复数据库表字段值
sql·node.js
huangql5209 小时前
Vite与Webpack完全指南:从零开始理解前端构建工具
前端·webpack·node.js
api_180079054609 小时前
【技术教程】Python/Node.js 调用拼多多商品详情 API 示例详解
大数据·开发语言·python·数据挖掘·node.js
用户479492835691513 小时前
你知道node背后的libuv是什么吗
node.js
pixle017 小时前
从零学习Node.js框架Koa 【一】 Koa 初探从环境搭建到第一个应用程序
前端·node.js·web·koa.js·web全栈·node服务端框架
Moment18 小时前
为什么我们从 Python 迁移到 Node.js
前端·后端·node.js
冴羽1 天前
为什么在 JavaScript 中 NaN !== NaN?背后藏着 40 年的技术故事
前端·javascript·node.js
IT古董1 天前
全面理解 Corepack:Node.js 的包管理新时代
前端·node.js·corepack
Jonathan Star1 天前
NestJS 是基于 Node.js 的渐进式后端框架,核心特点包括 **依赖注入、模块化架构、装饰器驱动、TypeScript 优先、与主流工具集成** 等
开发语言·javascript·node.js