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