PM2 生产环境进程管理:从零停机部署到常见故障排查
PM2 是 Node.js 应用最成熟的生产级进程管理器。它解决了开发者在生产环境中面临的几个核心痛点:应用崩溃后自动恢复、利用多核 CPU 提升吞吐量、零停机更新代码、以及统一的日志和监控。本文聚焦于实战配置、参数详解和典型报错排查,避免空泛介绍。
安装与快速上手
全局安装 PM2 后,即可启动任意 Node.js 脚本。
BASHnpm install pm2@latest -g
启动一个应用:
BASHpm2 start app.js --name my-api
查看进程状态:
BASHpm2 list
查看实时日志:
BASHpm2 logs my-api
停止并删除进程:
BASHpm2 stop my-api && pm2 delete my-api
核心参数详解与配置示例
PM2 的灵活性体现在丰富的启动参数上。以下表格列出最常用的参数及其生产环境典型用法。
| 参数 | 是否必填 | 说明 | 生产环境建议 |
|---|---|---|---|
--name | 否 | 指定应用名称,便于管理 | 始终使用,避免混淆 |
--watch | 否 | 监听文件变化并自动重启 | 生产环境禁用,避免意外重启 |
--max-memory-restart | 否 | 设置内存阈值,超过后自动重启(如 <200MB>) | 建议设置,防止内存泄漏耗尽服务器 |
--log | 否 | 指定日志文件路径 | 配合 pm2-logrotate 使用,避免磁盘写满 |
--restart-delay | 否 | 自动重启前的延迟(毫秒) | 建议设置 3000-5000ms,给应用初始化时间 |
--time | 否 | 在日志行前添加时间戳 | 建议启用,便于问题定位 |
--no-autorestart | 否 | 禁止自动重启 | 仅用于调试,生产环境不要使用 |
--cron | 否 | 指定 cron 表达式强制重启 | 可用于定期清理内存泄漏 |
--no-daemon | 否 | 前台运行(不后台化) | 仅用于调试 |
-i | 否 | 启用集群模式,指定实例数(0/max 表示所有 CPU,-1 表示所有 CPU 减 1) | 多核服务器建议使用 -i max |
--no-vizion | 否 | 禁用版本控制集成 | 如果不需要版本信息,建议禁用以提升性能 |
生产环境启动示例:
BASHpm2 start app.js \ --name production-api \ -i max \ --max-memory-restart 200M \ --restart-delay 5000 \ --time \ --no-vizion
Ecosystem File(推荐)
对于复杂配置,建议使用 ecosystem.config.js 文件统一管理。
JAVASCRIPTmodule.exports = { apps: [{ name: 'production-api', script: './app.js', instances: 'max', exec_mode: 'cluster', max_memory_restart: '200M', restart_delay: 5000, time: true, env: { NODE_ENV: 'production', PORT: 3000 } }] };
启动方式:
BASHpm2 start ecosystem.config.js
与同类方案对比
| 特性 | PM2 | forever | nodemon | systemd |
|---|---|---|---|---|
| 进程保活 | ✅ | ✅ | ❌(开发用) | ✅ |
| 集群模式(多核) | ✅ 内置 | ❌ | ❌ | ❌ |
| 零停机重载 | ✅ pm2 reload | ❌ | ❌ | ❌ |
| 内置监控 | ✅ pm2 monit | ❌ | ❌ | ❌ |
| 启动脚本 | ✅ pm2 startup | ❌ | ❌ | ✅ 原生 |
| 配置复杂度 | 低 | 低 | 极低 | 高 |
| 日志管理 | ✅ 内置 + 插件 | ❌ | ❌ | ❌ |
| 扩缩容 | ✅ pm2 scale | ❌ | ❌ | ❌ |
结论:forever 和 nodemon 功能过于简单,不适合生产环境。systemd 是系统级工具,配置繁琐且缺乏应用级特性。PM2 在单机或小规模部署场景下是最优选择。对于大规模容器化部署,应使用 Docker/K8s。
生产环境实践与注意事项
1. 无状态应用要求
PM2 集群模式使用轮询算法分发请求。如果应用在内存中存储 Session、WebSocket 连接或本地缓存,不同进程间的数据会不一致。
解决方案:使用外部存储共享状态,例如 Redis 存储 Session。
JAVASCRIPTconst session = require('express-session'); const RedisStore = require('connect-redis')(session); app.use(session({ store: new RedisStore({ host: 'localhost', port: 6379 }), secret: 'your-secret-key', resave: false, saveUninitialized: false }));
2. 文件锁定与并发写入
多个工作进程同时写入同一个文件可能导致数据损坏。
解决方案:使用专门的日志库(如 winston 配合 file 传输)或数据库处理并发写入。
3. 端口冲突
在集群模式下,PM2 会自动共享端口。不要手动指定 --port 参数,否则每个进程会尝试监听不同端口。
4. 内存泄漏处理
--max-memory-restart 可以自动重启超过内存阈值的进程,但无法修复根本原因。建议结合 heapdump 分析内存快照。
5. 安全建议
- 不要以 root 用户运行 PM2,创建专用用户(如
node)。 - 使用
pm2 startup时确保脚本权限正确。 - 安装
pm2-logrotate限制日志文件大小:pm2 install pm2-logrotate。 - 生产环境禁用
--watch。
6. 服务器重启后自动恢复
BASH# 生成启动脚本(以非 root 用户运行) pm2 startup # 保存当前进程列表 pm2 save
重启服务器后,使用 pm2 list 验证进程是否自动恢复。
常见报错与排查
错误 1:Error: listen EADDRINUSE :::3000
原因:端口 3000 已被占用。
解决步骤:
- 查找占用进程:
lsof -i :3000或netstat -tulpn | grep 3000 - 终止进程:
kill -9 <PID> - 如果是在集群模式下,确保没有手动指定
--port参数。
错误 2:PM2: Script '/path/to/app.js' had too many unstable restarts (16). Stopped. "errored"
原因:应用启动后立即崩溃,PM2 停止尝试重启。
解决步骤:
- 查看详细错误日志:
pm2 logs <app-name> - 手动运行
node app.js测试应用是否能正常启动。 - 增加
--restart-delay参数,给应用更多初始化时间。
错误 3:PM2: Cannot find module '/path/to/app.js'
原因:PM2 找不到指定的脚本文件。
解决步骤:
- 确认脚本路径是否正确,使用绝对路径或相对于 PM2 工作目录的路径。
- 使用
pwd查看当前工作目录,然后使用ls确认文件是否存在。 - 在 Ecosystem File 中设置
cwd选项指定工作目录。
错误 4:PM2: Error: listen EACCES :::80
原因:没有权限监听 80 端口(需要 root 权限)。
推荐解决方案:使用 Nginx 作为反向代理,监听 80 端口,将请求转发到 PM2 管理的应用端口(如 3000)。
NGINXserver { listen 80; server_name example.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
不推荐:使用 sudo pm2 start app.js(存在安全风险)。
常见问题 FAQ
Q: PM2 的 restart 和 reload 有什么区别?什么时候该用哪个?
A: restart 会立即杀死所有工作进程,然后重新启动它们,导致短暂停机。reload 逐个重启工作进程,实现零停机。
- 开发环境或允许短暂停机:使用
restart。 - 生产环境:使用
reload。
注意:reload 要求应用无状态,并正确处理 SIGINT 信号。如果超时未关闭,PM2 会回退到 restart。
Q: 如何让 PM2 管理的应用在服务器重启后自动启动?
A: 使用 pm2 startup 和 pm2 save。
- 生成启动脚本:
pm2 startup - 保存当前进程列表:
pm2 save
如果遇到权限问题,可以尝试:
BASHsudo env PATH=$PATH:/usr/bin pm2 startup systemd -u <your-username> --hp /home/<your-username>
Q: PM2 集群模式下,如何确保多个进程之间的 Session 共享?
A: 使用外部 Session 存储,如 Redis、MongoDB 或数据库。示例(Express + Redis)见上文“无状态应用要求”部分。