# 多进程优化:进程守护与管理
# Node.js 的稳定性
由于nodejs的单线程的脆弱性,一旦遇到运行错误便会严重到退出 node
进程导致系统或应用瘫痪。
所以,我们可以通过 进程守护
,在主进程对服务器进程进行监控。这样就可以大大提高服务器进程的健康程度,从而提高 Node.js
程序的稳定性。
# 进程守护
- 子进程捕获
uncaughtException
异常,并主动退出;
- 子进程捕获
- 父进程监听子进程退出事件,并延迟创建子进程;
- 子进程
监控内存
占用过大,主动退出;
- 子进程
- 父进程监听子进程心跳,指定时间内没有返回对应的心跳包,杀掉子进程;
/**
* 简单的进程守护器
*/
const cluster = require('cluster');
if (cluster.isMaster) {
// console.log(require('os').cpus())
for (let i = 0; i < require('os').cpus().length / 2; i++) {
createWorker();
}
// 父进程监听子进程退出事件,并延迟创建子进程;
cluster.on('exit', function () {
setTimeout(() => {
createWorker()
}, 5000)
})
function createWorker() {
// 创建子进程并进行心跳监控
var worker = cluster.fork();
var missed = 0;// 没有回应的ping次数
// 心跳 父进程监听子进程心跳,指定时间内没有返回对应的心跳包,杀掉子进程;
var timer = setInterval(function () {
// 三次没回应,杀之
if (missed == 3) {
clearInterval(timer);
console.log(worker.process.pid + ' has become a zombie!');
process.kill(worker.process.pid);
return;
}
// 开始心跳
missed++;
worker.send('ping#' + worker.process.pid);
}, 10000);
worker.on('message', function (msg) {
// 确认心跳回应。
if (msg == 'pong#' + worker.process.pid) {
missed--;
}
});
// 挂了就没必要再进行心跳了
worker.on('exit', function () {
clearInterval(timer);
});
}
} else {
// 当进程出现会崩溃的错误 子进程捕获uncautht异常,并主动退出;
process.on('uncaughtException', function (err) {
// 这里可以做写日志的操作
console.log(err);
// 退出进程
process.exit(1);
});
// 回应心跳信息
process.on('message', function (msg) {
if (msg == 'ping#' + process.pid) {
process.send('pong#' + process.pid);
}
});
// 内存使用过多,自杀 子进程监控内存占用过大,主动退出;
if (process.memoryUsage().rss > 734003200) {
process.exit(1);
}
require('./app')
}
# uncaughtException 捕获错误
当服务器进程(子进程)出现错误时,可以通过 uncaughtException
监听未捕获的错误。我们可以打印错误。或者在线上环境时,将错误上报到对应的日志服务。
官方文档有说明,uncaughtException
事件默认有一个 1
的错误码,退出这个进程。但是,如果我们手动监听了 uncaughtException
事件,这个默认的行为就会取消掉,服务器进程(子进程)出现错误时就不会退出进程了。
但是如果我们手动监听了 uncaughtException
事件,导致取消了默认的行为。并且没有手动退出进程,很有可能导致应用已经处于了未定义的状态。如果基于这种状态,尝试恢复应用正常进行,可能会造成未知或不可预测的问题。所以,我们应该手动退出进程。避免不可预测的风险。
子进程退出后,为了利用多核CPU的性能。我们可以在父进程中,监听子进程退出事件,创建新的子进程。
此外如果子进程发生的错误,是一直发生的。比如,一启动子进程,这个进程马上就死掉了。
那么如果我们在父进程中同步创建新的子进程,那么会导致消耗大量的CPU,最终也会导致程序崩溃。所以我们通过设置超时,采用相对更加安全的方式创建子进程。
通过上面的操作,即使子进程偶尔出现错误(仅限是偶尔的情况),我们也能在一定时间之内,将其恢复,重启回来。
# 捕获内存泄露
如果有内存泄露会大大降低服务器性能。我们还可以进行内存监控,主动退出进程,避免将来服务性能越来越低,同时也可以将内存泄露信息上报到日志服务。
同时父进程中,会监听子进程退出事件,创建新的子进程。
// 内存使用过多,自杀 子进程监控内存占用过大,主动退出;
if (process.memoryUsage().rss > 734003200) {
process.exit(1);
}
# 心跳检测
在主进程中,每隔一段时间给子进程发送一个心跳包。如果,指定时间内子进程没有返回对应的心跳返回包,我们就可以认为,这个子进程进入了假死状态,是一个僵死进程。我们可以在父进程直接杀死这个子进程。
例如,子进程存在一个死循环,会阻塞子进程中 message
事件的触发。这个子进程进入了假死状态。父进程在指定时间内,没有收到指定的心跳返回包,就会直接杀死这个子进程。