核心概念与工作原理
定位:建立正确的心智模型。不理解这一篇,后面所有命令都只是"背用法";理解了这一篇,PM2 的每个命令都变得"理所当然"。
前置:已按 概述 完成安装。
一句话模型
PM2 = 一个常驻的"监护人"进程(daemon)+ 一群由它拉起、看护的"被管理进程" + 一个命令行遥控器(CLI)。
你执行 pm2 start app.js 时,真正发生的事情是:
- CLI 向 daemon(守护进程)发出 RPC 请求:"请帮我把
app.js拉起来"; - daemon 以 子进程 方式启动
app.js(默认 fork 模式:node app.js); - daemon 从此"盯"着它:进程退出就按策略重启、记录日志、统计 CPU/内存、响应你的管理指令。
所以 pm2 start app.js 执行完,命令就返回了——你的终端可以关掉,服务照样在跑,因为真正干活的是 daemon,不是你的终端。
你的终端 后台
┌─────────────┐ RPC ┌──────────────────────────┐
│ pm2 CLI │ ──────▶ │ PM2 daemon (God 进程) │
│ start/ls/ │ ◀────── │ - 看护/重启/记录日志 │
│ restart... │ │ - 持有每个进程的状态 │
└─────────────┘ └──────────┬───────────────┘
│ fork / 管理
┌──────┴────────┐
│ 你的应用进程 │ (node app.js)
│ app-0 │
│ app-1 (cluster)│
└───────────────┘三个角色的区分(最容易混淆的点)
| 角色 | 是什么 | 谁启动的 | 特征 |
|---|---|---|---|
| PM2 daemon | PM2 自己的主进程 | 第一次执行 pm2 命令时自动启动 | 后台运行、每用户一个、退出后所有被管理进程失去看护 |
| 被管理进程 | 你的 Node 应用 | daemon 代为启动 | 是 daemon 的子进程 |
| pm2 CLI | 命令行工具 | 你每次敲命令时临时运行 | 执行完即退出,只是"遥控器" |
理解要点:
- daemon 挂了,进程未必立刻挂,但会失去"看护":孤儿进程仍在跑,但没人再负责崩溃重启、日志、状态汇报。所以生产环境要保证 daemon 本身也活着(→ 开机自启与远程部署,用 systemd 托管 daemon)。
- daemon 是按用户隔离的:daemon 与它所托管的进程属于同一系统用户、同一个
PM2_HOME(默认~/.pm2)。用 root 跑的应用和用www用户跑的应用互不可见,也无法互相管理。 - 换用户 = 换了一个世界:
sudo pm2 ls看到的是 root 的 daemon 列表,跟你自己的pm2 ls完全是两回事。
目录结构:~/.pm2(PM2_HOME)
daemon 的所有“记忆”都落在 PM2_HOME 目录下(默认 $HOME/.pm2,可用环境变量 PM2_HOME 改到别处,例如 /var/run/pm2 或容器卷):
| 路径 | 内容 |
|---|---|
~/.pm2/pm2.pid | daemon 的 PID(可用于确认 daemon 在跑) |
~/.pm2/pm2.log | PM2 自身的运行日志(不是你的应用日志!) |
~/.pm2/pm2.sock、rpc.sock、pub.sock | CLI 与 daemon 通信用的 Unix socket |
~/.pm2/logs/ | 应用日志默认落盘位置(见下) |
~/.pm2/pids/ | 每个被管理进程的 PID 文件 |
~/.pm2/dump.pm2 | pm2 save 生成的进程清单快照(重启 daemon / 开机自启靠它) |
~/.pm2/module_conf.json 等 | PM2 模块(如 pm2-logrotate)的配置 |
排查"PM2 怎么怪怪的"第一站:看
~/.pm2/pm2.log。注意别和应用日志搞混——应用打印的 console 日志默认落在~/.pm2/logs/下(默认文件名形如<app>-out-<pid>.log/<app>-error-<pid>.log,详见 日志与轮转)。
进程的"身份"与命名
PM2 里每个被管理进程同时拥有:
- name(应用名):你起的别名(
pm2 start app.js --name web→ name =web)。可以多个实例共用同一个 name。 - id(数字 ID):PM2 内部从 0 开始分配的编号。
pm2 start输出表格里的id列;pm2 describe 0、pm2 restart 0都可用。 - namespace(命名空间):逻辑分组(如
backend/frontend),用于列表过滤与成组操作(pm2 ls --namespace backend等);ecosystem 配置项与 CLI 支持程度随版本而异,以本机帮助为准。
日常习惯:
- 与人沟通、写文档:用 name;
- 脚本里自动化:优先用 name(id 会随删除/重启变动,name 稳定);
- name 冲突时可用 id 精确指定。
进程生命周期与状态机
一个被管理进程大致经历:launching → online → stopping/stopped → (重启) → … → errored/stopped。
pm2 ls 状态列常见取值(完整取值以 pm2 ls 实际输出与版本为准):
| 状态 | 含义 | 常见原因/对应动作 |
|---|---|---|
online | 正常运行中 | 健康状态 |
launching | 正在启动/拉起中 | 刚 start、restart 后的瞬间 |
stopping | 正在被停止 | 收到 stop/restart 指令,等待进程退出 |
stopped | 已停止(记录保留) | 你执行了 pm2 stop |
errored | 启动失败/运行中异常退出且重启策略认为不该再拉起 | 代码抛错、端口被占、启动超时 |
waiting restart | 按策略等待下一次重启 | 见重启策略(监控与告警) |
one-launch-status | 一次性任务(exec_mode 一次性脚本)结束 | --no-autorestart 或脚本执行完 |
关键机制:重启计数(restarts)
- 每当进程退出,daemon 会记一次 restarts(表格中的列)。重启不是"玄学",是策略驱动:
- 默认
autorestart: true—— 异常退出就重启; - 重启过快会被判定为 unstable(不稳定),受
max_restarts/min_uptime约束(详见 监控与告警 的重启策略小节)。
- 默认
- 表格里的
uptime(已运行时长)、cpu、memory、user都是 daemon 实时统计的。
崩溃自动重启:PM2 到底"看"什么
PM2 判定进程状态的依据是 进程是否还活着(OS 层面的 PID/退出码),它 不关心你的业务是否正常:
- 进程 exit(无论异常退出还是
process.exit())→ 触发重启逻辑; - 进程活着但"卡死"(死循环不响应请求)→ PM2 默认 不会 重启它,因为这超出进程管理器的职责(这是健康检查/监控要做的事,见 监控与告警);
- 你可以主动给出"我已就绪/我要退出"的信号来配合 PM2 做优雅启停(见 集群模式与零停机发布 的 wait_ready 与优雅退出)。
fork 与 cluster:两种执行模式
进程模型上 PM2 提供两种 exec_mode,这是后面很多行为的根源:
| 模式 | 进程形态 | 用途 |
|---|---|---|
fork(默认) | 每个实例 = 一个独立 node app.js 进程 | 常规应用、脚本、不需要多进程共享的应用 |
cluster | 主进程 fork 出 N 个 worker,共享一个端口,由 PM2 做负载分发 | 吃满多核、需要零停机 reload |
集群的内部细节(负载均衡、worker 通信、粘性会话等)在 集群模式与零停机发布 单独展开。此处只需记住:单实例用 fork,多核部署用 cluster。
数据持久化:save / dump / resurrect
daemon 活在内存里,但它可以把"当前管理了哪些进程、用什么参数"固化下来:
| 命令 | 含义 |
|---|---|
pm2 save | 把当前进程清单写入 ~/.pm2/dump.pm2(含启动参数、环境变量、日志路径) |
pm2 resurrect | 按 dump.pm2 把进程重新拉起 |
pm2 startup | 让系统在开机时自动"启动 daemon + resurrect"(Linux 下生成 systemd 单元,见 开机自启与远程部署) |
一句话:pm2 本身不"记住"任何东西,除非你 pm2 save。 这也是"机器重启后服务没回来"的头号原因——没 save,或 save 之后又改过进程清单没再 save。
常用速记(本页核心结论)
- 进程是 daemon 的子进程,由 daemon 看护,不是由你的终端看护。
- daemon 按用户隔离,数据落在
~/.pm2(PM2_HOME)。 - 崩了会重启;重启太频繁会触发保护策略;"活着但卡死"PM2 不管。
- 忘了
pm2 save,重启机器 = 服务全没了。 - 应用日志在
~/.pm2/logs/,PM2 自己的日志在~/.pm2/pm2.log,别混。
