跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Golang 编写一个支持平滑关机的 Web 服务器

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、中间件取消、连接生命周期全串起来——漏掉任意一环,都可能让某个请求静默失败。

相关文章