Graceful Shutdown 的实践:服务重启不丢请求
Graceful Shutdown 的实践服务重启不丢请求一、服务重启的「不体面」时刻独立开发者的部署流程通常是:推送代码,SSH 到服务器,git pull,pm2 restart或docker restart。这个过程看起来流畅,但每一次重启都潜藏着两个问题:正在处理中的请求丢失了,用户看到的是连接错误或 502 页面;正在执行中的异步任务(如文件上传、数据导出)被中断了,并且不知道中断在什么地方。这些问题在用户量少时无关紧要——偶尔几个用户遇到重试几次就好了。但当你的产品有几千个活跃用户、每天处理上万次 AI 调用时,每一次「不体面的重启」都可能造成数十个请求失败、几个异步任务中断、和若干用户的不满。Graceful Shutdown(优雅关闭)就是解决这个问题——在关闭服务之前,先停止接收新请求,等待正在处理的请求完成,再退出进程。二、HTTP 服务器的 Graceful ShutdownHTTP 服务器的优雅关闭,需要分三步:步骤一:停止接收新请求。当进程接收到 SIGTERM 或 SIGINT 信号时,关闭 HTTP 服务器的监听器(server.close()),不再接受新的 TCP 连接。但已经建立的连接,继续处理。步骤二:给现有请求完成时间。设置一个超时时间(如 30 秒),等待所有活跃的请求处理完成。如果超时后仍有请求未完成,强制关闭连接。步骤三:关闭依赖资源。请求全部处理完后,按顺序关闭依赖:数据库连接池 → 缓存连接 → 消息队列连接 → 进程退出。Node.js 的优雅关闭实现:process.on(SIGTERM, async () { console.log(Graceful shutdown initiated...); // 停止接收新请求 server.close(() { console.log(No longer accepting new requests); }); // 等待现有请求完成 setTimeout(async () { await db.disconnect(); await redis.disconnect(); process.exit(0); }, 30000); // 最多等 30 秒 });三、任务队列 Worker 的优雅关闭任务队列 Worker 的优雅关闭,比 HTTP 服务器复杂一些——因为任务的处理时间不可预测。一个报表生成任务可能需要 30 秒,一个视频处理任务可能需要几分钟。Worker 的优雅关闭策略:收到关闭信号后,停止从队列中获取新任务。等待所有正在执行的任务完成(包括重试中的任务)。如果一个任务执行时间超过预设的「最大等待时间」(如 60 秒),标记该任务为「未完成」,让它可以在下一个 Worker 实例上恢复执行。所有任务完成后,关闭与队列的连接,退出进程。关键细节:任务必须是幂等的。因为一个任务可能在 Worker A 上执行了一半被中断,然后在 Worker B 上重新执行。如果任务不是幂等的(如「发送邮件」,发了一半被中断,发送操作已经执行),重新执行会导致邮件重复发送。四、健康检查与流量切换优雅关闭不只是应用进程内的事。外部组件(负载均衡器、编排系统如 K8s)也需要配合。Kubernetes 的配合:在 K8s 中,当 Pod 被终止时,会先收到 SIGTERM 信号,然后有terminationGracePeriodSeconds(默认 30 秒)的时间来优雅关闭。应用需要在preStop钩子或 SIGTERM 处理中完成优雅关闭逻辑。同时,需要配合readinessProbe——应用在收到关闭信号后,readiness 探针应立刻返回「不健康」,让 Service 停止向这个 Pod 发送流量。Nginx 的配合:如果你用 Nginx 做反向代理,可以通过健康检查让 Nginx 感知到应用实例的关闭。在应用关闭期间,配置一个专门的健康检查端点(如/health),关闭期间返回 503,让 Nginx 停止向该实例分配请求。五、总结Graceful Shutdown,核心不是「让关闭变快」,而是让关闭过程「不伤害用户体验」。进程完全可以多活 30 秒,只要这 30 秒内,现有的请求被妥善处理、异步任务被标记为可恢复、外部流量被平滑切走。对于独立开发者,五个实践要点:(1)在应用中监听 SIGTERM 信号,实现优雅关闭逻辑;(2)关闭期间停止接收新请求,等待现有请求完成;(3)设置最大等待时间(如 30 秒),超过则强制关闭;(4)确保异步任务是幂等的,可以在新实例上重新执行;(5)配合负载均衡器的健康检查,在关闭期间停止接收新流量。优雅关闭是「在问题发生之前就解决问题」的实践——它不增加任何新功能,只是让已有功能在重启时不中断。用户永远不会知道你在做部署,而这正是优秀产品的体验:部署发生在用户身后,不需要他们感知。