别让你的 Node.js 应用丑陋收场:完美优雅关闭指南

发布: (2025年12月26日 GMT+8 22:43)
6 min read
原文: Dev.to

Source: Dev.to

Nse569h

我们花费数小时来完善 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,
});

选项细分

选项类型描述
closeServerFunction (Async)处理 HTTP 层。辅助函数 gracefulHttpClose(server) 监听中止信号并抽象原生 server.close() 的回调地狱。它在停止新连接的同时允许已有请求完成。
onShutdownFunction (Async)仅由系统信号(SIGINTSIGTERMSIGQUIT)触发的业务逻辑清理。这是“优雅关闭”的正常路径。典型任务:
• 关闭数据库连接
• 刷新日志
• 取消长时间运行的任务
onCrashFunction (Async)uncaughtExceptionunhandledRejection 触发。它让你可以将崩溃与优雅关闭区别对待(例如,立即发送警报并退出)。
exitOnUnhandledRejectionBoolean(默认 true若为 true,在未处理的拒绝后进程以代码 1 退出(快速失败)。仅在有非常特定的弹性策略时才设为 false
exitOnUncaughtExceptionBoolean(默认 true与上面相同的思路,只是针对同步的未捕获异常。

为什么要将 onShutdownonCrash 分开?

  • 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,你无需编写复杂的样板代码。它提供了一个完整类型化、零依赖的解决方案,能够开箱即用地处理优雅关闭和关键崩溃。

链接

祝编码愉快(也祝关闭顺利)! 🔌

Back to Blog

相关文章

阅读更多 »