别让你的 Node.js 应用丑陋收场:完美优雅关闭指南
Source: Dev.to
我们花费数小时来完善 CI/CD 流水线、优化数据库查询以及编写整洁的代码。但我们常常忽视了应用生命周期中一个至关重要的环节:死亡。
当你将新版本的应用部署到 Kubernetes、Docker Swarm,甚至是 PM2 时,编排器会向正在运行的进程发送 SIGTERM 信号。
如果你没有处理这个信号,Node.js 可能会立即终止。活跃的 HTTP 请求会被中断,数据库连接会保持打开(出现幽灵连接),事务可能会陷入悬而未决的状态。
今天,我想向你展示如何使用 ds‑express‑errors 专业地处理这个问题,特别是深入其 Graceful Shutdown API。
“零依赖” 方法
我构建 ds‑express‑errors 主要是为了错误映射,但我加入了一个强大的关闭管理器,因为我厌倦了仅仅为了捕获 SIGINT 而引入额外的依赖。
该库没有任何依赖,这意味着它轻量且安全。
完整配置(不仅仅是基础)
大多数教程只演示如何关闭服务器。真实的生产应用需要更多:它们必须区分 “计划关机”(部署) 与 “崩溃”(未捕获异常),并且需要对退出行为进行细粒度控制。
以下是 initGlobalHandlers 的完整 API 参考。
设置
安装包
npm install ds-express-errors
高级用户配置(例如 index.js)
const express = require('express');
const mongoose = require('mongoose');
const {
initGlobalHandlers,
gracefulHttpClose,
} = require('ds-express-errors');
const app = express();
const server = app.listen(3000);
// The Complete Configuration
initGlobalHandlers({
// 1️⃣ HTTP Draining Mechanism
// Wraps server.close() in a promise that waits for active requests to finish.
closeServer: gracefulHttpClose(server),
// 2️⃣ Normal Shutdown Logic (SIGINT, SIGTERM)
// Runs when you redeploy or stop the server manually.
onShutdown: async () => {
console.log('SIGTERM received. Closing external connections...');
// Close DB, Redis, Socket.io, etc.
await mongoose.disconnect();
console.log('Cleanup finished. Exiting.');
},
// 3️⃣ Crash Handling (Uncaught Exceptions / Unhandled Rejections)
// Runs when your code throws an error you didn’t catch.
onCrash: async (error) => {
console.error('CRITICAL ERROR:', error);
// Critical: Send alert to Sentry/Slack/PagerDuty immediately
// await sendAlert(error);
},
// 4️⃣ Exit Strategy for Unhandled Rejections
// Default: true.
// If false, the process continues running even after an unhandled promise rejection.
// (Recommended: true, because an unhandled rejection can leave the app in an unstable state)
exitOnUnhandledRejection: true,
// 5️⃣ Exit Strategy for Uncaught Exceptions
// Default: true.
// If false, the app tries to stay alive after a sync error.
// (High risk of memory leaks or corrupted state if set to false).
exitOnUncaughtException: true,
});
选项细分
| 选项 | 类型 | 描述 |
|---|---|---|
closeServer | Function (Async) | 处理 HTTP 层。辅助函数 gracefulHttpClose(server) 监听中止信号并抽象原生 server.close() 的回调地狱。它在停止新连接的同时允许已有请求完成。 |
onShutdown | Function (Async) | 仅由系统信号(SIGINT、SIGTERM、SIGQUIT)触发的业务逻辑清理。这是“优雅关闭”的正常路径。典型任务:• 关闭数据库连接 • 刷新日志 • 取消长时间运行的任务 |
onCrash | Function (Async) | 由 uncaughtException 或 unhandledRejection 触发。它让你可以将崩溃与优雅关闭区别对待(例如,立即发送警报并退出)。 |
exitOnUnhandledRejection | Boolean(默认 true) | 若为 true,在未处理的拒绝后进程以代码 1 退出(快速失败)。仅在有非常特定的弹性策略时才设为 false。 |
exitOnUncaughtException | Boolean(默认 true) | 与上面相同的思路,只是针对同步的未捕获异常。 |
为什么要将 onShutdown 与 onCrash 分开?
onShutdown– 你很镇定,有时间优雅地关闭资源。onCrash– 房子着火了,你可能想立即发送紧急警报并立刻终止。
安全网:超时
如果你的 onShutdown 逻辑挂起怎么办?如果 mongoose.disconnect() 永远不返回怎么办?你不希望你的 pod 永久停留在 “Terminating” 状态(Kubernetes 最终会 SIGKILL 它)。
ds-express-errors 为你的关闭逻辑包装了一个 10 秒超时。如果清理超过此限制,库会强制终止,确保容器不会阻塞部署流水线。
TL;DR
- 使用 ds‑express‑errors 中的
initGlobalHandlers来获得一个 零依赖、可用于生产环境的优雅关闭方案。 - 将优雅关闭 (
onShutdown) 与崩溃处理 (onCrash) 分离。 - 利用内置的 HTTP 排空助手
gracefulHttpClose(server)。 - 除非有充分理由在致命错误后保持进程存活,否则保持默认的 “快速失败” 行为(
exitOn…=true)。
现在你的 Node.js 应用可以 优雅地退出,让用户满意,基础设施保持整洁。
未在规定时间内完成的函数将被强制退出,防止僵尸进程。
结论
处理进程终止是高级工程师的必备技能。它将 “业余项目” 与可靠的分布式系统区分开来。
使用 ds-express-errors,你无需编写复杂的样板代码。它提供了一个完整类型化、零依赖的解决方案,能够开箱即用地处理优雅关闭和关键崩溃。
链接
祝编码愉快(也祝关闭顺利)! 🔌
