Node.js生产部署完全指南:PM2集群模式 + 内存泄漏排查 + 香港服务器零停机发布

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 崩溃后排查要高效得多。

Telegram