聊聊各种可能导致 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 都会提示用户:

1.png

进程主动退出

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  {    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');  }).listen(8000);  console.log(`启动工作进程: ${process.pid}`);}

登录后复制

应用实践

上面分析了 Node.js 进程退出的各种情况,现在我们来做一个监听进程退出的工具,在 Node.js 进程退出时,允许使用方执行自己的退出逻辑:

// exit-hook.js// 保存需要执行的退出任务const tasks = [];// 添加退出任务const addExitTask = fn => tasks.push(fn);const handleExit = (code, error) => {    // ...handleExit 的实现见下面};// 监听各种退出事件process.on('exit', code => handleExit(code));// 按照 POSIX 的规范,我们用 128 + 信号编号 得到最终的退出码// 信号编号参考下面的图片,大家可以在 linux 系统下执行 kill -l 查看所有的信号编号process.on('SIGHUP', () => handleExit(128 + 1));process.on('SIGINT', () => handleExit(128 + 2));process.on('SIGTERM', () => handleExit(128 + 15));// windows 下按下 ctrl+break 的退出信号process.on('SIGBREAK', () => handleExit(128 + 21));// 退出码 1 代表未捕获的错误导致进程退出process.on('uncaughtException', error => handleExit(1, error));process.on('unhandledRejection', error => handleExit(1, error));

登录后复制

信号编号:

2.png

接下来我们要实现真正的进程退出函数 handleExit,因为用户传入的任务函数可能是同步的,也可能是异步的;我们可以借助 process.nextTick 来保证用户的同步代码都已经执行完成,可以简单理解 process.nextTick 会在每个事件循环阶段的同步代码执行完成后执行(理解 process.nextTick);针对异步任务,我们需要用户调用 callback 来告诉我们异步任务已经执行完成了:

// 标记是否正在退出,避免多次执行let isExiting = false;const handleExit = (code, error) => {  if (isExiting) return;  isExiting = true;  // 标记已经执行了退出动作,避免多次调用  let hasDoExit = fasle;  const doExit = () => {      if (hasDoExit) return;      hasDoExit = true      process.nextTick(() => process.exit(code))  }  // 记录有多少个异步任务  let asyncTaskCount = 0;  // 异步任务结束后,用户需要调用的回调  let ayncTaskCallback = () => {      process.nextTick(() => {        asyncTaskCount--        if (asyncTaskCount === 0) doExit()       })  }  // 执行所有的退出任务  tasks.forEach(taskFn => {      // 如果 taskFn 函数的参数个数大于 1,认为传递了 callback 参数,是一个异步任务      if (taskFn.length > 1) {         asyncTaskCount++         taskFn(error, ayncTaskCallback)      } else {          taskFn(error)      }  });  // 如果存在异步任务  if (asyncTaskCount > 0) {      // 超过 10s 后,强制退出      setTimeout(() => {          doExit();      }, 10 * 1000)  } else {      doExit()  }};

登录后复制

至此,我们的进程退出监听工具就完成了,完整的实现可以查看这个开源库 async-exit-hookhttps://github.com/darukjs/daruk-exit-hook

进程优雅退出

通常我们的 web server 在重启、被运行容器调度(pm2 或者 docker 等)、出现异常导致进程退出时,我们希望执行退出动作,如完成已经连接到服务的请求响应、清理数据库连接、打印错误日志、触发告警等,做完退出动作后,再退出进程,我们可以使用刚才的进程退出监听工具实现:

const http = require('http');// 创建 web serverconst server = http.createServer((req, res) => {  res.writeHead(200);  res.end('hello world');}).listen(8000);// 使用我们在上面开发的工具添加进程退出任务addExitTask((error, callback) => {   // 打印错误日志、触发告警、释放数据库连接等   console.log('进程异常退出', error)   // 停止接受新的请求   server.close((error) => {       if (error) {         console.log('停止接受新请求错误', error)       } else {         console.log('已停止接受新的请求')       }   })   // 比较简单的做法是,等待一定的时间(这里我们等待 5s),让存量请求执行完毕   // 如果要完全保证所有请求都处理完毕,需要记录每一个连接,在所有连接都释放后,才执行退出动作   // 可以参考开源库 https://github.com/sebhildebrandt/http-graceful-shutdown   setTimout(callback, 5 * 1000)})

登录后复制

总结

通过上面的文字,相信你已经对导致 Node.js 进程退出的各种情况心里有数了。在服务上线后,虽然 k8s、pm2 等工具能够在进程异常退出时,不停地拉起进程,保证服务的可用性,但我们也应该在代码中主动感知进程的异常或者被调度的情况,从而能够更早发现问题。

更多node相关知识,请访问:nodejs 教程!

以上就是聊聊各种可能导致 Node.js 进程退出的情况的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2701807.html

(0)
上一篇 2025年3月7日 19:18:26
下一篇 2025年3月7日 05:54:07

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 浅析Node中Express的错误处理中间件

    本篇文章带大家了解一下node中express的错误处理中间件,介绍一下定义错误处理中间件、与 async/await 一起使用的方法,希望对大家有所帮助! Express 的错误处理中间件可帮助您处理错误,而无需重复同样的工作。假设您直接…

    2025年3月7日
    200
  • 深入浅析Nodejs中的net模块

    本篇文章带大家带大家了解一下node中的net模块,希望对大家有所帮助! 这是 node 系列的第一篇文章, 我之前看教程的时候,很多都是从 IO、buffer、path、event、fs、process、node 事件循环机制开始说起的.…

    2025年3月7日 编程技术
    200
  • 深入聊聊Node.js中的模块化

    什么是模块化?本篇文章带大家了解一下node中的模块化,介绍一下模块的加载机制,希望对大家有所帮助! 一、模块化的基本概念 1.1 什么是模块化 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是…

    2025年3月7日 编程技术
    200
  • 什么是单点登录?怎么使用Nodejs实现SSO

    什么是单点登录?下面本篇文章给大家给大家介绍一下单点登录原理,聊聊使用node实现单点登录sso的方法,希望对大家有所帮助! 什么是单点登录 随着公司业务的增多,必然会产生各个不同的系统,如果每个系统都需要单独登录的话就会很不方便。 因此产…

    2025年3月7日 编程技术
    200
  • 浅析利用node怎么获取mac系统版本

    利用node怎么获取mac系统版本?下面本篇文章给大家介绍一下利用node优雅地获取 mac 系统版本的方法,希望对大家有所帮助! 今天突然碰到了一个兼容性需求,需要根据不同 macOS 版本,进行不同的兼容性处理。 没想到看似简单的需求,…

    2025年3月7日
    200
  • node爬取数据实例:聊聊怎么抓取小说章节

    node怎么爬取数据?下面本篇文章给大家分享一个node爬虫实例,聊聊利用node抓取小说章节的方法,希望对大家有所帮助! 准备用electron制作一个小说阅读工具练练手,那么首先要解决的就是数据问题,也就是小说的文本。 这里准备使用no…

    2025年3月7日 编程技术
    200
  • Vercel是什么?怎么部署Node服务?

    vercel是什么?本篇文章带大家了解一下vercel,并介绍一下在vercel中部署 node 服务的方法,希望对大家有所帮助! 之前在写node时,曾经就强缓存和协商缓存写过两个demo,但缓存要在服务端做,只能贴上代码,不能在网页上感…

    2025年3月7日 编程技术
    200
  • 什么是JWT?JWT怎么在nodejs中使用?

    什么是jwt?本篇文章带大家了解一下jwt,介绍一下jwt在node中的应用,以及jwt的优缺点,希望对大家有所帮助! 什么是JWT JWT也就是JSON Web Token的缩写,也就是为了在网络应用环境中一种认证解决方案,在传统的认证机…

    2025年3月7日 编程技术
    200
  • 浅析nodejs项目中package.json的常见属性

    本篇文章带大家了解一下node项目中的package.json配置文件,聊聊package.json中一些常见配置属性、环境相关属性、依赖相关属性和三方属性,希望对大家有所帮助! npm是前端开发人员广泛使用的包管理工具,项目中通过pack…

    2025年3月7日
    200
  • 浅谈Node第三方框架Express的使用方法

    本篇文章带大家了解一下node第三方框架express,简单聊聊如何使用好express框架,希望对大家有所帮助! 1.Express框架介绍 1.Express是node开发中一个非常重量级的第三方框架,它对于NodeJS服务端就相当于J…

    2025年3月7日
    200

发表回复

登录后才能评论