引言:为什么监控对 Node.js 应用程序至关重要
现代基于 JavaScript (Node.js) 的 Web 应用程序在高负载和分布式架构下运行。即使是完美编写的代码也可能因内存泄漏、性能下降或异步操作错误而出现故障。对 JavaScript 应用程序进行监控不仅可以发现问题,还可以在问题影响用户之前加以预防。
在本文中,我们将分析需要跟踪哪些指标,如何使用流行工具 (Prometheus, Grafana) 配置数据收集,以及如何为 Node.js 组织告警系统。所有代码示例均使用 JavaScript 编写,可直接使用。
Node.js 应用程序的关键指标
Node.js 应用程序的监控可以分为三个层次:基础设施层、应用层和业务指标。我们来逐一分析。
基础设施指标
这些指标显示服务器和运行时的状态:
- CPU 和 Memory (RSS, Heap Total, Heap Used) — 内存泄漏和 CPU 高负载。
- Event Loop Lag — 事件循环延迟(如果 >50ms,应用程序开始变慢)。
- Garbage Collection (GC) — 垃圾回收的频率和持续时间。
- File Descriptors — 打开的文件和套接字数量。
应用层指标
显示应用程序如何处理请求:
- RPS (Requests Per Second) — 每秒请求数。
- Latency (p50, p95, p99) — 响应延迟。
- Error Rate — 错误百分比 (4xx, 5xx)。
- Active Connections — 活跃的 WebSocket/HTTP 连接数。
业务指标
特定于您的应用程序:
- 注册用户数量。
- 支付处理速度。
- 外部 API 的响应时间。
使用 Prometheus 和 Node.js 收集指标
Prometheus 是现代基础设施中收集指标的事实标准。对于 Node.js,有一个名为 prom-client 的库,可以轻松导出指标。
安装和配置 prom-client
npm install prom-client express创建一个带有指标的基础服务器:
const express = require('express');const client = require('prom-client');const app = express();
// 创建指标注册表const register = new client.Registry();client.collectDefaultMetrics({ register });
// 创建自定义延迟直方图const httpRequestDuration = new client.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'], buckets: [0.1, 0.5, 1, 2, 5]});register.registerMetric(httpRequestDuration);
// 用于测量延迟的中间件app.use((req, res, next) => { const end = httpRequestDuration.startTimer(); res.on('finish', () => { end({ method: req.method, route: req.route?.path || 'unknown', status_code: res.statusCode }); }); next();});
// 指标端点app.get('/metrics', async (req, res) => { res.set('Content-Type', register.contentType); res.end(await register.metrics());});
app.get('/', (req, res) => { res.send('Hello World');});
app.listen(3000, () => console.log('Server running on port 3000'));收集 Event Loop 指标
为了监控事件循环,我们使用 process.hrtime() 或 event-loop-lag 库:
const eventLoopLag = new client.Gauge({ name: 'nodejs_event_loop_lag_seconds', help: 'Event loop lag in seconds'});register.re