Node.js 生产运行:优雅退出、信号与 twelve-factor 心智

本地 node app.js 一停就完事;线上是 多副本、滚动发布、健康检查、配置注入。Node 进程 怎么起、怎么停、怎么证明自己还活着,和代码逻辑一样影响可用性。
这一篇想讲清楚几件事:SIGTERM / SIGINT 在生产里意味着什么、什么叫优雅关闭、以及 twelve-factor 里和 Node 最相关的几条怎么落地

在容器与 systemd 环境里,停止服务 常见路径是:

  • 先发 SIGTERM,给一段 宽限期
  • 超时再 SIGKILL(不可捕获,直接硬杀)。

Node 里可以 process.on('SIGTERM', handler)(以及 SIGINT 用于本地 Ctrl+C)。

优雅关闭 通常包括:

  • 停止接新请求(HTTP server close);
  • 等已有请求收尾(或限时);
  • 关掉数据库连接池、定时器、子进程
  • 最后 process.exit(或由运行时自然退出)。

若忽略 SIGTERM、或关闭逻辑 超过编排系统的 kill 时间,会被硬杀 → 事务半截、连接泄漏、用户看到 502

server.close(callback)

  • 不再接受新连接
  • 已有连接 仍由你决定是否等它们结束。

server.closeAllConnections()(较新 API,视版本)之类手段的差别是:

  • 前者偏 温和
  • 紧急腾退时可能需要 更硬的断连策略(要接受客户端重试)。

长连接(WebSocket、gRPC)要 单独协议层 通知客户端重连。

编排里常见两个探针:

  • Liveness:进程 死没死——卡死也要被拉起来;
  • Readiness能不能接流量——依赖没好就先别进负载均衡。

Node 服务可以:

  • 轻量 HTTP 路径(如 /healthz)只做 本进程心跳
  • readiness查一下 DB/Redis(注意 超时与失败策略,别反把事件循环拖死)。

别把 重业务逻辑 塞进健康检查。

配置(Config):从 环境变量 注入,不把密钥写进镜像;与 安全 那篇一致。

日志(Logs)stdout/stderr 流,由平台收集;应用里 不要绑死文件路径(除非明确要落盘且可轮转)。

端口绑定(Port binding):服务 监听 process.env.PORT,由外面注入。

进程(Processes)无状态可水平扩展;会话进 Redis 等外置存储。

管理进程(Admin processes):migration、一次性任务 用同一套 release、同一环境,不要手敲生产机器上的神秘命令。

非零退出码 告诉编排 重启副本

策略上:

  • 不可恢复错误(配置缺失、依赖版本不对)应 快速失败
  • 瞬时错误(网络闪断)可用 重试 + 熔断,但要 上限,避免死循环刷日志。

uncaughtException 里继续跑 要谨慎——状态可能已坏,记录后退出 往往更安全(由平台拉起新进程)。

  • 信号 是编排和你的 合约,要有 超时内有界的关闭路径
  • server.close + 资源清理 是优雅退出的骨架;
  • 健康检查轻、快、语义分清
  • twelve-factor配置、日志、端口、无状态 从代码里 剥离 成环境能力。

把进程当成 可随时被换掉的 cattle,而不是 碰不得的 pet,和 调试观测子进程 两篇一起看,部署图就完整一截。