Maison > interface Web > js tutoriel > Parlons de diverses situations pouvant entraîner la fermeture du processus Node.js.

Parlons de diverses situations pouvant entraîner la fermeture du processus Node.js.

青灯夜游
Libérer: 2022-04-02 20:09:02
avant
3631 Les gens l'ont consulté

Cet article parle de la sortie du processus de Node et présente diverses situations pouvant entraîner la sortie du processus Node.js. J'espère qu'il sera utile à tout le monde !

Parlons de diverses situations pouvant entraîner la fermeture du processus Node.js.

Une fois notre service publié, il sera inévitablement planifié par l'environnement d'exploitation (comme les conteneurs, pm2, etc.), les mises à niveau du service provoqueront des redémarrages et diverses exceptions provoqueront un crash du processus d'une manière générale, l'environnement d'exploitation a des restrictions sur le processus de service. La surveillance de l'état redémarrera le processus lorsque le processus est anormal. Lors de la mise à niveau, il existe également une stratégie de mise à niveau continue. Cependant, la stratégie de planification de l'environnement d'exécution traite notre processus de service comme une boîte noire et ne se soucie pas des conditions d'exécution internes du processus de service. Par conséquent, notre processus de service doit détecter activement les actions de planification de l'environnement d'exécution, puis les exécuter. certaines actions de nettoyage de sortie.

Aujourd'hui, nous allons trier les différentes situations qui peuvent provoquer la sortie du processus Node.js, et ce que nous pouvons faire en écoutant ces événements de sortie de processus.

Principe

Un processus doit se terminer dans deux situations : l'une est que le processus se termine de sa propre initiative, et l'autre est qu'il reçoit un signal système exigeant que le processus se termine.

Notification du signal système pour quitter

Les signaux système courants sont répertoriés dans le document officiel Node.js Nous nous concentrons principalement sur quelques-uns :

  • SIGHUP : n'arrêtez pas le processus via ctrl+c, mais fermez. directement Le terminal de ligne de commande déclenchera ce signal
  • SIGINT : Déclenché lorsque ctrl+c est enfoncé pour arrêter le processus ; lorsque pm2 redémarre ou arrête le processus enfant, il enverra également ce signal au processus enfant
  • SIGTERM : Généralement utilisé pour avertir le processus de se terminer correctement. Par exemple, lorsque k8s supprime un pod, il enverra un signal SIGTERM au pod. Le pod peut effectuer certaines actions de nettoyage de sortie dans le délai d'attente (30 s par défaut)
  • SIGBREAK : sur le. système de fenêtre, appuyer sur ctrl+break déclenchera ce signal
  • SIGKILL : quitter de force le processus. Le processus ne peut effectuer aucune action de nettoyage. Si vous exécutez la commande kill -9 pid, le processus recevra ce signal. Lorsque k8s supprime un pod, si cela prend plus de 30 secondes et que le pod ne s'est pas terminé, k8s enverra un signal SIGKILL au pod et quittera immédiatement le processus du pod lorsque pm2 redémarre ou arrête le processus, si cela prend plus de 1,6 ; secondes et que le processus ne s'est pas terminé, il enverra également un signal SIGKILL

Lors de la réception d'un signal de sortie non forcé, le processus Node.js peut écouter le signal de sortie et effectuer une logique de sortie personnalisée. Par exemple, nous avons écrit un outil cli qui prend beaucoup de temps pour exécuter une tâche. Si l'utilisateur souhaite quitter le processus via ctrl+c avant que la tâche ne soit terminée, l'utilisateur peut être invité à attendre :

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();
Copier après la connexion

L'effet est comme suit, chaque fois que l'utilisateur appuie sur ctrl + c, l'utilisateur sera invité :

Parlons de diverses situations pouvant entraîner la fermeture du processus Node.js.

Le processus se termine activement

Le processus Node.js se termine activement, incluant principalement les situations suivantes :

  • Les erreurs non détectées sont déclenché lors de l'exécution du code, vous pouvez passer process.on('uncaughtException') surveille cette situation
  • Un rejet de promesse non géré est déclenché lors de l'exécution du code (Node.js v16 entraînera la fermeture du processus), vous pouvez surveiller cette situation via le processus. on('unhandledRejection') Situation
  • EventEmitter déclenche un événement d'erreur non surveillé
  • Dans le code, la fonction process.exit est activement appelée pour quitter le processus. Vous pouvez la surveiller via process.on('exit')
  • Le. La file d'attente d'événements de Node.js est vide. Vous pouvez simplement penser qu'il n'y en a pas. Le code qui doit être exécuté peut être surveillé via process.on('exit')

Nous savons que pm2 a l'effet d'un processus démon. . Lorsque votre processus se termine avec une erreur, pm2 redémarrera votre processus. Nous sommes également en Node. Sous le mode cluster de js, l'effet d'un processus enfant démon est obtenu (en fait, pm2 a une logique similaire).

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(&#39;exit&#39;, (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 退出,错误码: ${code || signal}, 重启中...`);
    // 重启子进程
    cluster.fork();
  });
}

// 工作进程代码
if (cluster.isWorker) {
  // 监听未捕获错误事件
  process.on(&#39;uncaughtException&#39;, error => {
    console.log(`工作进程 ${process.pid} 发生错误`, error);
    process.emit(&#39;disconnect&#39;);
    process.exit(1);
  });
  // 创建 web server
  // 各个工作进程都会监听端口 8000(Node.js 内部会做处理,不会导致端口冲突)
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(&#39;hello world\n&#39;);
  }).listen(8000);
  console.log(`启动工作进程: ${process.pid}`);
}
Copier après la connexion

Pratique d'application

Ce qui précède a analysé diverses situations dans lesquelles le processus Node.js se termine. Créons maintenant un outil qui surveille la sortie du processus lorsque le processus Node.js se termine, il permet à l'utilisateur de l'exécuter. propre logique de sortie :

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

Numéro de signal :

Parlons de diverses situations pouvant entraîner la fermeture du processus Node.js.

接下来我们要实现真正的进程退出函数 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()
  }
};
Copier après la connexion

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

https://github.com/darukjs/daruk-exit-hook

进程优雅退出

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

const http = require(&#39;http&#39;);

// 创建 web server
const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end(&#39;hello world\n&#39;);
}).listen(8000);

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

总结

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal