聊聊各种可能导致 Node.js 进程退出的情况

本篇文章聊聊Node的进程退出 , 介绍各种可能导致 Node.js 进程退出的情况 , 希望对大家有所帮助!

聊聊各种可能导致 Node.js 进程退出的情况

文章插图

在我们的服务发布后 , 难免会被运行环境(如容器、pm2 等)调度、升级服务导致重启、各种异常导致进程崩溃;一般情况下 , 运行环境都有对服务进程的健康监测 , 在进程异常时 , 会重新拉起进程 , 在升级时 , 也有滚动升级的策略 。 但运行环境的调度策略是把我们服务的进程当成黑盒来处理的 , 不会管服务进程内部的运行情况 , 因此需要我们的服务进程主动感知运行环境的调度动作 , 然后做一些退出的清理动作 。
因此我们今天就是梳理各种可能导致 Node.js 进程退出的情况 , 以及我们可以通过监听这些进程退出事件做哪些事情 。
原理
一个进程要退出 , 无非就是两种情况 , 一是进程自己主动退出 , 另外就是收到系统信号 , 要求进程退出 。
系统信号通知退出
在 Node.js 官方文档 中列出了常见的系统信号 , 我们主要关注几个:
    SIGHUP:不通过 ctrl+c 停止进程 , 而是直接关闭命令行终端 , 会触发该信号SIGINT:按下 ctrl+c 停止进程时触发;pm2 重启或者停止子进程时 , 也会向子进程发送该信号SIGTERM:一般用于通知进程优雅退出 , 如 k8s 删除 pod 时 , 就会向 pod 发送 SIGTERM 信号 , pod 可以在超时时间内(默认 30s)做一些退出清理动作SIGBREAK:在 window 系统上 , 按下 ctrl+break 会触发该信号SIGKILL:强制退出进程 , 进程无法做任何清理动作 , 执行命令 kill -9 pid , 进程会收到该信号 。 k8s 删除 pod 时 , 如果超过 30s , pod 还没退出 , k8s 会向 pod 发送 SIGKILL 信号 , 立即退出 pod 进程;pm2 在重启或者停止进程时 , 如果超过 1.6s , 进程还没退出 , 也会发送 SIGKILL 信号
在收到非强制退出信号时 , Node.js 进程可以监听退出信号 , 做一些自定义的退出逻辑 。 比如我们写了一个 cli 工具 , 需要比较长的时间执行任务 , 如果用户在任务执行完成前想要通过 ctrl+c 退出进程时 , 可以提示用户再等等:
const readline = require('readline');process.on('SIGINT', () => { // 我们通过 readline 来简单地实现命令行里面的交互 const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('任务还没执行完 , 确定要退出吗?', answer => { if (answer === 'yes') { console.log('任务执行中断 , 退出进程'); process.exit(0); } else { console.log('任务继续执行...'); } rl.close(); });});// 模拟一个需要执行 1 分钟的任务const longTimeTask = () => { console.log('task start...'); setTimeout(() => { console.log('task end'); }, 1000 * 60);};longTimeTask();实现效果如下 , 每次按下 ctrl + c 都会提示用户:
聊聊各种可能导致 Node.js 进程退出的情况

文章插图

进程主动退出
Node.js 进程主动退出 , 主要包含下面几种情况:
    代码执行过程中触发了未捕获的错误 , 可以通过 process.on('uncaughtException') 监听这种情况代码执行过程中触发了未处理的 promise rejection(Node.js v16 开始会导致进程退出) , 可以通过 process.on('unhandledRejection') 监听这种情况EventEmitter 触发了未监听的 error 事件代码中主动调用 process.exit 函数退出进程 , 可以通过 process.on('exit') 监听Node.js 的事件队列为空 , 可简单认为没有需要执行的代码了 , 可以通过 process.on('exit') 监听
我们知道 pm2 有守护进程的效果 , 在你的进程发生错误退出时 , pm2 会重启你的进程 , 我们也在 Node.js 的 cluster 模式下 , 实现一个守护子进程的效果(实际上 pm2 也是类似的逻辑):
const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;const process = require('process');// 主进程代码if (cluster.isMaster) { console.log(`启动主进程: ${process.pid}`); // 根据 cpu 核数 , 创建工作进程 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } // 监听工作进程退出事件 cluster.on('exit', (worker, code, signal) => { console.log(`工作进程 ${worker.process.pid} 退出 , 错误码: ${code || signal}, 重启中...`); // 重启子进程 cluster.fork(); });}// 工作进程代码if (cluster.isWorker) { // 监听未捕获错误事件 process.on('uncaughtException', error => { console.log(`工作进程 ${process.pid} 发生错误`, error); process.emit('disconnect'); process.exit(1); }); // 创建 web server // 各个工作进程都会监听端口 8000(Node.js 内部会做处理 , 不会导致端口冲突) http.createServer((req, res) => { res.writeHead(200); res.end('hello world\n'); }).listen(8000); console.log(`启动工作进程: ${process.pid}`);}

推荐阅读