Node.js 行程控制:掌握系統訊號與優雅停機實戰
當我們在生產環境中重新部署應用程式,或是容器 (Container) 需要重啟時,系統(如 Kubernetes 或 Docker)會向 Node.js 行程發送訊號。如果我們不予理會,Node.js 可能會被瞬間強制殺死。
這會導致:
- 資料損毀:正在寫入資料庫或磁碟的操作被腰斬。
- 請求中斷:正在處理到一半的使用者請求直接連線斷開。
這篇文章將帶你了解如何處理系統訊號,實作優雅停機 (Graceful Shutdown)。
認識常見的系統訊號
Node.js 的 process 物件可以監聽 OS 發出的訊號:
SIGINT:當你在終端機按下Ctrl + C時發出。SIGTERM:系統管理工具(如 Docker stop)要求程式停止的正式訊號,這是最常用的停機觸發點。
實務開發:實作優雅停機流程
一個完整且優雅的停機流程應包含以下步驟:
- 接收到停機訊號。
- 停止接收新的請求(讓 Load Balancer 知道你已準備離線)。
- 等待所有進行中的請求處理完畢。
- 關閉資料庫與快取連線。
- 正式退出行程。
示範程式碼
const http = require('http');
const server = http.createServer((req, res) => {
// 模擬耗時的業務邏輯
setTimeout(() => {
res.end('處理完成');
}, 5000);
});
server.listen(3000, () => {
console.log('伺服器運行中,PID:', process.pid);
});
// 監聽 SIGTERM 訊號
process.on('SIGTERM', () => {
console.info('收到 SIGTERM 訊號,準備開始優雅停機...');
// 1. 停止接收新連線
server.close(() => {
console.log('HTTP 伺服器已關閉');
// 2. 這裡可以放置關閉資料庫連線的邏輯
// await db.close();
console.log('所有資源已清理完畢,安全離線。');
process.exit(0); // 0 代表正常退出
});
// 備用方案:如果 30 秒後還沒關掉,強制退出,防止程式卡死
setTimeout(() => {
console.error('停機超時,強制關閉');
process.exit(1);
}, 30000);
});
處理未預期的崩潰:Exception 與 Rejection
除了主動停機,我們也應監控程式碼中的致命錯誤,確保它們被妥善記錄後才離開。
// 處理未捕獲的 Promise 錯誤
process.on('unhandledRejection', (reason, promise) => {
console.error('偵測到未處理的 Rejection:', reason);
// 建議:發送警告到日誌系統並重啟
});
// 處理未捕獲的異常同步錯誤 (Last resort)
process.on('uncaughtException', (err) => {
console.error('發生嚴重錯誤 (Uncaught Exception):', err);
// 注意:發生此錯誤時,行程的狀態可能已經混亂,必須強制重啟
process.exit(1);
});
為什麼要加上強制退出超時?
在生產環境中,有時資料庫連線可能會因為某些網路原因卡住而無法成功關閉。如果我們只寫了 server.close() 但它永遠不回呼,行程就會變成一個「殭屍行程」,佔用記憶體。因此,一定要設定一個強制退出的 Timer。
總結
SIGTERM是後端工程師最需要重視的訊號。- 優雅停機 能保證資料一致性並提升用戶體驗。
- 透過
process.on監聽各種類型的事件,讓你的應用程式在運維層面更加穩健。