<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在我們的服務釋出後,難免會被執行環境(如容器、pm2 等)排程、升級服務導致重啟、各種異常導致程序崩潰;一般情況下,執行環境都有對服務程序的健康監測,在程序異常時,會重新拉起程序,在升級時,也有捲動升級的策略。但執行環境的排程策略是把我們服務的程序當成黑盒來處理的,不會管服務程序內部的執行情況,因此需要我們的服務程序主動感知執行環境的排程動作,然後做一些退出的清理動作。
因此我們今天就是梳理各種可能導致 Node.js 程序退出的情況,以及我們可以通過監聽這些程序退出事件做哪些事情。
一個程序要退出,無非就是兩種情況,一是程序自己主動退出,另外就是收到系統訊號,要求程序退出。
在 Node.js 官方檔案 中列出了常見的系統訊號,我們主要關注幾個:
在收到非強制退出訊號時,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 程序主動退出,主要包含下面幾種情況:
我們知道 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 worldn'); }).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));
訊號編號:
接下來我們要實現真正的程序退出函數 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-hook
通常我們的 web server 在重啟、被執行容器排程(pm2 或者 docker 等)、出現異常導致程序退出時,我們希望執行退出動作,如完成已經連線到服務的請求響應、清理資料庫連線、列印錯誤紀錄檔、觸發告警等,做完退出動作後,再退出程序,我們可以使用剛才的程序退出監聽工具實現:
const http = require('http'); // 建立 web server const server = http.createServer((req, res) => { res.writeHead(200); res.end('hello worldn'); }).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.js程序退出的文章就介紹到這了,更多相關Node.js程序退出內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45