Node.js生产部署完全指南:PM2集群模式 + 内存泄漏排查 + 香港服务器零停机发布
Node.js 单进程默认只能利用一个 CPU 核心,在 4核8G 香港服务器上只发挥了 25% 的计算能力。PM2 是 Node.js 最成熟的进程管理器,通过集群模式自动启动多个 Worker 进程充分利用所有 CPU 核心,同时提供零停机重启、日志管理、内存监控和开机自启等生产必备功能。本文覆盖从基础部署到内存泄漏排查的完整生产实践。
一、安装 PM2
<code"># 全局安装 PM2 npm install -g pm2 # 验证安装 pm2 --version # 安装日志轮转模块(防止日志文件无限增长) pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 100M pm2 set pm2-logrotate:retain 14 # 保留14天的日志
二、PM2 配置文件(ecosystem.config.js)
<code">// ecosystem.config.js(放在项目根目录)
module.exports = {
apps: [
{
name: 'myapi',
script: './src/server.js',
// ── 集群模式(充分利用多核 CPU)──
instances: 'max', // 自动等于 CPU 核数
// instances: 4, // 或者指定数量
exec_mode: 'cluster', // 必须设置为 cluster
// ── 工作目录和环境 ──
cwd: '/opt/myapp',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: process.env.DATABASE_URL,
REDIS_URL: process.env.REDIS_URL,
},
// ── 内存泄漏保护(超过阈值自动重启)──
max_memory_restart: '512M', // 单个 Worker 超过 512MB 自动重启
// ── 崩溃重启策略 ──
restart_delay: 3000, // 崩溃后等待 3 秒再重启
max_restarts: 10, // 10分钟内最多重启10次
min_uptime: '10s', // 运行少于10秒视为不稳定启动
// ── 日志配置 ──
output: '/var/log/myapp/out.log',
error: '/var/log/myapp/err.log',
merge_logs: true, // 集群模式下合并所有 Worker 日志
log_date_format: 'YYYY-MM-DD HH:mm:ss',
// ── 优雅关闭 ──
kill_timeout: 5000, // 等待 5 秒让进程优雅退出
listen_timeout: 10000, // 等待 10 秒监听就绪
// ── 监控 ──
instance_var: 'INSTANCE_ID', // 集群实例 ID 注入环境变量
},
// 可以在同一配置文件管理多个应用
{
name: 'worker',
script: './src/worker.js',
instances: 2,
exec_mode: 'cluster',
env_production: { NODE_ENV: 'production' }
}
]
};
三、应用代码:优雅关闭处理
<code">// src/server.js
const express = require('express');
const app = express();
// ... 路由配置 ...
const server = app.listen(process.env.PORT || 3000, () => {
console.log(`Worker ${process.pid} 已启动,监听端口 ${process.env.PORT}`);
});
// ── 优雅关闭(PM2 发送 SIGINT/SIGTERM 信号时)──
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
async function gracefulShutdown(signal) {
console.log(`收到 ${signal} 信号,开始优雅关闭...`);
// 停止接受新连接
server.close(async () => {
console.log('HTTP 服务已关闭,处理剩余请求...');
try {
// 等待进行中的数据库操作完成
await db.end();
await redis.quit();
console.log('数据库连接已关闭');
process.exit(0);
} catch (err) {
console.error('关闭时出错:', err);
process.exit(1);
}
});
// 超时强制退出(配合 PM2 的 kill_timeout)
setTimeout(() => {
console.error('优雅关闭超时,强制退出');
process.exit(1);
}, 4500); // 比 kill_timeout(5000) 少 500ms
}
// ── 集群模式下的进程信息 ──
app.get('/health', (req, res) => {
res.json({
status: 'ok',
pid: process.pid,
worker_id: process.env.INSTANCE_ID,
memory: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB',
uptime: Math.round(process.uptime()) + 's',
});
});
四、常用 PM2 命令
<code"># ── 启动和停止 ── pm2 start ecosystem.config.js --env production # 生产环境启动 pm2 stop myapi # 停止应用 pm2 restart myapi # 重启(有短暂停机) pm2 reload myapi # 零停机重载(集群模式) pm2 delete myapi # 删除进程 # ── 查看状态 ── pm2 list # 进程列表 pm2 show myapi # 详细信息 pm2 monit # 实时监控(CPU/内存/日志) # ── 日志 ── pm2 logs myapi # 实时查看日志 pm2 logs myapi --lines 200 # 查看最近200行 pm2 flush myapi # 清空日志文件 # ── 开机自启 ── pm2 startup # 生成开机自启脚本(按提示执行) pm2 save # 保存当前进程列表(重启后恢复) pm2 resurrect # 恢复保存的进程列表
五、零停机滚动更新
<code"># 零停机更新流程(集群模式特有) # PM2 reload 逐个重启 Worker,保证始终有 Worker 在服务 # 方法一:直接 reload pm2 reload myapi # 依次重启每个 Worker,无停机时间 # 方法二:CI/CD 中的完整更新脚本 cat > /opt/myapp/deploy.sh << 'SCRIPT' #!/bin/bash set -euo pipefail APP_DIR="/opt/myapp" APP_NAME="myapi" echo "[$(date)] 开始部署..." cd $APP_DIR # 1. 拉取最新代码 git pull origin main # 2. 安装新依赖(生产依赖) npm ci --only=production # 3. 数据库迁移(如有) # node migrate.js # 4. 零停机重载 pm2 reload $APP_NAME --update-env # 5. 验证服务健康 sleep 3 curl -sf http://localhost:3000/health || exit 1 echo "[$(date)] 部署成功 ✓" SCRIPT chmod +x /opt/myapp/deploy.sh
六、内存泄漏排查
<code"># ── 症状识别 ──
pm2 monit # 观察内存是否持续增长(不下降)
# ── 生成堆内存快照 ──
npm install -g heapdump clinic
# 方法一:在代码中手动触发快照
const heapdump = require('heapdump');
process.on('SIGUSR2', () => {
const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, (err, filename) => {
if (!err) console.log('堆快照已保存:', filename);
});
});
# 方法二:在 PM2 中发送信号触发快照
pm2 sendSignal SIGUSR2 myapi<code"># ── 使用 clinic.js 自动分析性能问题 ── # 火焰图(找出 CPU 热点) clinic flame -- node src/server.js # Bubbleprof(分析 async 操作瓶颈) clinic bubbleprof -- node src/server.js # HeapProfiler(内存分析) clinic heapprofiler -- node src/server.js # 分析完成后用浏览器打开 .clinic 目录中的 HTML 报告
<code">// 常见内存泄漏模式与修复
// ❌ 泄漏:事件监听器未移除
class MyClass extends EventEmitter {
start() {
this.on('data', this.handleData); // 每次 start() 都添加一个监听器
}
}
// ✅ 修复
start() {
this.removeAllListeners('data'); // 先移除旧监听器
this.on('data', this.handleData);
}
// ❌ 泄漏:闭包引用大对象
let cache = {};
function processRequest(req) {
const bigData = loadBigData(); // bigData 被闭包捕获无法释放
cache[req.id] = () => bigData.result;
}
// ✅ 修复:使用 WeakMap 或 LRU Cache
const LRU = require('lru-cache');
const cache = new LRU({ max: 1000, ttl: 60000 });
// ❌ 泄漏:全局数组无限增长
const logs = [];
app.post('/log', (req, res) => {
logs.push(req.body); // logs 永远不清空
res.ok();
});
// ✅ 修复:写入数据库或使用环形缓冲区七、PM2 Plus 监控(可选)
<code"># PM2 Plus 提供云端监控面板(免费计划支持 4 个服务器) pm2 link 密钥 公钥 # 从 pm2.io 获取 # 在 pm2.io 可以看到: # - 所有进程的实时 CPU/内存图表 # - 异常告警推送 # - 远程重启/部署(无需 SSH) # - 代码异常追踪(Exception tracking)
八、总结
PM2 集群模式让 Node.js 在香港服务器上充分利用所有 CPU 核心,4核服务器可以运行 4 个 Worker 进程,吞吐量提升约 3~4 倍。结合优雅关闭处理和 pm2 reload 的零停机更新,实现了真正的生产级 Node.js 部署。内存泄漏是 Node.js 长期运行的最大隐患,定期用 heapdump 和 clinic.js 主动检测,比等待 OOM 崩溃后排查要高效得多。