欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Node.js進程退出的深入理解

 更新時間:2022年04月02日 11:51:48   作者:字節(jié)架構(gòu)前端  
NodeJS可以感知和控制自身進程的運行環(huán)境和狀態(tài),也可以創(chuàng)建子進程并與其協(xié)同工作,這使得NodeJS可以把多個程序組合在一起共同完成某項工作,下面這篇文章主要給大家介紹了關(guān)于Node.js進程退出的相關(guān)資料,需要的朋友可以參考下

背景介紹

在我們的服務(wù)發(fā)布后,難免會被運行環(huán)境(如容器、pm2 等)調(diào)度、升級服務(wù)導(dǎo)致重啟、各種異常導(dǎo)致進程崩潰;一般情況下,運行環(huán)境都有對服務(wù)進程的健康監(jiān)測,在進程異常時,會重新拉起進程,在升級時,也有滾動升級的策略。但運行環(huán)境的調(diào)度策略是把我們服務(wù)的進程當成黑盒來處理的,不會管服務(wù)進程內(nèi)部的運行情況,因此需要我們的服務(wù)進程主動感知運行環(huán)境的調(diào)度動作,然后做一些退出的清理動作。

因此我們今天就是梳理各種可能導(dǎo)致 Node.js 進程退出的情況,以及我們可以通過監(jiān)聽這些進程退出事件做哪些事情。

原理

一個進程要退出,無非就是兩種情況,一是進程自己主動退出,另外就是收到系統(tǒng)信號,要求進程退出。

系統(tǒng)信號通知退出

Node.js 官方文檔 中列出了常見的系統(tǒng)信號,我們主要關(guān)注幾個:

  • SIGHUP:不通過 ctrl+c 停止進程,而是直接關(guān)閉命令行終端,會觸發(fā)該信號
  • SIGINT:按下 ctrl+c 停止進程時觸發(fā);pm2 重啟或者停止子進程時,也會向子進程發(fā)送該信號
  • SIGTERM:一般用于通知進程優(yōu)雅退出,如 k8s 刪除 pod 時,就會向 pod 發(fā)送 SIGTERM 信號,pod 可以在超時時間內(nèi)(默認 30s)做一些退出清理動作
  • SIGBREAK:在 window 系統(tǒng)上,按下 ctrl+break 會觸發(fā)該信號
  • SIGKILL:強制退出進程,進程無法做任何清理動作,執(zhí)行命令 kill -9 pid,進程會收到該信號。k8s 刪除 pod 時,如果超過 30s,pod 還沒退出,k8s 會向 pod 發(fā)送 SIGKILL 信號,立即退出 pod 進程;pm2 在重啟或者停止進程時,如果超過 1.6s,進程還沒退出,也會發(fā)送 SIGKILL 信號

在收到非強制退出信號時,Node.js 進程可以監(jiān)聽退出信號,做一些自定義的退出邏輯。比如我們寫了一個 cli 工具,需要比較長的時間執(zhí)行任務(wù),如果用戶在任務(wù)執(zhí)行完成前想要通過 ctrl+c 退出進程時,可以提示用戶再等等:

const readline = require('readline');

process.on('SIGINT', () => {
  // 我們通過 readline 來簡單地實現(xiàn)命令行里面的交互
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.question('任務(wù)還沒執(zhí)行完,確定要退出嗎?', answer => {
    if (answer === 'yes') {
      console.log('任務(wù)執(zhí)行中斷,退出進程');
      process.exit(0);
    } else {
      console.log('任務(wù)繼續(xù)執(zhí)行...');
    }
    rl.close();
  });
});

// 模擬一個需要執(zhí)行 1 分鐘的任務(wù)
const longTimeTask = () => {
  console.log('task start...');
  setTimeout(() => {
    console.log('task end');
  }, 1000 * 60);
};

longTimeTask();

實現(xiàn)效果如下,每次按下 ctrl + c 都會提示用戶:

進程主動退出

Node.js 進程主動退出,主要包含下面幾種情況:

  • 代碼執(zhí)行過程中觸發(fā)了未捕獲的錯誤,可以通過 process.on('uncaughtException') 監(jiān)聽這種情況
  • 代碼執(zhí)行過程中觸發(fā)了未處理的 promise rejection(Node.js v16 開始會導(dǎo)致進程退出),可以通過 process.on('unhandledRejection') 監(jiān)聽這種情況
  • EventEmitter 觸發(fā)了未監(jiān)聽的 error 事件
  • 代碼中主動調(diào)用 process.exit 函數(shù)退出進程,可以通過 process.on('exit') 監(jiān)聽
  • Node.js 的事件隊列為空,可簡單認為沒有需要執(zhí)行的代碼了,可以通過 process.on('exit') 監(jiān)聽

我們知道 pm2 有守護進程的效果,在你的進程發(fā)生錯誤退出時,pm2 會重啟你的進程,我們也在 Node.js 的 cluster 模式下,實現(xiàn)一個守護子進程的效果(實際上 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}`);
  // 根據(jù) cpu 核數(shù),創(chuàng)建工作進程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  // 監(jiān)聽工作進程退出事件
  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作進程 ${worker.process.pid} 退出,錯誤碼: ${code || signal}, 重啟中...`);
    // 重啟子進程
    cluster.fork();
  });
}

// 工作進程代碼
if (cluster.isWorker) {
  // 監(jiān)聽未捕獲錯誤事件
  process.on('uncaughtException', error => {
    console.log(`工作進程 ${process.pid} 發(fā)生錯誤`, error);
    process.emit('disconnect');
    process.exit(1);
  });
  // 創(chuàng)建 web server
  // 各個工作進程都會監(jiān)聽端口 8000(Node.js 內(nèi)部會做處理,不會導(dǎo)致端口沖突)
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);
  console.log(`啟動工作進程: ${process.pid}`);
}

應(yīng)用實踐

上面分析了 Node.js 進程退出的各種情況,現(xiàn)在我們來做一個監(jiān)聽進程退出的工具,在 Node.js 進程退出時,允許使用方執(zhí)行自己的退出邏輯:

// exit-hook.js
// 保存需要執(zhí)行的退出任務(wù)
const tasks = [];
// 添加退出任務(wù)
const addExitTask = fn => tasks.push(fn);
const handleExit = (code, error) => {  
  // ...handleExit 的實現(xiàn)見下面
};
// 監(jiān)聽各種退出事件
process.on('exit', code => handleExit(code));
// 按照 POSIX 的規(guī)范,我們用 128 + 信號編號 得到最終的退出碼
// 信號編號參考下面的圖片,大家可以在 linux 系統(tǒng)下執(zhí)行 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 代表未捕獲的錯誤導(dǎo)致進程退出
process.on('uncaughtException', error => handleExit(1, error));
process.on('unhandledRejection', error => handleExit(1, error));

信號編號:

接下來我們要實現(xiàn)真正的進程退出函數(shù) handleExit,因為用戶傳入的任務(wù)函數(shù)可能是同步的,也可能是異步的;我們可以借助 process.nextTick 來保證用戶的同步代碼都已經(jīng)執(zhí)行完成,可以簡單理解 process.nextTick 會在每個事件循環(huán)階段的同步代碼執(zhí)行完成后執(zhí)行(理解 process.nextTick);針對異步任務(wù),我們需要用戶調(diào)用 callback 來告訴我們異步任務(wù)已經(jīng)執(zhí)行完成了:

// 標記是否正在退出,避免多次執(zhí)行
let isExiting = false;
const handleExit = (code, error) => {
  if (isExiting) return;
  isExiting = true;

  // 標記已經(jīng)執(zhí)行了退出動作,避免多次調(diào)用
  let hasDoExit = fasle;
  const doExit = () => {
      if (hasDoExit) return;
      hasDoExit = true
      process.nextTick(() => process.exit(code))
  }

  // 記錄有多少個異步任務(wù)
  let asyncTaskCount = 0;
  // 異步任務(wù)結(jié)束后,用戶需要調(diào)用的回調(diào)
  let ayncTaskCallback = () => {
      process.nextTick(() => {
        asyncTaskCount--
        if (asyncTaskCount === 0) doExit() 
      })
  }
  // 執(zhí)行所有的退出任務(wù)

  tasks.forEach(taskFn => {
      // 如果 taskFn 函數(shù)的參數(shù)個數(shù)大于 1,認為傳遞了 callback 參數(shù),是一個異步任務(wù)
      if (taskFn.length > 1) {
         asyncTaskCount++
         taskFn(error, ayncTaskCallback)
      } else {
          taskFn(error)
      }
  });

  // 如果存在異步任務(wù)
  if (asyncTaskCount > 0) {
      // 超過 10s 后,強制退出
      setTimeout(() => {
          doExit();
      }, 10 * 1000)
  } else {
      doExit()
  }
};

至此,我們的進程退出監(jiān)聽工具就完成了,完整的實現(xiàn)可以查看這個開源庫 async-exit-hook

進程優(yōu)雅退出

通常我們的 web server 在重啟、被運行容器調(diào)度(pm2 或者 docker 等)、出現(xiàn)異常導(dǎo)致進程退出時,我們希望執(zhí)行退出動作,如完成已經(jīng)連接到服務(wù)的請求響應(yīng)、清理數(shù)據(jù)庫連接、打印錯誤日志、觸發(fā)告警等,做完退出動作后,再退出進程,我們可以使用剛才的進程退出監(jiān)聽工具實現(xiàn):

const http = require('http');

// 創(chuàng)建 web server
const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);

// 使用我們在上面開發(fā)的工具添加進程退出任務(wù)
addExitTask((error, callback) => {
   // 打印錯誤日志、觸發(fā)告警、釋放數(shù)據(jù)庫連接等
   console.log('進程異常退出', error)
   // 停止接受新的請求
   server.close((error) => {
       if (error) {
         console.log('停止接受新請求錯誤', error)
       } else {
         console.log('已停止接受新的請求')
       }
   })
   // 比較簡單的做法是,等待一定的時間(這里我們等待 5s),讓存量請求執(zhí)行完畢
   // 如果要完全保證所有請求都處理完畢,需要記錄每一個連接,在所有連接都釋放后,才執(zhí)行退出動作
   // 可以參考開源庫 https://github.com/sebhildebrandt/http-graceful-shutdown
   setTimout(callback, 5 * 1000)
})

總結(jié)

通過上面的文字,相信你已經(jīng)對導(dǎo)致 Node.js 進程退出的各種情況心里有數(shù)了。在服務(wù)上線后,雖然 k8s、pm2 等工具能夠在進程異常退出時,不停地拉起進程,保證服務(wù)的可用性,但我們也應(yīng)該在代碼中主動感知進程的異常或者被調(diào)度的情況,從而能夠更早發(fā)現(xiàn)問題。

到此這篇關(guān)于Node.js進程退出的文章就介紹到這了,更多相關(guān)Node.js進程退出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Node.js插件的正確編寫方式

    Node.js插件的正確編寫方式

    正如Node.js在官方說明文檔中所言,插件是以動態(tài)方式進行鏈接的共享式對象,能夠?qū)avaScript代碼與C/C++庫接駁起來。這意味著我們可以引用任何來自C/C++庫中的內(nèi)容,并通過創(chuàng)建插件的方式將其納入到Node.js當中。
    2014-08-08
  • 詳解如何使用nvm管理Node.js多版本

    詳解如何使用nvm管理Node.js多版本

    這篇文章主要介紹了詳解如何使用nvm管理Node.js多版本,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • nodejs基于express實現(xiàn)文件上傳的方法

    nodejs基于express實現(xiàn)文件上傳的方法

    這篇文章主要介紹了nodejs基于express實現(xiàn)文件上傳的方法,結(jié)合實例形式分析了nodejs基于express框架實現(xiàn)文件上傳功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2018-03-03
  • 使用pm2自動化部署node項目的方法步驟

    使用pm2自動化部署node項目的方法步驟

    這篇文章主要介紹了使用pm2自動化部署node項目的方法步驟,pm2是一個進程管理工具,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • node.js中watch機制詳解

    node.js中watch機制詳解

    本文給大家?guī)淼氖且黄P(guān)于nodejs中watch機制的探討,主要探討內(nèi)容是為什么watch不是銀彈,嘗試使用更好的方案來解決這個問題
    2014-11-11
  • 在Node.js中實現(xiàn)文件復(fù)制的方法和實例

    在Node.js中實現(xiàn)文件復(fù)制的方法和實例

    這篇文章主要介紹了在Node.js中實現(xiàn)文件復(fù)制的方法和實例,使用FS模塊實現(xiàn),需要的朋友可以參考下
    2014-06-06
  • 讓你的Node.js應(yīng)用程序處理數(shù)百萬的API請求技巧

    讓你的Node.js應(yīng)用程序處理數(shù)百萬的API請求技巧

    歡迎閱讀關(guān)于優(yōu)化 NodeJS 應(yīng)用以處理數(shù)百萬 API 請求的終極指南,如果你是一名開發(fā)人員,希望擴展應(yīng)用,那么你來對地方了,在這篇博客中,我們將深入研究最佳實踐和技術(shù),幫助你處理高流量負載,確保應(yīng)用保持性能和響應(yīng)速度
    2023-10-10
  • 詳解Nodejs中自動化瀏覽器操作神器Puppeteer的使用

    詳解Nodejs中自動化瀏覽器操作神器Puppeteer的使用

    Puppeteer是一個JavaScript庫,它提供了一種方式來通過DevTools協(xié)議控制無頭瀏覽器,本文主要為大家介紹了Puppeteer的主要特性和使用方法,感興趣的可以了解下
    2024-01-01
  • Express 配置HTML頁面訪問的實現(xiàn)

    Express 配置HTML頁面訪問的實現(xiàn)

    這篇文章主要介紹了Express 配置HTML頁面訪問的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2020-11-11
  • Node.js檢測端口(port)是否被占用的簡單示例

    Node.js檢測端口(port)是否被占用的簡單示例

    大家有沒有遇到過在開啟本地服務(wù)時,有這么一種情況:當前端口已經(jīng)被另一個項目使用了,導(dǎo)致服務(wù)開啟失敗。那么接下來,我們通過簡簡單單的示例代碼來檢測端口是否已經(jīng)被占用。有需要的朋友們可以參考借鑒。
    2016-09-09

最新評論