进程守护
一、认识
启动方式
守护进程的启动方式,过程如下:
- 创建一个进程A
- 在进程A中创建进程B,我们可以使用
fork
方式,或者其他方法 - 对进程B执行
setsid
方法 - 进程A退出,进程B由
init
进程接管。此时进程B为守护进程
setsid
setsid 工作
setid
主要完成三件事:
- 该进程变成一个新会话的会话领导
- 该进程变成一个新进程组的组长
- 该进程没有控制终端
然而, Nodejs
中并没有对 setsid
方法的直接封装,翻阅文档发现有一个地方是可以调用该方法的。
setsid 调用
var spawn = require('child_process').spawn;
var process = require('process');
var p = spawn('node',['b.js'],{
detached : true
});
console.log(process.pid, p.pid);
process.exit(0);
在 spawn
的第三个参数中,可以设置 detached
属性,如果该属性为 true
,则会调用 setsid
方法。这样就满足我们对守护进程的要求。
二、实现
三、问题
3.1 请问实现一个 Node 子进程被杀死,然后自动重启代码的思路?
思路:
-
父进程启动子进程, 使用
fork
(或spawn
)创建子进程,将子进程的生命周期纳入父进程的管理范围。 -
监听退出事件, 为子进程绑定
exit
事件。当子进程因异常退出或者被杀死时,exit
事件会被触发,并返回退出码和信号。 -
重启子进程, 在
exit
事件处理器中,调用创建子进程的函数重新启动一个新的子进程,从而实现自动重启。 -
异常与错误处理, 同时监听
error
事件,确保在发生错误时能记录日志或采取其他必要的措施。 -
防止无限重启, 根据实际需求,可以设置一定的重启次数限制或者重启时间间隔,以防止因子进程连续异常导致无限重启。
const { fork } = require('child_process');
// 定义一个函数用于启动子进程
function startChild() {
// 使用 fork 创建子进程,这里假设子进程代码在 child.js 中
const child = fork('./child.js');
// 监听子进程退出事件
child.on('exit', (code, signal) => {
console.log(`子进程退出,退出码: ${code}, 信号: ${signal}`);
// 在这里可以添加重启次数限制或者重启间隔等待等逻辑
console.log('正在重启子进程...');
startChild();
});
// 监听子进程错误事件
child.on('error', (err) => {
console.error('子进程发生错误:', err);
});
return child;
}
// 启动子进程
startChild();
3.2 实现限量重启, 比如我最多让其在1分钟内重启5次, 超过了就报警给运维?
思路:
-
父进程启动子进程, 使用
fork
(或spawn
)创建子进程,将子进程的生命周期纳入父进程的管理范围。 -
监听退出事件, 为子进程绑定
exit
事件。当子进程因异常退出或者被杀死时,exit
事件会被触发, 开始进行限量重启处理:-
记录重启时间, 使用
restartTimes
数组记录每次重启时的时间戳。每次子进程退出后, 将当前时间加入该数组。 -
过滤时间窗口, 通过过滤
restartTimes
中超过1
分钟(60 * 1000
毫秒)的时间戳, 只保留最近1
分钟内的重启记录。(滑动窗口的思想) -
判断重启次数, 如果
1
分钟内的重启记录超过5
次,则调用alertOps()
发出报警,并不再自动重启子进程。实际应用中,可以结合报警系统或日志系统处理后续动作。 -
自动重启, 如果重启次数未达到限制, 调用
startChild()
重新启动子进程,从而实现自动恢复。
-
-
重启子进程, 在
exit
事件处理器中,调用创建子进程的函数重新启动一个新的子进程,从而实现自动重启。 -
异常与错误处理, 同时监听
error
事件,确保在发生错误时能记录日志或采取其他必要的措施。
const { fork } = require('child_process');
// 用于记录每次重启的时间戳(毫秒)
let restartTimes = [];
// 模拟报警函数,实际场景中可以调用邮件、短信或监控系统接口
function alertOps() {
console.error('警告:子进程在 1 分钟内重启次数超过 5 次,请及时检查!');
// 这里可以扩展,比如发送邮件或调用运维系统接口
}
// 启动子进程的函数
function startChild() {
const child = fork('./child.js');
// 监听子进程退出事件
child.on('exit', (code, signal) => {
console.log(`子进程退出,退出码: ${code}, 信号: ${signal}`);
// 记录当前重启的时间
const now = Date.now();
restartTimes.push(now);
// 过滤掉超过 1 分钟的重启记录
restartTimes = restartTimes.filter(timestamp => now - timestamp < 60 * 1000);
// 如果在 1 分钟内的重启次数超过 5 次,则报警并不再重启
if (restartTimes.length > 5) {
alertOps();
return;
}
console.log('正在重启子进程...');
startChild();
});
// 监听子进程错误事件
child.on('error', (err) => {
console.error('子进程发生错误:', err);
});
return child;
}
// 启动初次子进程
startChild();