PM2
一、认识
PM2
是一个功能强大的 Node.js
进程管理工具,可以实现 Node.js
服务的 守护进程、进程管理、负载均衡、日志管理 等。在实际应用中,PM2
主要用于:
-
守护进程:
PM2
确保Node
服务在意外退出时自动重启,保证服务的稳定性和高可用性。 -
多进程管理:
PM2
可以通过集群模式(Cluster Mode
)启动多个进程,提高服务的并发能力,充分利用多核CPU
。 -
进程管理:提供启动、重启、停止、删除进程的操作,并支持进程列表查询、资源使用监控。
-
日志管理:自动收集标准输出(
stdout
)和错误输出(stderr
)的日志,支持日志文件管理、分割。 -
部署与环境管理:支持配置文件(
ecosystem.config.js
),可以通过环境变量来管理多个环境(如生产环境、开发环境)。
1.1 PM2 工作机制
PM2
的核心工作机制是基于 进程管理 和 守护进程 的实现,主要包括以下几个方面:
一、守护进程(Daemon Process
): PM2
通过启动一个独立的 守护进程(Daemon
) 来管理所有的 应用进程。守护进程常驻后台,负责 监控、重启 和 管理 所有子进程。当执行 pm2 start app.js
命令时,PM2
会首先检查守护进程是否存在,如果不存在,则启动守护进程。PM2 CLI
将用户的启动指令通过 IPC
通信 发送给 守护进程。守护进程收到指令后,通过 child_process.fork
启动一个新的子进程运行 app.js
。守护进程 与 用户应用进程 分离,保证应用进程异常退出后,守护进程可以检测到并自动重启。守护进程 会保存应用进程的状态到文件中(~/.pm2/dump.pm2
),即使服务器重启也能恢复进程状态。
const { spawn } = require('child_process');
spawn('pm2 daemon', { detached: true });
二、PM2
的两种进程启动模式:
2.1 Fork
模式(单进程模式): PM2
使用 child_process.fork
启动一个子进程
const { fork } = require('child_process');
const child = fork('app.js', { silent: true });
2.2 Cluster
模式(多进程模式): 适合多核 CPU
的场景。PM2
支持 Cluster
模式,利用 Node.js
提供的 cluster
模块实现多进程并发。PM2
会启动一个 Master
进程,再 fork
出多个 Worker
子进程(基于 IPC
机制与 Master
通信),每个子进程都会运行同一个服务脚本(app.js
), 所有子进程共享同一个端口。主进程通过 Round-Robin
(轮询)调度算法实现将请求分发到不同的子进程中,实现负载均衡。利用多核 CPU
,提高服务的性能和并发能力。通过 IPC
机制 在 Master
和 Worker
之间传递消息。
const cluster = require('cluster');
const os = require('os');
const numCPUs = os.cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) cluster.fork();
} else {
require('./app'); // 启动服务
}
三、自动故障恢复与重启机制: PM2
内置故障检测机制,当应用进程崩溃或退出时,守护进程会自动重启它。确保服务持续运行,减少人工干预。可以通过 --max-memory-restart
限制进程内存使用,超过阈值后自动重启,防止内存泄漏。具体实现为: 守护进程监听子进程的退出事件, 实现如下
const { fork } = require('child_process');
const child = fork('app.js');
child.on('exit', () => {
console.log('Child process exited, restarting...');
fork('app.js'); // 自动重启
});
四、日志管理与监控: PM2
自动捕获应用的标准输出(stdout
)和错误输出(stderr
),并将日志保存在文件中。PM2
提供 pm2 monit
命令监控 CPU
、内存等资源使用情况。与外部监控工具集成(如 PM2 Plus
和 Prometheus
),实现自定义监控和告警。
五、文件监听与热重载: PM2
支持 文件变更监听,在代码变更后自动重启服务,提升开发效率。具体实现为: 使用 chokidar
库监听文件变动,当检测到变更时,重启进程
const chokidar = require('chokidar');
chokidar.watch('./').on('change', () => {
process.exit(0); // 触发重启
});
六、PM2
的设计思想: 主要围绕 高可用性 和 易维护性
6.1 守护进程设计:通过守护进程保证服务稳定运行,实现自动恢复。
6.2 多进程架构:通过 Fork
和 Cluster
模式充分利用多核 CPU
。
6.3 进程间通信(IPC
):解耦 CLI
与守护进程,便于管理。
6.3.1 child_process IPC
通信: Node.js
提供了 child_process
模块来创建子进程,并允许父进程和子进程进行消息传递。这是 Node.js
中最常见的 IPC
机制:父进程使用 fork()
创建了一个子进程,并通过 send()
向子进程发送一个消息。子进程接收到消息后,通过 process.on('message')
监听消息,并在处理完消息后通过 process.send()
向父进程发送回复。父进程通过 on('message')
监听子进程返回的消息。
父进程代码
const { fork } = require('child_process');
// 创建子进程,执行子进程脚本
const child = fork('child.js');
// 监听子进程发送的消息
child.on('message', (msg) => {
console.log('父进程接收到消息:', msg);
});
// 向子进程发送消息
child.send({ from: '父进程', data: 'Hello, 子进程!' });
子进程代码
// 监听父进程发送的消息
process.on('message', (msg) => {
console.log('子进程接收到消息:', msg);
// 向父进程发送回复消息
process.send({ from: '子进程', data: 'Hello, 父进程!' });
});
6.3.2 spawn() IPC
通信机制: spawn()
方法用于创建子进程,它不会直接通过 IPC
发送消息,而是通过 标准输入(stdin
)、标准输出(stdout
) 和 标准错误输出(stderr
) 进行数据交换。通常用于启动和控制外部进程。父进程通过 spawn()
启动一个 Node.js
子进程,并通过标准输入(stdin.write()
)发送数据。子进程接收到输入后,打印并将结果通过标准输出(stdout.write()
)发送回父进程。父进程监听标准输出,接收来自子进程的返回数据。
父进程代码
const { spawn } = require('child_process');
// 启动子进程
const child = spawn('node', ['child.js']);
// 向子进程写入数据
child.stdin.write('Hello from parent!\n');
// 从子进程读取输出
child.stdout.on('data', (data) => {
console.log(`从子进程接收到的数据: ${data}`);
});
子进程代码
process.stdin.on('data', (data) => {
console.log(`子进程接收到数据: ${data}`);
process.stdout.write('Hello from child!\n');
});
6.3.3 cluster IPC
多进程通信机制: cluster
模块使得 Node.js
可以利用多核 CPU
,进行多进程处理。每个工作进程都可以通过 IPC
通道与主进程通信。在 cluster
模块中,主进程通过 fork()
创建工作进程,每个工作进程都会通过 process.send()
向主进程发送消息。进程通过 cluster.on('message')
监听来自工作进程的消息。
父进程代码
const cluster = require('cluster');
const http = require('http');
if (cluster.isMaster) {
// 主进程创建工作进程
cluster.fork();
// 监听工作进程发送的消息
cluster.on('message', (worker, message) => {
console.log('主进程接收到消息:', message);
});
} else {
// 工作进程发送消息给主进程
process.send({ msg: 'Hello from worker!' });
// 模拟工作进程的 HTTP 服务器
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello, World!');
}).listen(8000);
}
6.4 Round-Robin
(轮询)调度算法: 在 Cluster
模式下, 主进程通过 Round-Robin
(轮询)调度算法实现将请求分发到不同的子进程中,实现负载均衡。Round-Robin
调度算法 是一种 公平分配任务 的调度方法,它按照顺序(轮询)将任务分配给可用资源,确保每个资源接收到的任务数量大致相等,不会出现资源长期空闲或过载的情况, 每个资源按顺序被依次调度,不偏向任何一方, 适用于任务轻量、处理时间较短、资源处理能力相对均衡的场景。Round-Robin
调度算法 的核心原理为: 维护一个资源列表(例如服务器、任务队列等),并从头到尾顺序分配任务。当资源分配到最后一个时,调度器会回到列表的第一个资源,继续分配任务。调度器通常需要维护一个索引(或指针),记录上一次分配到哪个资源,下次调度时从下一个资源开始。简单来说: 定义一个资源列表,使用一个指针记录当前调度到哪个资源,每次分配任务时,更新指针,循环访问资源列表。
class RoundRobinScheduler {
constructor(servers) {
this.servers = servers; // 资源列表,如服务器
this.currentIndex = -1; // 当前分配的索引位置
}
// 获取下一个资源
getNextServer() {
// 更新索引,轮询选择下一个服务器
this.currentIndex = (this.currentIndex + 1) % this.servers.length;
return this.servers[this.currentIndex];
}
// 分配任务
assignTasks(tasks) {
tasks.forEach((task, index) => {
const server = this.getNextServer(); // 获取下一个服务器
console.log(`任务 ${index + 1} 分配给服务器: ${server}`);
});
}
}
// 示例使用
const servers = ['Server-A', 'Server-B', 'Server-C']; // 服务器列表
const tasks = [1, 2, 3, 4, 5, 6, 7]; // 待分配的任务
const scheduler = new RoundRobinScheduler(servers);
scheduler.assignTasks(tasks);
// 输出结果:
任务 1 分配给服务器: Server-A
任务 2 分配给服务器: Server-B
任务 3 分配给服务器: Server-C
任务 4 分配给服务器: Server-A
任务 5 分配给服务器: Server-B
任务 6 分配给服务器: Server-C
任务 7 分配给服务器: Server-A
Round-Robin
调度算法 算法逻辑清晰,代码量少。每个资源都会得到相同的任务分配机会。不需要复杂的数据结构,适合轻量级场景。但是, 如果不同资源的处理能力差异较大,Round-Robin
可能导致高负载的资源处理不过来,而低负载的资源处于空闲状态。如果任务的大小或耗时不均匀,轮询分配可能导致任务处理效率低。当资源数量动态变化时(如新增或移除服务器),需要重新初始化资源列表,影响性能。
在实际应用中,Round-Robin
非常适合资源能力相当、任务轻量的场景。如果任务或资源存在差异,可以引入 权重轮询(Weighted Round-Robin
) 或其他更复杂的调度算法。
1.2 如何使用 PM2 部署 Node 服务?
使用配置文件(ecosystem.config.js
)部署 Node
服务: PM2
支持 pm2 start 入口文件
快速启动服务。 PM2
也支持支持通过配置文件来管理服务,方便处理更复杂的场景,比如多个服务、不同环境配置。通过配置 ecosystem.config.js
, 比如: script
入口、instances
多进程模式,max
表示根据 CPU
核心数自动配置。exec_mode
: fork
(默认)单进程,cluster
多进程模式。max_memory_restart
超过内存阈值时自动重启,防止内存泄漏。watch
启用文件监听,文件变更时自动重启服务。在生产环境中,为了保证服务随系统启动,PM2
提供了 开机自启动 的功能。
1.3 为什么使用 PM2 部署 Node 服务?
使用 PM2
部署 Node
服务主要是因为 PM2
能够解决以下关键问题,提升服务的 高可用性、稳定性 和 性能,同时降低运维的复杂性。
-
进程守护与自动重启:
Node.js
的单线程特性意味着,如果服务崩溃或者异常退出,进程就会终止,服务中断。PM2
提供了进程守护功能,自动监控和管理服务进程。Node.js
服务意外崩溃时,PM2
能自动重启进程,避免服务中断,减少人工介入。同时PM2
能够实时监控服务的运行状态,并记录日志,便于后续问题排查。 -
多进程支持与集群模式(
Cluster Mode
):Node.js
是单线程运行的,单个实例无法充分利用多核CPU
资源。PM2
通过Cluster Mode
,利用Node.js
的cluster
模块,启动多个进程,实现多核CPU
负载均衡,提高并发处理能力。PM2
使用 轮询(Round-Robin
)算法 将请求均匀分发给不同的进程。 -
资源管理与故障恢复:
PM2
提供资源管理功能,可以限制单个进程的内存和CPU
使用量,防止资源耗尽。避免内存泄漏导致服务器崩溃。在故障发生时自动恢复服务,提升服务稳定性。 -
文件变更监听与热重启:
PM2
提供 文件变更监听 功能,可以在代码发生变更时自动重启服务,避免手动重启带来的麻烦。 -
服务日志管理:
PM2
提供了完善的日志管理功能,自动记录进程的输出、错误信息和资源使用情况。记录服务运行日志,便于快速排查故障。统一管理日志,方便运维和监控。
1.4 Kubernetes 部署 Node Vs PM2 部署 Node
PM2
是一个流行的进程管理工具,专门用于管理 Node.js
应用程序。虽然 PM2
对于 Node.js
应用的部署与管理非常有效,但相比于 Kubernetes
,PM2
更适用于单机环境或者较小的服务集群。以下是二者的主要区别和优势对比:
PM2
:PM2
主要用于单节点的进程管理,虽然支持多进程模式(通过 pm2 scale
命令),但其本质上是管理本地机器上的 Node.js
进程,不能自动扩展到多个机器,也无法进行跨节点的服务管理。
Kubernetes
:Kubernetes
是一个分布式系统平台,能够管理跨多个节点的服务部署、扩展和管理。通过 Pods
、Deployments
和 ReplicaSets
,可以轻松实现自动扩容、滚动更新、负载均衡等功能,适用于大规模分布式应用。
如果你的应用规模较小,PM2
是一个简单有效的进程管理工具,而如果你面临的是大规模、分布式的 Node
服务部署,Kubernetes
提供的自动化管理、高可用性、资源管理和扩展性优势将显得更加突出。
二、构建服务
三、部署服务
使用配置文件(ecosystem.config.js
)部署 Node
服务: PM2
支持 pm2 start 入口文件
快速启动服务。 PM2
也支持支持通过配置文件来管理服务,方便处理更复杂的场景,比如多个服务、不同环境配置。
3.1 创建配置文件
在项目根目录下创建 ecosystem.config.js
文件:
3.2 编写配置文件
ecosystem.config.js
配置示例:
module.exports = {
apps: [
{
name: "my-node-service", // 应用名称
script: "app.js", // 启动脚本路径
instances: "max", // 启动实例数 (max 表示根据 CPU 核心数启动)
exec_mode: "cluster", // 启用集群模式 (多进程)
watch: true, // 开启文件变更监听,自动重启服务
max_memory_restart: "300M", // 内存超过 300M 自动重启
env: { // 默认环境变量
NODE_ENV: "development",
PORT: 3000
},
env_production: { // 生产环境变量
NODE_ENV: "production",
PORT: 8000
}
}
]
};
-
instances
: 多进程模式,max
表示根据CPU
核心数自动配置。 -
exec_mode
:fork
(默认)单进程,cluster
多进程模式。 -
watch
: 启用文件监听,文件变更时自动重启服务。 -
max_memory_restart
: 超过内存阈值时自动重启,防止内存泄漏。 -
env / env_production
: 环境变量设置。
3.3 操作 Node 服务
启动服务:
pm2 start ecosystem.config.js --env production
重启服务
pm2 restart ecosystem.config.js
停止服务
pm2 stop ecosystem.config.js
删除服务
pm2 delete app.js
查看进程表
pm2 list
监控进程资源
pm2 monit
日志查看
pm2 logs
在生产环境中,为了保证服务随系统启动,PM2
提供了 开机自启动 的功能。
# 1. 生成开机启动脚本
pm2 startup
# 2. 根据输出的命令执行,例如在 Linux 系统中
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u your-user --hp /home/your-user
# 3. 保存当前服务列表: pm2 save 会将当前 PM2 管理的进程列表保存到磁盘,重启后自动恢复。
pm2 save