必须用 http.Server.Shutdown() 配合带超时的 context.Context 才算优雅关机;server.Close() 会立即关闭监听器并强制中断所有连接,而 Shutdown() 先拒新连、再等待存量请求自然完成或超时。

必须用 http.Server.Shutdown() 配合带超时的 context.Context,否则不是优雅关机,是强行杀进程。
为什么 server.Close() 不行
它会立刻关闭监听器,并强制中断所有活跃连接——大文件上传中断、长轮询断开、流式响应被截断,客户端收到 connection reset。这不是“等请求做完”,是“不管你在干啥,现在就停”。Shutdown() 才是标准解法:先拒新连,再等存量请求自然返回(或超时)。
-
server.Close()是底层 socket 级关闭,无等待逻辑 -
Shutdown()内部调用srv.closeListenerConn()拒绝新连接,再遍历并等待 active conn - 若 handler 里有没传
ctx的阻塞调用(如http.DefaultClient.Do(req)),Shutdown()会卡住直到超时
如何正确监听 SIGTERM 并触发 Shutdown()
线上环境发的是 SIGTERM,不是 SIGINT;且信号通道必须带缓冲,否则快速连发两次可能丢一次。
- 用
make(chan os.Signal, 1)创建缓冲通道,避免信号丢失 -
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)显式列出信号,别用os.Interrupt - 收到信号后,立即启动
Shutdown(),不要在 signal handler 里做耗时操作 - 主 goroutine 必须阻塞等待
Shutdown()完成,否则进程提前退出
示例关键片段:
立即学习“go语言免费学习笔记(深入)”;
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan // 阻塞在此,等信号
<p>ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("shutdown error: %v", err) // 注意:err 可能是 context.DeadlineExceeded
}为什么 Shutdown() 总是超时失败
不是函数有问题,是你没管住 handler 里的“不守规矩”操作。它只等连接上的 handler 返回,不杀 goroutine,也不设 I/O 超时。
- 没设
srv.ReadTimeout/WriteTimeout,挂起连接永远不结束 - handler 中用了
time.Sleep(5 * time.Second)而非select { case - 第三方调用没传
req.Context():比如db.QueryRow("...")应为db.QueryRowContext(req.Context(), "...") - 起了无 context 约束的 goroutine:
go sendToKafka(data)→ 改为go func() { select { case
多个组件共存时怎么统一关机顺序
HTTP server、gRPC server、redis.Client、定时器、后台 worker —— 它们不会自动跟着 Shutdown() 关。必须手动按依赖顺序清理,且只能执行一次。
- 全局只注册一次
signal.Notify,用sync.Once包裹 shutdown 主逻辑 - 关机顺序建议:停新入口(HTTP/gRPC listener)→ 停任务分发(暂停消费者、关定时器)→ 等 handler 自然退出 → 关资源(
redis.Close()、db.Close()) - 切忌先
db.Close()再等 HTTP handler,否则 handler 里查库 panic - K8s 场景下,
terminationGracePeriodSeconds必须 ≥ 代码中context.WithTimeout设的值,否则 K8s 强杀前 cleanup 没跑完
最易被忽略的一点:反向代理(Nginx/Traefik)和 readiness probe。服务已进 Shutdown(),但 probe 还返回 200,代理继续转发流量,导致新请求被重置。得在收到信号后立刻让健康检查端口失效或返回 503。


















