聊聊各種可能導致 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}`);}

推薦閱讀