http.Server.Shutdown是唯一靠谱的选择,因它先拒新连、再等旧连完成或超时;而os.Exit或杀进程会硬中断请求,导致connection reset;server.Close()则立即关闭listener且不等待处理中请求,等同暴力关机。

为什么 http.Server.Shutdown 是唯一靠谱的选择
直接调用 os.Exit 或杀进程会导致正在处理的 HTTP 请求被硬中断,客户端收到 connection reset 或空响应。Go 标准库的 http.Server.Shutdown 是专为此设计的:它先关闭监听器,再等待所有活跃连接完成或超时。关键点是——它不阻塞主 goroutine,但需要你主动触发,并配合信号监听。
常见错误是只调用 server.Close():这会立刻关闭 listener,但不管还在跑的请求,等同于暴力关机。
-
Shutdown必须传入一个context.Context,超时控制靠它,不是靠自己 sleep - 不能在
http.ListenAndServe返回后才调用Shutdown,因为那时 server 已退出,Shutdown会立即返回http.ErrServerClosed - 必须确保
Shutdown只调用一次,重复调用会 panic
如何用 os.Signal 捕获 SIGTERM 和 SIGINT
Linux 容器(如 Kubernetes)发 SIGTERM,本地 Ctrl+C 是 SIGINT。两者都该响应。别漏掉 SIGINT,否则本地调试时没法优雅退出。
注意信号 channel 的缓冲大小必须 ≥1,否则并发发两个信号可能丢失一个:
立即学习“go语言免费学习笔记(深入)”;
sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
收到信号后,应立即停止接收新连接,所以要立刻调用 Shutdown,而不是等当前 handler 返回。
- 不要在 signal handler 里做耗时操作(如写日志文件),避免阻塞信号接收
- 若需记录关机日志,用 goroutine 异步写,或用带超时的
log.SetOutput配合io.MultiWriter - Kubernetes 场景下,
terminationGracePeriodSeconds决定了你有多少时间完成Shutdown,超时后系统会发SIGKILL强杀
Shutdown 超时设多少才合理
超时时间取决于你的业务:长轮询、文件上传、数据库事务都会拖慢连接关闭。设太短会强行中断请求;设太长又拖慢部署节奏。生产环境建议 10–30 秒,本地开发可设 5 秒快速验证。
用 context.WithTimeout 构造 shutdown context,别用 time.AfterFunc 手动 cancel:
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("shutdown error: %v", err)
}- 超时错误
context.DeadlineExceeded是正常现象,表示有请求没及时结束,不用 panic - 若业务中用了
http.TimeoutHandler,它的超时和Shutdown超时是两层,后者必须 ≥ 前者,否则可能卡在中间件里 - 某些中间件(如自定义 auth handler)若阻塞在外部 RPC,需确保它们支持 context 取消,否则
Shutdown会等满超时
别忽略 http.Server 的 ReadTimeout 和 WriteTimeout
这两个字段不是为优雅关机设计的,但会影响 Shutdown 行为。如果没设 ReadTimeout,一个恶意客户端保持连接不发请求,Shutdown 会一直等它读超时(默认无限),导致关机卡住。
建议显式设置(即使只是 30 秒),并确保小于 Shutdown 超时:
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
}-
IdleTimeout更重要:它控制 keep-alive 连接空闲多久后自动关闭,能减少关机时待处理的“僵尸连接” - 若用了
http.Transport作 client,它的IdleConnTimeout和 server 的IdleTimeout要协调,否则可能出现连接复用失败 - Go 1.19+ 支持
http.Server.SetKeepAlivesEnabled(false)彻底禁用 keep-alive,适合极简场景,但会增加 TCP 握手开销
关机逻辑本身不复杂,难的是把 timeout、signal、中间件取消、连接生命周期全串起来——漏掉任意一环,都可能让某个请求静默失败。


















