# 多进程优化:进程守护与管理

# Node.js 的稳定性

由于nodejs的单线程的脆弱性,一旦遇到运行错误便会严重到退出 node 进程导致系统或应用瘫痪。

所以,我们可以通过 进程守护,在主进程对服务器进程进行监控。这样就可以大大提高服务器进程的健康程度,从而提高 Node.js 程序的稳定性。

# 进程守护

    1. 子进程捕获 uncaughtException 异常,并主动退出;
    1. 父进程监听子进程退出事件,并延迟创建子进程;
    1. 子进程 监控内存 占用过大,主动退出;
    1. 父进程监听子进程心跳,指定时间内没有返回对应的心跳包,杀掉子进程;
/**
 * 简单的进程守护器
 */
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 事件的触发。这个子进程进入了假死状态。父进程在指定时间内,没有收到指定的心跳返回包,就会直接杀死这个子进程。

# 进程守护方案

  • pm2:带有内置负载均衡器的Node.js Production Process Manager。
  • forever:一个nodejs的守护进程,能够启动,停止,重启我们的app应用。

# 參考

更新时间: 6/30/2020, 2:09:36 AM