pm2守护进程的原理
大约 6 分钟
PM2 是 Node.js 应用的高级进程管理工具,它的守护进程原理涉及多个方面。让我详细解释 PM2 的工作原理:
1. PM2 的基本架构
主进程(Daemon)模式
// PM2 采用守护进程架构
// 当运行 pm2 start app.js 时,会启动一个主守护进程
// 主要组件:
// 1. CLI 接口 - 用户交互
// 2. Daemon 进程 - 核心管理进程
// 3. Worker 进程 - 实际运行的应用进程
// 4. God 系统 - 进程监控和管理核心进程管理架构图
用户命令 (CLI)
│
▼
PM2 Daemon (主进程)
│
▼
God 系统 (进程管理核心)
│
▼
Worker 进程 (实际应用)2. 进程守护机制
进程监控和重启
// PM2 的核心监控逻辑(简化版)
class ProcessManager {
constructor() {
this.processes = new Map();
this.monitorInterval = null;
}
// 启动应用进程
startProcess(script, options) {
const child = fork(script, options.args, {
cwd: options.cwd,
env: options.env,
silent: true
});
// 监听进程事件
child.on('exit', (code, signal) => {
console.log(`进程 ${child.pid} 退出,代码: ${code}, 信号: ${signal}`);
// 根据配置决定是否重启
if (this.shouldRestart(options, code, signal)) {
this.restartProcess(script, options);
}
});
// 存储进程信息
this.processes.set(child.pid, {
process: child,
config: options,
startTime: Date.now(),
restartCount: 0
});
return child;
}
// 判断是否应该重启
shouldRestart(options, code, signal) {
// 检查重启策略
if (options.autorestart === false) return false;
// 检查最大重启次数
if (options.maxRestarts && this.restartCount >= options.maxRestarts) {
return false;
}
return true;
}
}心跳检测机制
// 心跳检测实现
class HeartbeatMonitor {
constructor(process, interval = 5000) {
this.process = process;
this.interval = interval;
this.lastHeartbeat = Date.now();
this.monitorTimer = null;
}
start() {
// 设置心跳超时检查
this.monitorTimer = setInterval(() => {
const now = Date.now();
const timeSinceLastBeat = now - this.lastHeartbeat;
if (timeSinceLastBeat > this.interval * 3) {
console.log('进程无响应,需要重启');
this.restartProcess();
}
}, this.interval);
// 监听进程心跳消息
this.process.on('message', (msg) => {
if (msg.type === 'heartbeat') {
this.lastHeartbeat = Date.now();
}
});
}
stop() {
if (this.monitorTimer) {
clearInterval(this.monitorTimer);
}
}
}3. 集群模式原理
多进程管理
const cluster = require('cluster');
const os = require('os');
// PM2 集群模式实现原理
class ClusterManager {
constructor() {
this.workers = new Map();
this.maxWorkers = os.cpus().length;
}
// 启动集群
startCluster(script, options) {
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 启动`);
// 根据 CPU 核心数启动工作进程
for (let i = 0; i < this.maxWorkers; i++) {
this.forkWorker(script, options);
}
// 监听工作进程退出事件
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出`);
// 自动重启工作进程
this.forkWorker(script, options);
});
} else {
// 工作进程执行应用代码
require(script);
}
}
// 创建工作进程
forkWorker(script, options) {
const worker = cluster.fork({
...process.env,
...options.env
});
this.workers.set(worker.id, {
worker,
script,
options,
startTime: Date.now()
});
console.log(`启动工作进程 ${worker.process.pid}`);
}
}
// 使用示例
const clusterManager = new ClusterManager();
clusterManager.startCluster('./app.js', {
instances: 'max', // 使用所有 CPU 核心
env: { NODE_ENV: 'production' }
});4. 进程间通信(IPC)
PM2 的 IPC 机制
// PM2 进程间通信示例
if (cluster.isMaster) {
// 主进程
const worker = cluster.fork();
// 发送消息给工作进程
worker.send({ type: 'config', data: { port: 3000 } });
// 接收工作进程消息
worker.on('message', (msg) => {
switch (msg.type) {
case 'ready':
console.log('应用已启动');
break;
case 'error':
console.error('应用错误:', msg.error);
break;
case 'metrics':
console.log('性能指标:', msg.data);
break;
}
});
} else {
// 工作进程
process.on('message', (msg) => {
if (msg.type === 'config') {
// 根据配置启动应用
startApp(msg.data);
}
});
// 向主进程发送消息
process.send({ type: 'ready' });
// 定期发送性能指标
setInterval(() => {
process.send({
type: 'metrics',
data: {
memory: process.memoryUsage(),
uptime: process.uptime()
}
});
}, 5000);
}
function startApp(config) {
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
});
server.listen(config.port, () => {
console.log(`服务器运行在端口 ${config.port}`);
});
}5. 日志管理和监控
日志处理机制
const fs = require('fs');
const path = require('path');
class LogManager {
constructor(logDir = './logs') {
this.logDir = logDir;
this.logStreams = new Map();
// 确保日志目录存在
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
}
// 创建日志流
createLogStream(appName, type) {
const logFile = path.join(this.logDir, `${appName}-${type}.log`);
const stream = fs.createWriteStream(logFile, { flags: 'a' });
this.logStreams.set(`${appName}-${type}`, stream);
return stream;
}
// 重定向进程输出
redirectOutput(process, appName) {
const outStream = this.createLogStream(appName, 'out');
const errStream = this.createLogStream(appName, 'error');
// 重定向 stdout 和 stderr
process.stdout.pipe(outStream);
process.stderr.pipe(errStream);
}
// 日志轮转
rotateLogs(appName) {
const outStream = this.logStreams.get(`${appName}-out`);
const errStream = this.logStreams.get(`${appName}-error`);
if (outStream) outStream.end();
if (errStream) errStream.end();
// 创建新的日志文件
this.createLogStream(appName, 'out');
this.createLogStream(appName, 'error');
}
}6. 自动重启策略
智能重启机制
class RestartManager {
constructor() {
this.restartHistory = new Map();
this.restartLimits = new Map();
}
// 智能重启策略
shouldRestart(appName, exitCode, signal) {
const now = Date.now();
const history = this.getRestartHistory(appName);
// 记录重启时间
history.push(now);
// 清理超过1小时的记录
const oneHourAgo = now - 60 * 60 * 1000;
this.restartHistory.set(
appName,
history.filter(time => time > oneHourAgo)
);
// 检查重启频率
if (history.length > 10) {
console.log(`应用 ${appName} 重启过于频繁,暂停重启`);
return false;
}
// 检查重启次数限制
const limit = this.restartLimits.get(appName) || 100;
if (history.length > limit) {
return false;
}
return true;
}
getRestartHistory(appName) {
if (!this.restartHistory.has(appName)) {
this.restartHistory.set(appName, []);
}
return this.restartHistory.get(appName);
}
// 设置重启限制
setRestartLimit(appName, limit) {
this.restartLimits.set(appName, limit);
}
}7. PM2 配置文件示例
ecosystem.config.js
module.exports = {
apps: [{
name: 'api-server',
script: './app.js',
// 进程管理配置
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster', // 集群模式
// 重启策略
max_restarts: 10, // 最大重启次数
min_uptime: '1m', // 最小运行时间
max_memory_restart: '1G', // 内存超过1G时重启
// 环境配置
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8000
},
// 日志配置
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
// 监控配置
watch: true, // 文件变化时重启
ignore_watch: ['node_modules', 'logs'],
// 进程管理
autorestart: true, // 自动重启
vizion: false, // 禁用版本控制
// 杀死进程超时
kill_timeout: 3000
}]
};8. PM2 的守护进程启动流程
守护进程生命周期
// PM2 启动流程简化版
class PM2Daemon {
constructor() {
this.isDaemon = false;
this.processes = new Map();
}
async start() {
// 检查是否已有守护进程运行
if (await this.isDaemonRunning()) {
console.log('PM2 守护进程已在运行');
return;
}
// 启动守护进程
if (cluster.isMaster) {
this.isDaemon = true;
console.log('启动 PM2 守护进程');
// 监听 CLI 命令
this.setupIPC();
// 保持进程运行
this.keepAlive();
} else {
// 启动工作进程(应用)
this.startApplication();
}
}
setupIPC() {
// 监听来自 CLI 的命令
process.on('message', async (command) => {
switch (command.action) {
case 'start':
await this.startApp(command.options);
break;
case 'stop':
await this.stopApp(command.name);
break;
case 'restart':
await this.restartApp(command.name);
break;
case 'list':
this.sendProcessList();
break;
}
});
}
async startApp(options) {
try {
const app = new Application(options);
await app.start();
this.processes.set(options.name, app);
console.log(`应用 ${options.name} 启动成功`);
} catch (error) {
console.error(`启动应用失败: ${error.message}`);
}
}
keepAlive() {
// 防止守护进程退出
setInterval(() => {
// 心跳检测
console.log('PM2 守护进程运行中...');
}, 30000);
// 处理退出信号
process.on('SIGINT', () => this.gracefulShutdown());
process.on('SIGTERM', () => this.gracefulShutdown());
}
gracefulShutdown() {
console.log('PM2 守护进程正在关闭...');
// 优雅关闭所有应用进程
for (const [name, app] of this.processes) {
app.stop();
}
process.exit(0);
}
}总结
PM2 守护进程的核心原理包括:
- 双进程架构:CLI + Daemon 主进程 + Worker 应用进程
- 进程监控:持续监控应用进程状态,异常时自动重启
- 集群管理:利用 Node.js cluster 模块实现多进程负载均衡
- IPC 通信:进程间通信机制实现命令传递和状态同步
- 智能重启:基于重启频率和次数的智能重启策略
- 日志管理:集中化日志收集和轮转
- 配置管理:灵活的配置文件支持复杂部署需求
PM2 通过这些机制确保 Node.js 应用在生产环境中的高可用性和稳定性,是生产环境中部署 Node.js 应用的首选工具。