PM2 实用配置:小内存机器的进程守护方案
PM2 不只是"让进程后台运行"。配好内存上限重启、日志轮转和开机自启,才算真正把服务交给它管。
#PM2 解决的是什么问题
直接 node server.js 有四个致命问题:
- 关掉 SSH 进程就没了 —— 需要
nohup或 systemd - 崩溃后不会自动恢复 —— 半夜挂了没人知道
- 重启服务器后不会自启 —— 需要额外配置
- 日志无管理,会撑爆磁盘 —— 这是最容易被忽略的
PM2 把这四件事一次解决。
#用配置文件而不是命令行参数
命令行启动的参数下次重建就忘了。写成配置文件,才能复现和版本管理:
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'nebula-blog',
script: 'server/index.js',
cwd: '/www/wwwroot/blog',
instances: 1,
exec_mode: 'fork', // 见下文说明,低配机器不要用 cluster
max_memory_restart: '380M', // 小内存机器的保命参数
autorestart: true,
max_restarts: 20,
min_uptime: '20s',
restart_delay: 3000,
env: {
NODE_ENV: 'production',
PORT: 3000,
HOST: '127.0.0.1',
},
output: '/www/wwwlogs/blog-out.log',
error: '/www/wwwlogs/blog-err.log',
merge_logs: true,
time: true,
}],
};
启动:
pm2 start ecosystem.config.cjs
pm2 save
#几个参数的实际意义
#max_memory_restart 是最重要的一个
1 GB 内存的机器上,如果 Node 进程因为内存泄漏涨到 600 MB,整个系统会开始 swap,所有服务一起变慢,比直接重启这个进程糟糕得多。
设成 380M 的效果是:进程涨到 380 MB 时 PM2 主动重启它,用一个短暂的中断换整个系统的稳定。
这个值怎么定?参考公式:
max_memory_restart ≈ (可用内存 - 其他服务占用) × 0.6
比如可用 432 MB,其他服务占 100 MB,那么:
(432 - 100) × 0.6 ≈ 200 MB
保守一点设 380 MB 也是可以的,关键不要设成接近物理内存上限的值。
#min_uptime 防止无限重启循环
如果应用有启动即崩的问题,没有 min_uptime 的话 PM2 会疯狂重启,刷满日志文件并占满 CPU。
min_uptime: '20s' 表示"进程活过 20 秒才算启动成功"。配合 max_restarts: 20,20 次都失败后 PM2 会停止尝试并标记为 errored,这时你能从 pm2 list 一眼看出问题。
#exec_mode 为什么选 fork
cluster 模式会用 Node 的 cluster 模块起多个进程共享端口,理论上能利用多核。但有两个坑:
- 每个 worker 是独立进程,内存翻倍 —— 2 核机器就是 2 倍内存
- 如果用 SQLite 这类文件数据库,多进程写会锁冲突
对 1 GB 内存的个人站点,fork 单实例是正确选择。真需要多核,说明该考虑升级机器或换架构了。
#日志管理
#内置日志没有轮转
PM2 默认的日志文件会无限增长。一个中等流量的站点,几个月就能写到几个 GB,最后把磁盘写满导致服务崩溃。
必须装日志轮转模块:
pm2 install pm2-logrotate
# 配置:单文件最大 20MB,保留 7 份,每天切割
pm2 set pm2-logrotate:max_size 20M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'
pm2 set pm2-logrotate:compress true
验证:
pm2 list # 应该能看到 pm2-logrotate 模块在运行
ls -lh /root/.pm2/logs/
#应用自身也应该写日志
PM2 抓的是 stdout/stderr。结构化日志建议应用自己写到文件,便于按级别过滤:
function log(level, msg, extra = {}) {
const line = JSON.stringify({
t: new Date().toISOString(),
level,
msg,
...extra,
});
fs.appendFileSync('/www/wwwlogs/app.log', line + '\n');
}
配合 logrotate 按天切割即可。
#开机自启
pm2 startup
这个命令会输出一行需要你手动执行的命令(类似 sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root)。复制执行它,然后:
pm2 save # 保存当前进程列表,开机时按这个列表恢复
pm2 save 容易被忘掉。 只做 pm2 startup 不 save,重启后是空的。
注意:每次新增/删除应用后都要重新 pm2 save,否则重启后恢复的是旧列表。
#日常运维命令
pm2 list # 进程列表
pm2 monit # 实时监控(CPU/内存/日志)
pm2 logs nebula-blog # 实时日志
pm2 logs nebula-blog --lines 100 --nostream # 看最近 100 行
pm2 describe nebula-blog # 详细信息
pm2 restart nebula-blog # 重启
pm2 reload nebula-blog # 平滑重载(fork 模式下等同 restart)
pm2 stop nebula-blog # 停止
pm2 delete nebula-blog # 删除
pm2 flush # 清空所有日志
#部署更新代码后的正确流程
cd /www/wwwroot/blog
git pull # 或上传新代码
npm install --omit=dev
pm2 reload nebula-blog
pm2 save
顺序不能反:先更新代码和依赖,再重启进程。反过来会出现"新进程加载旧代码"的情况。
#常见问题
Q:pm2 list 显示 online 但访问 502
进程活着不代表在监听端口。查:
ss -ltn | grep 3000
pm2 logs nebula-blog --lines 50 --nostream
多半是启动时报错但进程还没退出,或者监听地址写错了。
Q:pm2 startup 后重启还是没自启
检查 pm2 save 有没有执行,以及 systemd 服务是否真的启用了:
systemctl status pm2-root
Q:内存限制触发了但日志里没有重启记录
max_memory_restart 触发时 PM2 会记录一条 PM2 | Process ... restarted because it exceeded the max memory。如果没有,说明是被系统 OOM Killer 杀的,而不是 PM2 主动重启 —— 这两种情况要分开排查。
#相关资源
以下资料可直接下载:
服务器只读巡检脚本
一条命令采集系统、CPU、内存、磁盘、Docker、端口与防火墙信息,不修改任何系统状态,适合部署前体检。
#小结
PM2 用好的关键就三条:
- 配置文件化,参数进版本管理
max_memory_restart必设,小内存机器的保命符- 日志轮转必装,否则迟早被日志写满磁盘
配置模板可以在文末下载。