0

0

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

青灯夜游

青灯夜游

发布时间:2022-04-02 20:08:42

|

3986人浏览过

|

来源于掘金社区

转载

本篇文章聊聊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 进程主动退出,主要包含下面几种情况:

小微助手
小微助手

微信推出的一款专注于提升桌面效率的助手型AI工具

下载
  • 代码执行过程中触发了未捕获的错误,可以通过 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}`);
}

应用实践

上面分析了 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 server
const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).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 教程

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

89

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

276

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

59

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

99

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

105

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

230

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

619

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

173

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Node.js 教程
Node.js 教程

共57课时 | 13.4万人学习

【web前端】Node.js快速入门
【web前端】Node.js快速入门

共16课时 | 2.1万人学习

Node.js-前端工程化必学
Node.js-前端工程化必学

共19课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号