背景

发布时立即退出进程,会中断正在处理的请求;无限等待又会超出编排系统给的终止窗口。退出应该有顺序和总预算。

核心判断

  1. 先标记不可就绪,并按端点传播延迟安排排空。
  2. 收到信号后使用新的带超时 context 调用 Shutdown。
  3. HTTP 排空后再等待后台任务,超时策略和强制关闭必须显式记录。

最小示例

下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。

signalCtx, stop := signal.NotifyContext(
    context.Background(), os.Interrupt, syscall.SIGTERM,
)
defer stop()
<-signalCtx.Done()

// ready 是探针读取的 atomic.Bool;摘流量传播等待按部署环境安排。
ready.Store(false)
shutdownCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
    slog.Error("HTTP drain failed", "error", err)
    _ = server.Close()
}
// 在总退出预算内,另行停止并等待后台任务。

边界与误区

信号 context 收到 SIGTERM 后已经取消,不能直接拿它执行排空。Shutdown 不负责回收 hijacked/WebSocket 连接;主协程也必须等待关闭流程结束。

验证方式

同时发送短请求、长请求和 WebSocket 再终止服务,检查完成率与退出时长。ListenAndServe 返回 ErrServerClosed 属于正常关闭,不应记成启动失败。

小结

readiness 先摘流量,Shutdown 再等待连接和后台任务,并设置明确上限。