NODE服务优雅退出

最近 socket 服务遇到了脏进程的问题,也有人叫僵尸进程,复现步骤基本如下

const cp = require('child_process');

const worker1 = cp.fork('./bin/worker');
const worker2 = cp.fork('./bin/worker');

setTimeout(() => {
  console.info(`主进程关闭`);
  process.exit();
}, 3000);

// bin/worker
setInterval(() => {
  console.info(`子进程 setInterval:`, Date.now());
}, 3000);

无论是process.exit()还是 ctrl+c 结束,主进程关闭后子进程依然在进行,查看 NODE API 发现有这一句:

On Linux, child processes of child processes will not be terminated when attempting to kill their parent.

在 Linux,子进程的子进程不会被终止,当它们的父进程退出时

然后在网上有人说:

默认情况,父进程退出,子进程变为"孤儿进程",孤儿进程会被托管给进程 init,也就是进程 1,所以 kill B 进程,B 进程的子进程 C D 会被托管给进程 1,不会被终止。
请注意“僵尸进程”,你 KILL B 进程的时候 ,A 进程有没有 wait,如果没有会产生僵尸进程。想终止 CD 进程 可以对 CD 执行 kill 操作强制终止,发送 KILL 信号,以及使用 IPC 来通知进程,让进城自己退出。

NODE API 中有提供方法process.kill(pid[, signal]),用于终止指定进程,但如果 PID 已经重新分配给其他进程,信号就会被发送到新的进程里,造成难以复现和排查的错误,且,子进程退出前通常需要做一些收尾工作,比如发出警报,收集日志等,所以直接终止子进程大部分时间也并不适用

最终方案:

// 主
process.on('uncaughtException', (err, origin) => {
  // 记录日志、发起报警
  elegantExit();
});
process.on('exit', elegantExit);
process.on('SIGINT', elegantExit); //catches ctrl+c event
process.on('SIGUSR1', elegantExit); // catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR2', elegantExit);

// 优雅退出
function elegantExit() {
  if (elegantExit.called) return;
  elegantExit.called = true;
  子进程.send({ cmd: 'SIGHUP' }); // 不使用 kill 方法,仅 send 一条 SIGHUP 消息
  process.exit();
}
// 子
process.on('message', ({ cmd }) => {
  if (cmd === 'SIGHUP') exitProcessByMain();
});

process.on('uncaughtException', (err, origin) => {
  // 记录日志、发起报警
  exitProcessByMain();
});

// 优雅退出
function exitProcessByMain() {
  // 3000s 后进程还在,手动结束
  const to = setTimeout(() => process.exit(), 3000);
  to.unref(); // unref 不会把进程保持住
}

关于pm2,它通知会辅助管理进程,比如进程守护和垃圾进程回收(第一个例子使用 pm2 进程启动、停止、重启并不会产生上面的问题)。但实际应用中,子进程会被未断开的连接保持住,导致无法正常结束,且 pm2 在这种情况下并没有正确处理,且,如果这时手动 kill -KILL PID 会触发 pm2 的进程守护,导致这个僵尸进程引发一些无法想象事情

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 背景 有这样一个业务场景,需要主进程可以监测它创建的子进程是否还存活,通常来说使用 waitpid 来获取子进程的...
    hxysayhi阅读 1,255评论 1 47
  • 背景 1. 进程管理 进程管理主要是指创建,终止和监控进程。进程管理器主要是用来确保你的应用在启动后能够保持在线。...
    twentyshaw阅读 3,050评论 0 4
  • Spring微服务项目实现优雅停机(平滑退出) 为什么要优雅停机(平滑退出) 所以就需要优雅停机出场了,让服务在收...
    WaterMin阅读 1,622评论 0 0
  • 本文主要阐述如何让 docker 容器优雅的终止。 优雅退出定义 所谓优雅退出,指的是程序在退出之前,有清理资源、...
    莹宝与梨梦阅读 3,516评论 0 0
  • 深入浅出Nodejs 模块机制 Commonjs规范 node的模块实现步骤:路径分析文件定位编译执行核心模块在n...
    lmmy123阅读 446评论 0 1