背景
1. 进程管理
进程管理主要是指创建,终止和监控进程。进程管理器主要是用来确保你的应用在启动后能够保持在线。
2. pm2 是什么
pm2 是 nodejs 的进程管理器,默认支持负载均衡,能够守护进程。还支持查看应用运行时的性能,资源占用情况等。
3. 为什么要用 pm2
普通 node 进程的缺陷
- 普通启动方式:node index.js,关闭终端就结束进程
- node 进程挂掉和“僵死”:
- 进程挂掉大概有以下几种原因:
- 内存泄漏:内存一点点积累到达临界值爆掉
- 死循环导致内存突增爆掉
- 死循环导致磁盘写爆
- 被动被kill,主动退出。
- “僵死”大概有以下几种可能:
- 有某个非常耗时的cpu操作正在执行
- 写了个死循环(死循环不一定会导致服务挂掉,有可能只会cpu飙升,让服务处于假死状态)
- 进程挂掉大概有以下几种原因:
不管发生上面哪种情况,都会造成服务的不可访问,需要等我们自己去发现问题后,然后重启应用。
pm2 启动的优势
- 后台运行:pm2可以后台运行,终端关闭不影响。
- 自动重启:在应用意外挂掉或者机器重启后,能够自动重启应用
- 自动负载均衡:支持集群模式,可以方便的启动多个node进程,充分利用cpu和内存
- 0 秒停机重启:集群模式下,可以达到重启时不停止服务
- 可以监控应用性能,资源占用情况等,自动输出日志
- 开发模式支持热更新
使用
1. 安装
pm2 本质是一个 cli ,可以通过 npm 进行安装
$ npm install pm2
# or
$ yarn add pm2
2. 启动
开发模式
开发 node 应用的时候,修改代码过后每次都要重启下应用,很麻烦。使用 pm2-dev
命令启动应用,应用会在在代码发生变更过后,自动重启。
$ pm2-dev start server.js
生产模式
-
pm2 start
应用部署到生产环境后,可以使用
pm2 start
来启动应用$ pm2 start server.js ┌─────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ server │ default │ 0.1.0 │ fork │ 17471 │ 71s │ 0 │ online │ 0% │ 77.5mb │ ayo │ disabled │ └─────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
该命令启动的应用,默认支持进程守护,并且可以通过 pm2 进行监控
-
一些可以传给 cli 的常用配置
-
--name <app_name>
指定应用名。不传的话默认为应用入口文件的文件名。如上图的 server
-
--watch
在文件发生变化时自动重启。类似于开发模式的
pm2-dev start
命令 -
--max-memory-restart <200MB>
该配置项设置后,应用会在运行内存达到设定值
200MB
后,自动重启。避免了 Node.js heap out of memory error 问题。 -
--log <log_path>
指定日志输出目录
-
--restart-delay <delay in ms>
设置自动重启的延迟时间
-
--cron <cron_pattern>
定时任务。可以设置 一天中的固定重启时间, 一周内固定重启日期,或者 一个时间间隔(如 每 48 小时重启一次)。cron_pattern 请参照这里
-
--no-autorestart
关掉应用的自动重启
-
更多配置请参照 集成部署EcosystemFile
3. 管理进程
下面是部分常用命令行
$ pm2 restart app_name
$ pm2 reload app_name
$ pm2 stop app_name
$ pm2 delete app_name
-
app_name
的位置除了传应用名,还可以传:-
all
针对所有进程进行操作 -
id
针对特定id的进程进行操作
-
- 集群模式下,restart中断服务,而reload不会
详细介绍下以下命令:
查看应用列表 pm2 list
$ pm2 list
┌─────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├─────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ app │ default │ 1.0.0 │ fork │ 16573 │ 9m │ 0 │ online │ 0% │ 57.3mb │ ayo │ disabled │
│ 2 │ index │ default │ 1.0.0 │ fork │ 0 │ 0 │ 16 │ errored │ 0% │ 0b │ ayo │ disabled │
│ 1 │ server │ default │ 0.1.0 │ fork │ 17471 │ 71s │ 0 │ online │ 0% │ 77.5mb │ ayo │ disabled │
└─────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
运行上面的命令后,可以查看所有 pm2 启动的应用
-
id
:应用 id -
name
:应用名称 -
namespace
:在配置文件里面可以给应用设置 namespace。设置后可以使用命令行单独启动或停止某个 namespace 的应用。详细用法见 集成部署EcosystemFile -
mode
: 进程的运行方式。分为fork
与cluster
。详细介绍见 集群模式 -
pid
:进程id -
uptime
:运行时间 -
↺
:重启次数(从0开始计数) -
status
:进程是否在线 -
cpu
:cpu占用率 -
mem
:内存占用大小
查看应用详情信息 pm2 show app_name|app_id
$ pm2 show server
Describing process with id 1 - name server
┌───────────────────┬──────────────────────────────────────────────────┐
│ status │ online │
│ name │ server │
│ namespace │ default │
│ version │ 0.1.0 │
│ restarts │ 0 │
│ uptime │ 60m │
│ script path │ /home/ayo/dev/demo/analytics-dashboard/server.js │
│ script args │ N/A │
│ error log path │ /home/ayo/.pm2/logs/server-error.log │
│ out log path │ /home/ayo/.pm2/logs/server-out.log │
│ pid path │ /home/ayo/.pm2/pids/server-1.pid │
│ interpreter │ node │
│ interpreter args │ N/A │
│ script id │ 1 │
│ exec cwd │ /home/ayo/dev/demo/analytics-dashboard │
│ exec mode │ fork_mode │
│ node.js version │ 17.0.0 │
│ node env │ N/A │
│ watch & reload │ ✘ │
│ unstable restarts │ 0 │
│ created at │ 2021-12-03T08:33:01.489Z │
└───────────────────┴──────────────────────────────────────────────────┘
运行上面的命令后,可以查看应用详情信息。
除了 pm2 list
命令返回的信息以外,还可以获取几个常用信息:
-
script path
:启动入口的文件路径 -
script args
:启动文件的参数 -
error log path
:错误日志的文件路径 -
out log path
:输出日志的文件路径 -
exec mode
:进程的模式 -
watch&reload
:是否开启监听文件变动重启 -
unstable restarts
:不稳定的重启次数
查看日志 pm2 logs
$ pm2 logs
C:\Users\fish2\.pm2\pm2.log last 15 lines:
PM2 | 2022-06-22T17:26:49: PM2 log: Concurrent actions : 2
PM2 | 2022-06-22T17:26:49: PM2 log: SIGTERM timeout : 1600
PM2 | 2022-06-22T17:26:49: PM2 log: ===============================================================================
PM2 | 2022-06-22T17:26:49: PM2 log: App [server:0] starting in -fork mode-
PM2 | 2022-06-22T17:26:49: PM2 log: App [server:0] online
PM2 | 2022-06-23T08:57:15: PM2 log: Stopping app:server id:0
PM2 | 2022-06-23T08:57:15: PM2 log: App [server:0] exited with code [1] via signal [SIGINT]
PM2 | 2022-06-23T08:57:15: PM2 log: pid=18708 msg=process killed
PM2 | 2022-06-23T09:35:21: PM2 log: App [server:0] starting in -fork mode-
PM2 | 2022-06-23T09:35:21: PM2 log: App [server:0] online
PM2 | 2022-06-23T09:59:15: PM2 log: Stopping app:server id:0
PM2 | 2022-06-23T09:59:15: PM2 log: App [server:0] exited with code [1] via signal [SIGINT]
PM2 | 2022-06-23T09:59:15: PM2 log: pid=7828 msg=process killed
PM2 | 2022-06-23T10:05:22: PM2 log: App [server:0] starting in -fork mode-
PM2 | 2022-06-23T10:05:22: PM2 log: App [server:0] online
C:\Users\fish2\.pm2\logs\server-error.log last 15 lines:
C:\Users\fish2\.pm2\logs\server-out.log last 15 lines:
0|server | [Symbol(kNeedDrain)]: false,
0|server | [Symbol(corked)]: 0,
0|server | [Symbol(kOutHeaders)]: [Object: null prototype]
0|server | },
0|server | _currentUrl: 'http:undefined/appmarket/api/client/types/',
0|server | [Symbol(kCapture)]: false
0|server | },
0|server | response: undefined,
0|server | isAxiosError: true,
0|server | toJSON: [Function (anonymous)]
0|server | }
0|server | ERR! [SSR] generate html template error
0|server | [SSR] generating html content
0|server | [SSR] generating html content
0|server | [SSR] generating html content
还可以直接进入日志保存的目录查看。示例中日志目录为:C:\Users\fish2\.pm2\pm2.log
监控所有进程 pm2 monit
- 左上角 Process list:进程列表
- 右上角 Logs:所有实时日志
- 左下角 Custom Metrics:当前选中进程的指标
- 右下角 Metadata:进程的信息
集成部署EcosystemFile
当我们需要管理多个应用,或者应用有多个运行环境时,使用 pm2 start
来启动应用+配置参数是很不方便的。所以,pm2 支持使用配置文件来启动和管理应用。
1. 生成配置文件
$ pm2 init simple
运行该命令可在工程下初始化一个 ecosystem.config.js
配置文件
module.exports = {
apps : [{
name : "app1",
script : "./app.js"
}]
}
app是要管理应用的数组,每个对象都是一个应用的配置
你也可以自己创建一个配置文件,文件名以
.config.js
结尾,pm2 会自动识别并读取该文件。
2. 配置项
基础配置
- name:进程名
- script:node 启动文件的路径
- cwd :项目所在的目录
- args :通过命令行传递给node启动文件的参数
- interpreter :编译器的绝对路径(默认 node )
- interpreter_args :传给编译器的参数
- node_args:传给 node 的参数
进阶配置
- instances :应用启动实例个数,仅在cluster模式有效
- exec_mode :应用程序启动模式。可选
cluster
或fork
,默认为fork
- watch :是否启用监控模式,默认是
false
。如果设置成true
,当应用程序变动时,pm2会自动重载 - ignore_watch :不用监听的文件,如:
ignore_watch: [ 'node_modules', 'logs', ]
- max_memory_restart :应用运行内存达到设定值后,会自动重启。避免了 Node.js heap out of memory error 问题
- env :应用中的默认环境变量
- env_ :命令行中可传入的环境变量,覆盖默认环境变量
- source_map_support :默认
true
,支持 sourcemap 文件
日志配置
- log_date_format :日志时间格式
- error_file :错误日志存放路径
- out_file :全部日志存放路径
- combine_logs:是否将不同 id 的进程日志合并
- merge_logs:同上
控制流
-
min_uptime:应用被视为已启动最小运行时间
min uptime of the app to be considered started
-
max_restarts: 在应用状态被认定为
error
前,一定时间(min_uptime
指定的时间,若未配置,则为 1 秒)内的连续不稳定重启次数number of consecutive unstable restarts (less than 1sec interval or custom time via min_uptime) before your app is considered errored and stop being restarted
以上两个配置项一般需要同时设置。
示例:
server.js
setTimeout(function () {
console.log('killed');
process.exit(1)
}, 1000);
ecosystem.config.js
module.exports = {
apps: [{
name: "app-market",
script: "./server.js",
max_restarts: 4,
min_uptime: 5000,
}]
}
如示例所示,server 应用在启动 1s 后,会自己强行结束进程。这时, pm2 由于进程守护的特性,会去重新拉起应用。示例中设置了 min_uptime
为 5000(即 5s),max_restarts
为 4 次,所以 pm2 在进程停止(1s)后去重启进程,5s 钟可以内重启次数肯定可以达到 4 次。达到设定次数后,则停止重启应用。
当第 4 次重启时,应用状态将变为 error
,pm2 将停止重启应用。上图重启次数显示为 3,是因为是从 0 开始计数的(0, 1, 2, 3 共 4 次)
- listen_timeout:如果应用没有发送
ready
信号,间隔多长时间 reload。单位为毫秒。 - wait_ready:是否等待进程发送
ready
信号。默认为false
。设置为true
后,需要在应用内部添加process.send('ready')
语句发送ready
信号。
以上两个配置项一般需要搭配使用,详细使用场景见 优雅的启动与停止应用
-
kill_timeout:从告诉进程要关闭到强制关闭进程的间隔时间。
module.exports = { apps : [{ name: 'app', script: './app.js', kill_timeout: 3000 }] }
当 pm2 要停止或重启一个应用时,会按顺序给你的进程发送一系列系统信号。它首先会发送一个
SIGINT
信号告诉你应用将会被停止。然后 pm2 会等待 3s(示例中kill_timeout
设置的时间)。若 3s 后应用没有自己停止(exit),pm2 会发送一个SIGKILL
信号来强制停止应用。When a process is stopped/restarted by PM2, some system signals are sent to your process in a given order.
First a SIGINT a signal is sent to your processes, signal you can catch to know that your process is going to be stopped. If your application does not exit by itself before 1.6s (customizable) it will receive a SIGKILL signal to force the process exit.详细使用场景见 优雅的启动与停止应用
-
shutdown_with_message:使用
process.send(‘shutdown’)
方式来代替process.kill(pid, SIGINT)
停止应用。默认值为false
。
主要用于进程无法使用信号流来关闭的场景。When signals are not available your process gets killed. In that case you have to use --shutdown-with-message via CLI or shutdown_with_message in Ecosystem File and listen for shutdown events.
restart_delay:进程掉线后,等待多长时间自动重启, 默认 0s
autorestart: 是否开启自动重启。默认开启。
cron_restart: 定时重启。可以设置 一天中的固定重启时间, 一周内固定重启日期,或者 一个时间间隔(如 每 48 小时重启一次)。cron_pattern 请参照这里
集群模式
1. pm2 的 cluster 模式与 fork 模式的区别
-
fork 模式
fork 模式使用最基本的进程运行方式,只是单实例运行server,无法实现 TCP 连接共享。并且:我们知道 JavaScript 代码是运行在单线程上的,换句话说一个 Node.js 进程只能运行在一个 CPU 上。那么如果用 Node.js 来做 Web Server,就无法享受到多核运算的好处
-
cluster 模式
集群模式允许 nodejs 应用程序在所有可用的 CPU 上进行扩展,而无需修改任何代码。可以显著提高应用程序的性能和可靠性,具体取决于可用的 CPU 数量。在底层,它使用了 Node.js 集群模块,这样扩展后的应用程序的子进程可以自动共享服务器端口。要了解更多信息,请参阅 Node.js 集群。The cluster mode allows networked Node.js applications (http(s)/tcp/udp server) to be scaled across all CPUs available, without any code modifications. This greatly increases the performance and reliability of your applications, depending on the number of CPUs available. Under the hood, this uses the Node.js cluster module such that the scaled application’s child processes can automatically share server ports. To learn more, see How It Works in the official Node.js documentation on the cluster module.
2. 使用
- 命令行启动
$ pm2 start app.js -i max
max
的意思是:把应用部署到服务器所有可用的CPU上,并运行尽可能多的进程
- 通过配置文件启动
ecosystem.config.js
module.exports = {
apps : [{
script : "app.js",
instances : "max",
exec_mode : "cluster"
}]
}
注意:pm2 的负载均衡的特性是建立在以集群模式启动的基础上的,默认的启动模式是 fork
,不支持负载均衡
3. 重载 Reload
集群模式下,由于 pm2 的 restart
命令会先杀掉所有进程,再重新拉起,会造成服务终止。但是 reload
命令可以实现 0s 重启,维护升级的时候不需要停机。
当重载时间过长或者无法重载时,pm2 会自动切换成 restart
4. 动态扩展
如果希望在运行中动态增加实例数,可以使用下面的命令:
$ pm2 scale <app_name> +4 # add 4 additional workers in realtime
Docker 集成
1. 在容器中安装 pm2
RUN npm install pm2 -g
2. 启动 pm2
- 直接启动应用
CMD ["pm2-runtime", "app.js"]
- 通过配置文件启动
CMD ["pm2-runtime", "ecosystem.config.js"]
注意:在容器中使用 pm2 启动应用时,要使用 pm2-runtime
而不是 pm2 start
, 因为:
pm2 是默认后台启动的, docker 感知不到。CMD 命令执行完成,docker 容器就结束了。 所以直接使用
node app.js
启动应用后,应用能一直在容器中保持运行。 pm2 以后台形式运行,CMD 命令执行完成,docker 就认为可以退出了。
所以需要使用 pm2-runtime
来进行处理。pm2-runtime
是为 Docker 容器设计的,它将应用程序置于前台,从而使容器保持运行状态。