必须用 http.server.shutdown() 配合带超时的 context.context 实现优雅关闭,禁用 server.close();因后者立即中断连接、不等待响应完成,易致客户端错误、上传失败、流式接口断连及 panic,仅适用于测试。

Go 语言 Web 项目中自动优雅关闭 HTTP 服务,核心就是用 http.Server.Shutdown() 配合信号监听和带超时的 context.Context;server.Close() 会立刻中断所有连接,生产环境必须禁用。
为什么不能用 server.Close()?
server.Close() 立即关闭监听器,并强制终止所有已建立但尚未完成的连接。它不等 handler 返回、不等 response 写完、也不通知客户端断连。常见后果包括:
- 客户端收到
connection reset by peer或空响应 - 大文件上传卡在 87% 后失败
- 长轮询或流式接口突然断开,前端反复重试
-
ListenAndServe()返回http.ErrServerClosed以外的错误,日志里频繁 panic
这不是“快”,是“糙”。Go 官方文档明确标注:仅适用于测试或极端清理场景。
必须用 Shutdown() 并传入带超时的 context
http.Server.Shutdown() 是 Go 1.8+ 唯一标准的优雅关闭入口。但它本身不阻塞——你得自己控制等待上限,否则可能永远 hang 住(比如某个客户端死活不发 FIN)。
- 超时时间建议设为略小于 K8s 的
terminationGracePeriodSeconds(例如后者 30s,这里设 25s) - 别把
time.Second * 30直接传给Shutdown()—— 它只接受context.Context,不是time.Duration - 别在调用
Shutdown()前调用cancel(),否则 context 立即失效,等于没等 - 超时后返回
context.DeadlineExceeded,应记录告警但继续执行后续清理(如 DB 关闭)
信号监听要同时处理 SIGINT 和 SIGTERM
本地 Ctrl+C 发的是 os.Interrupt(本质是 SIGINT),而 Kubernetes 或 systemd 发的是 SIGTERM。只监听其中一个,会导致部分场景无法触发关闭。
- 正确写法:
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - 不要用
os.Interrupt单独监听——它在 Windows 上行为不一致,不可移植 - 监听通道只需容量为 1:
sigChan := make(chan os.Signal, 1),避免信号丢失 - 收到信号后应立即启动停机流程,不要在 signal handler 里做阻塞操作
handler 和中间件必须响应上下文取消
Shutdown() 不会主动 cancel req.Context(),它只等连接自然结束。如果 handler 里有未受控的数据库查询、HTTP 调用或 time.Sleep(),它们可能永远不退出,拖垮整个 shutdown 流程。
- 所有耗时操作必须使用
req.Context(),例如db.QueryContext(req.Context(), ...) - 避免启动无 cancel 控制的 goroutine(如
time.AfterFunc(10*time.Second, ...)) - 设置
server.ReadTimeout和server.WriteTimeout,防止挂起连接无限等待 - 忽略
http.ErrServerClosed:这是ListenAndServe()正常退出标志,不是错误
最容易被忽略的一点:Shutdown 不会自动关闭数据库连接池、gRPC server 或后台 worker。它们必须用同一个 context 协同退出,否则服务看似关了,资源还在泄漏。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











