人人都会AI编程

19.4 线上监控与排错

更新时间:2026-07-11

将 Node.js 应用部署到生产环境只是第一步,保证它能够持续稳定、高性能地运行才是运维的核心目标。与开发阶段不同,线上问题往往没有交互式调试环境,也无法随意重启服务,因此需要提前建立一套可靠的监控与排错体系。本节从日志、性能指标、错误告警、以及常见瓶颈(内存泄漏、CPU 飙高)的排查方法四个方面,介绍如何在生产环境中维护 Node.js 服务。

19.4.1 日志体系:可读、可查、不丢失

日志是排查问题的最基本依靠。生产环境的日志需要满足几个要求:分级明确、格式统一、输出高效、集中存储。

日志分级与输出格式

使用结构化日志库,例如 pino(性能极高)或 winston,按 errorwarninfodebug 等级别记录日志。在 Node.js 中推荐使用 JSON 格式输出,便于后续日志系统索引与检索。例如:

const pino = require('pino');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });

// 记录请求日志
logger.info({ method: req.method, url: req.url, status: res.statusCode }, 'request completed');
// 记录错误,附带堆栈信息
logger.error({ err, requestId }, 'database connection failed');

pino 自带极低序列化开销,当与 pino-pretty 搭配用于本地开发时会输出可读格式,但在生产环境应保留原始 JSON 并交给集中式日志平台(如 ELK、Splunk、Loki)去解析。

日志切割与多目标输出

生产环境中单一日志文件会快速膨胀,可通过 pino 的传输模块或系统工具(如 logrotate)进行切割。另外,关键错误日志除了写入文件外还应直接输出到 stdout/stderr,以便容器编排平台(Kubernetes、Docker)统一抓取。

集中式日志的实践

对于微服务或多节点部署,必须将所有服务日志汇聚到集中式系统。常见的开源方案有 ELK(Elasticsearch + Logstash + Kibana)或 Grafana Loki。每个 Node.js 实例只需将日志以 JSON 格式发送到 stdout,再由 Filebeat 或 Fluentd 转发至中心存储。在 Kibana 或 Grafana 中,可以按 requestIduserId 等字段快速串联一次请求的完整链路日志。

19.4.2 性能监控:洞悉系统脉搏

日志告诉我们“发生了什么”,监控指标则告诉我们“正在发生什么”。Node.js 线上运行需要关注至少以下四类指标:

进程级别指标

  • CPU 使用率(user、system 占比)
  • 内存占用(RSS、heapUsed、heapTotal、external)
  • 事件循环延迟(lag):测量任务被调度的延迟,延迟过高会导致请求响应变慢
  • 活跃句柄数与文件描述符数(防止句柄泄漏)
  • 请求吞吐量(RPS)与响应时间(P50、P95、P99)

采集方式

  • PM2 内嵌监控pm2 monit 可实时查看每个进程的 CPU/内存/事件循环延迟,并可通过 pm2 server:monit 启动 Web 监控面板。适合小规模部署。
  • 应用性能管理(APM)工具:如 Elastic APM、Datadog、New Relic、Sentry Performance 等提供 Node.js 客户端库,通过 require 引入即可自动收集数据库查询、外部 HTTP 请求、中间件耗时等细节,并生成服务拓扑与慢端点分析。
  • 开源自建方案:使用 prom-client(Prometheus 客户端)暴露 /metrics 端点,再用 Prometheus 抓取,最终通过 Grafana 制作大盘。这是目前云原生环境下的主流方案。

一个暴露基础指标的示例:

const client = require('prom-client');
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ prefix: 'myapp_' });

// 自定义指标:请求计数器
const httpRequestsTotal = new client.Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status'],
});

在 Grafana 中可基于这些指标可视化 QPS、错误率、内存趋势等,并设置阈值告警。

事件循环延迟监控

事件循环延迟是 Node.js 健康度的一个重要预警指标。可使用 toobusy-js 或自行监测 setImmediate 的时间差来判断是否过载:

let lastTime = Date.now();
setInterval(() => {
  const now = Date.now();
  const loopDelay = now - lastTime - 1000; // 如果该回调延迟超过 1 秒,则差值>0
  lastTime = now;
  if (loopDelay > 100) {
    logger.warn({ loopDelay }, 'event loop delayed');
  }
}, 1000);

更精确的方式是使用 perf_hooks.monitorEventLoopDelay(),可以得到事件循环延迟的直方图统计。

19.4.3 错误告警:第一时间响应

在生产环境中,未被捕獲的异常会导致进程退出;而逻辑错误即使不崩溃也可能造成业务异常。需要建立多层级告警通道。

全局异常捕获与进程守护

代码中应使用 process.on('uncaughtException', handler)process.on('unhandledRejection', handler) 记录致命错误日志后再优雅退出,让 PM2 或 Kubernetes 自动重启。但是不应在此类 handler 中尝试恢复应用状态,因为进程可能已经不稳定。

错误收集与发送告警

推荐使用 SentryFundebug 等错误跟踪服务。它们提供了 Node.js SDK,可捕获并聚合错误,附带请求上下文、堆栈和用户环境信息,并支持邮件、Slack、钉钉等即时通知。集成方式很简单:

const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'your-dsn' });

对于业务自定义错误,可手动调用 Sentry.captureException(error)。同时可以将 Sentry 与日志系统打通,用 Sentry 事件 ID 关联详细日志。

告警分级与降噪

设置合理的告警阈值,避免“狼来了”。例如:接口报错率连续 5 分钟 >5% 时触发警告;内存使用率持续增长超过 80% 时告警。利用监控系统的告警规则配置(如 Prometheus Alertmanager)可以细粒度管理告警路由与静默。

19.4.4 常见性能问题排查

当监控指标显示异常时(如内存持续上涨、CPU 使用率突然飙高),需要针对性地排查根因。

内存泄漏排查

内存泄漏在 Node.js 中通常表现为 RSS 稳步增长,最终导致 OOM 崩溃。常见原因有:闭包中意外持有大对象引用、事件监听器未移除、定时器未清除、流未正确消费等。

定位步骤:

  1. 生成 heap snapshot:在检测到内存升高时,通过 v8.writeHeapSnapshot() 或使用 heapdump 模块生成堆快照。可以在应用内添加一个受保护的、仅内网可访问的触发接口。
  2. 对比快照:使用 Chrome DevTools 的 Memory 面板,加载两个时间点的快照,对比两者之间哪些对象数量和大小激增,找出可疑的构造函数或字符串。
  3. 结合应用上下文:一旦通过快照定位到哪类对象(如数据库连接、缓存对象)在累积,便可回溯代码中相应对象的生命周期管理。

对于线上难以生成快照的情况,可以使用 clinic 工具的 clinic doctor,通过采样生成分析报告,它通常能指出内存泄漏的嫌疑区域。

CPU 占用过高排查

高 CPU 常意味着事件循环被密集计算阻塞,导致服务响应变慢甚至超时。可能的原因:巨大的同步循环、复杂正则表达式、JSON 序列化/反序列化大对象、缺少限流的加密操作等。

排查工具与流程:

  • 使用 --prof 生成 V8 日志:启动应用时加入 node --prof app.js,会生成 isolate-.log 文件,再通过 node --prof-process isolate-.log 转换成人类可读的分析报告,查看最耗时的 C++/JS 函数调用栈。
  • 使用火焰图:推荐使用 0x 工具,它自动采样 CPU profile 并生成火焰图。
  npm install -g 0x
  0x -o app.js
  

火焰图中宽度最大的“平顶”就是 CPU 热点路径,可直接定位到具体的代码函数。

  • 使用 clinic flameclinic flame -- node app.js 启动后模拟流量,它会生成一份火焰图报告,直观展示事件循环中被耗时的同步操作。
  • 使用 Chrome DevTools:可以在本地复现场景,用 node --inspect app.js 启动,然后通过 Chrome 的 chrome://inspect 连接,使用 Profiler 开始录制 CPU profile,操作线上同样的逻辑,然后分析函数耗时。

在 Node.js 中,一个常见的 CPU 杀手是同步阻塞的大规模计算。例如,一次返回数万条数据的数据库查询后用 JSON.stringify 同步转换,占用 CPU 数百毫秒。这类问题可以通过流式处理、分页、或转移到 Worker 线程来解决。

19.4.5 建立排错文化

监控与排错最终离不开团队的响应流程。建议:

  • 将监控大盘作为团队每日第一眼:关键服务指标应投射到办公室大屏或团队 IM 频道,形成习惯。
  • 每次故障后产出复盘报告:记录报警过程、定位步骤、根因、修复措施,并归档到内部知识库,避免重复踩坑。
  • 定期进行故障演练:在预发环境人为注入内存泄漏或 CPU 高负载,演练团队的发现、响应、恢复流程。

线上监控与排错不是一次性配置,而是一个随着业务迭代持续演进的体系。借助 Node.js 生态丰富的监控 SDK 和诊断工具,我们完全可以将生产环境的黑盒逐渐透明化,在问题影响用户之前就发现并解决它。