Skip to content
Charles Shao
Go back

PM2 进程管理:Node.js 应用守护、配置文件与批量调度

views

应用写完了、测试过了,最后一步永远是”怎么让它稳稳地在服务器上跑起来、挂了能自动拉起、还能顺手看日志”。直接 node app.js 起进程的问题很直白:SSH 一断进程就没了,崩了没人管,多进程要手动一个个盯。PM2 就是专门解决这件事的守护进程管理工具——它常驻在后台守护你的应用,进程异常退出会自动重启,同时把多进程管理、内置负载均衡、日志系统、监控面板打包在一个命令行里。一般在服务上线时,我们就用 PM2 来托管进程。

这篇把 PM2 从安装、常用命令、两种使用方式,一路讲到两个实战场景(资讯类 Node 应用部署、批量调度 Python 脚本),并附上一份 ecosystem 启动参数速查表。

一句话定位:PM2 = 进程守护 + 自动重启 + 多进程管理 + 日志/监控,把”手动 node app.js”升级成”上线级的进程托管”。

TL;DR

1. PM2 安装

PM2 是一个全局命令行工具,直接用 npm 全局安装:

npm i pm2 -g

装完后 pm2 命令就全局可用了。第一次执行 pm2 命令时,它会自动在后台拉起一个常驻的守护进程(daemon),后续所有被管理的应用都挂在它下面。

2. 常用命令速查

PM2 的日常操作基本围绕”启动 → 查看 → 重启/停止 → 日志”这条主线。下面按功能分组,0 代表进程 id,all 代表所有进程。

目的命令
启动应用(进程名默认取文件名)pm2 start app.js
启动并指定进程名pm2 start app.js --name mynode
列出所有进程pm2 list / pm2 ls
查看某进程详情pm2 show 0 / pm2 info 0
监控面板(CPU/内存)pm2 monit
停止 / 停止全部pm2 stop 0 / pm2 stop all
删除 / 删除全部pm2 delete 0 / pm2 delete all
重启 / 重启全部pm2 restart 0 / pm2 restart all
0 秒停机热重载 / 全部pm2 reload 0 / pm2 reload all
查看日志 / 指定进程日志pm2 logs / pm2 logs 0
清空所有日志pm2 flush
重载日志pm2 reloadLogs
杀掉 PM2 守护进程本身pm2 kill

对应的完整命令示例:

# 启动 Node 应用,进程的默认名称为文件名(app)
pm2 start app.js

# 启动 Node,并指定进程名称为 mynode
pm2 start app.js --name mynode

# 显示所有进程
pm2 list      # 或 pm2 ls

# 查看进程 id 为 0 的详细信息
pm2 show 0    # 或 pm2 info 0

# 进入监视页面,监视每个进程的 CPU 和内存使用情况
pm2 monit

# 停止 id 为 0 的进程 / 停止所有进程
pm2 stop 0
pm2 stop all

# 删除 id 为 0 的进程 / 删除所有进程
pm2 delete 0
pm2 delete all

# 重启 id 为 0 的进程 / 重启所有进程
pm2 restart 0
pm2 restart all

# 0 秒停机重载 id 为 0 的进程(用于 networked 进程)
pm2 reload 0
pm2 reload all

# 显示所有进程日志 / 指定进程日志
pm2 logs
pm2 logs 0

# 清空所有日志文件 / 重载日志
pm2 flush
pm2 reloadLogs

# 杀死 PM2 守护进程
pm2 kill

restartreload 的区别restart 是”先停止旧进程、再启动新进程”,中间会有短暂的服务中断;reload滚动热重载,在 cluster 模式下逐个替换 worker,做到 0 秒停机,适合线上不能断流量的服务。

3. 两种使用方式:命令行 vs 配置文件

使用 PM2 主要有 2 种方式:

虽然使用配置文件的方式最终仍然需要用命令行来启动,但两者的主要区别是:

  1. 命令行方式:需要把各种配置参数在命令行里逐个输入;
  2. 配置文件方式:把各种配置参数放在配置文件里,命令行只负责”按这个文件启动”。

举个例子:你需要启动一个应用,指定应用名称为 newApp、入口文件路径为 index.js。我们看看这两个参数怎么用两种方式带进去。

3.1 命令行方式

pm2 start index.js --name newApp

3.2 配置文件方式

先创建一个配置文件(pm2.config.js),内容如下:

// 文件名为 pm2.config.js
module.exports = {
  apps: [
    {
      name: "newApp", // 应用名称
      script: "./index.js", // 入口文件
    },
  ],
};

然后在命令行执行以下命令,表示以指定的配置文件启动应用:

pm2 start pm2.config.js

3.3 该用哪种?

对比下来结论很清晰:使用配置文件的方式,可以把各种参数、环境变量等内容持久化地保留在文件里,方便批量管理多个应用,也避免在命令行里由于遗忘、手误等原因导致启动参数不可控。因此除了临时调试起单个进程,上线场景一律推荐用配置文件

4. 开机自启与持久化

守护进程解决的是”应用崩了自动拉起”,但机器重启后 PM2 自身也没了,进程自然也不在了。要让服务器重启后自动恢复所有应用,需要两步配合:

# 1. 生成并注册开机自启脚本(根据当前系统 init 生成对应脚本)
pm2 startup

# 2. 保存当前正在运行的进程列表(作为开机恢复的快照)
pm2 save

pm2 startup 负责”让 PM2 守护进程在开机时被拉起”,pm2 save 负责”记住此刻要恢复哪些应用”。两者缺一不可:只 startupsave,重启后 PM2 起来了却是空的;只 savestartup,快照存了但开机根本没人来恢复。

5. 实战一:资讯类 Node 应用部署

以一个资讯类站点为例(构建产物 + npm start 启动),上线流程大致如下:

cd /project
npm install
npm run build

# 用 npm start 作为启动命令,指定进程名为站点域名,便于识别
pm2 start --name "funnylegends.top" npm -- start

# 查看进程状态
pm2 ls

这里的关键是 pm2 start ... npm -- start:让 PM2 托管的是 npm start 这条命令(-- 之后的 start 是传给 npm 的参数),而不是直接跑某个 .js 文件。这样就能复用项目 package.json 里已经定义好的启动脚本。

6. 实战二:批量调度 Python 脚本

PM2 并不局限于 Node——通过 interpreter 指定解释器,它同样能守护和批量调度 Python 脚本,还能给每个实例单独注入环境变量、命令行参数和日志路径。下面用一个最小示例演示。

6.1 Python 脚本

一个读取环境变量、接收命令行参数的简单脚本:

# test.py
import os
import sys

env = os.environ.get('SCRIPT_ENV')
print(env)

arg1 = sys.argv[1]
arg2 = sys.argv[2]
print(arg1, arg2)


def add_func(a, b):
    sums = a + b
    return sums

a = 1
b = 2
print(add_func(a, b))

6.2 PM2 调度脚本

用一份 ecosystem.config.js 同时定义两个实例,各自带不同的参数、环境变量和日志文件:

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "cph-sg1-00001",
      args: ["cph-sg1-00001", "game-001"],
      cwd: "/data/apps/python",
      script: "/data/apps/python/test.py",
      interpreter: "python",
      out_file: "/data/apps/python/cph-sg1-00001.log",
      error_file: "/data/apps/python/cph-sg1-00001.log",
      autorestart: false,
      log_date_format: "YYYY-MM-DD HH:mm:ss",
      env: {
        SCRIPT_ENV: "production",
      },
    },
    {
      name: "cph-sg1-00002",
      args: ["cph-sg1-00002", "game-002"],
      cwd: "/data/apps/python",
      script: "/data/apps/python/test.py",
      interpreter: "python",
      out_file: "/data/apps/python/cph-sg1-00002.log",
      error_file: "/data/apps/python/cph-sg1-00002.log",
      autorestart: false,
      log_date_format: "YYYY-MM-DD HH:mm:ss",
      env: {
        SCRIPT_ENV: "development",
      },
    },
  ],
};

几个要点:

6.3 执行日志

启动后,两个实例分别按各自的环境变量和参数运行,日志落到对应文件:

cat cph-sg1-00001.log
2024-06-14 16:50:55: production
2024-06-14 16:50:55: ('cph-sg1-00001', 'game-001')
2024-06-14 16:50:55: 3

cat cph-sg1-00002.log
2024-06-14 16:50:55: development
2024-06-14 16:50:55: ('cph-sg1-00002', 'game-002')
2024-06-14 16:50:55: 3

可以看到 SCRIPT_ENVargs 都按各自实例的配置生效了——这就是用一份配置文件批量拉起、参数各自隔离的效果。

7. ecosystem 启动参数速查

配置文件(以及命令行)里常用的启动参数如下:

参数说明
script启动脚本路径
instances应用启动实例个数,仅在 cluster 模式有效,默认为 fork
exec_mode应用启动模式,支持 forkcluster 模式
name指定 app 名字
watch监听重启:启用后文件夹或子文件夹变化时应用自动重启
ignore_watch忽略监听的文件夹,支持正则,配合 watch 使用
max_memory_restart最大内存限制,超出自动重启
env环境变量,object 类型,如 {"NODE_ENV":"production","ID":"42"}
log指定 log 位置;若要指定新位置,需先删掉原 process 再重新启动
output指定 output(stdout)log 位置
error指定 error(stderr)log 位置
log_date_format指定日志日期格式,如 YYYY-MM-DD HH:mm:ss
args传给脚本的额外参数
restart_delay异常自动重启时的延时(毫秒)
autorestart默认为 true,发生异常时自动重启
cron_restart按 crontab 时间格式重启应用,目前只支持 cluster 模式

小结

PM2 的核心价值可以浓缩成一句话:把”手动跑进程”升级为”上线级的进程托管”


views
Share this post on:

Previous Post
过滤与重排:推荐链路最不性感、却最影响体感的最后一道闸门
Next Post
地区广告政策与合规:GDPR、中国广告法与平台审核差异