先记住这个答案
收到 SIGTERM 后先进入幂等的 draining 状态,协调流量入口撤下就绪状态,并停止接受新连接和新任务,再在明确截止时间内等待已有请求与关键作业结束,随后按依赖顺序关闭连接池等资源。Node 的信号监听器会改变默认退出行为,因此必须自己保证流程能结束。到期仍未完成的连接需要明确强制处理,WebSocket 等升级连接也要单独管理。部署平台可能最终强杀,网络和客户端也会失败,所以应结合幂等、重试和可恢复任务降低损失,而非保证绝不丢请求。
- 接管信号后必须负责退出完成条件
- 排空、停止后台任务和关闭依赖有先后关系
- 截止时间必须短于外部平台的强杀期限
先定义哪些工作算完成
HTTP 响应结束、数据库提交和后台消息确认可能是三个不同时间点。停机计数应对应业务承诺,不能只看到 socket 数归零就假定所有异步写入和作业都已完成。
例如下单接口若在发送响应后仍异步写关键状态,排空 HTTP 并不能保护这段工作。应调整事务与响应边界,或把可靠后续处理交给有持久化确认的任务系统,再明确停机时停止领取与归还任务的规则。
用单次状态转换处理信号和并发
多个 SIGTERM 或 SIGINT 可能连续到达,关闭函数应只启动一次,并让后续触发复用同一个完成状态。否则重复关闭连接池、重复释放锁或多次改退出码,会使偶发停机故障难以复现。
开始 draining 后调用 server.close 停止接收新连接,同时与反向代理的摘流时机配合。仍在处理的请求需要保留可用依赖,不能一收到信号就先关闭数据库,再要求这些请求正常完成。
为永不完成的资源设置明确上限
长轮询、SSE、WebSocket、失联下游或卡住的后台任务都可能让等待没有尽头。给各类资源定义停止通知与截止行为,统一总期限,并记录哪些工作在到期后被中断。
正常排空后应取消兜底定时器,释放剩余句柄并让进程结束;到期则执行约定的强制终止策略。应用内定时器也无法抢占同步死循环,因此外部进程管理器仍需拥有最终强杀与重启能力。
容易答错的地方
- 收到信号立刻 process.exit(0)
- 这会直接中断仍在执行的请求和异步清理,退出码还可能掩盖未完成工作。应先进入停机流程,正常完成后结束;若必须强制退出,记录触发条件并采用能反映结果的状态。
- 把不丢请求当作单机函数能实现的保证
- 代理摘流延迟、客户端断开和平台强杀都超出一个函数的控制范围。需要通过可重试接口、幂等键、持久任务与真实发布演练降低影响,并监测停机期间失败和重复执行。
面试官还会怎么问?
SIGKILL 能做同样的清理吗?
不能依赖进程捕获或处理 SIGKILL,所以持久状态不能等到最后信号才保存。优雅流程只利用可协商的时间窗口,关键数据仍需在正常执行路径里建立可靠提交和恢复机制。
为什么数据库池要晚一点关?
已有请求可能还需要数据库完成事务或读取结果。过早关闭池会让本可完成的请求失败,应先停止新工作并等待依赖它的工作结束,再关闭池,同时让整个等待受总期限控制。
测试停机时只看退出码够吗?
不够,还要持续发送受控请求,覆盖长请求、后台任务和重复信号,记录完成、失败与重试结果。退出码说明进程最终状态,不能证明客户端收到了响应或某笔业务只执行了一次。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。