Go微服务必须用http.Server.Shutdown()配合信号监听,否则SIGTERM会导致请求中断、DB事务不完整、连接泄漏;需显式构造Server、goroutine启动ListenAndServe、signal.Notify监听SIGINT/SIGTERM、带缓冲channel、先srv.Close()再srv.Shutdown(ctx)、反向关闭依赖组件、所有I/O用Context版本并设超时。

Go 微服务必须用 http.Server.Shutdown() 配合信号监听,否则 SIGTERM 一来就 kill,请求中断、DB 事务不完整、连接泄漏全是常态。
为什么直接调用 http.ListenAndServe() 就会挂掉
它在主 goroutine 里阻塞,信号监听逻辑根本跑不起来;哪怕你写了 signal.Notify,也收不到信号。K8s 发 SIGTERM 后几秒内进程就被强杀,正在写的数据库事务只写了一半——A 加了 B 为好友,B 的列表里却没 A。
- 必须显式构造
http.Server实例,不能依赖默认服务器 -
srv.ListenAndServe()必须放在单独 goroutine 里启动,主 goroutine 留给信号监听 -
signal.Notify要同时监听syscall.SIGINT和syscall.SIGTERM,只监听os.Interrupt在容器环境会失效 - channel 必须带缓冲:
make(chan os.Signal, 1),否则第一个信号可能丢失
srv.Shutdown() 前为什么要先让监听器停摆
Shutdown() 自己不会关 listener,只等已有连接结束。如果 listener 还开着,新请求照进不误,关到一半又来一堆,永远关不完。
- 收到信号后第一件事是调
srv.Close(),它立刻让ListenAndServe()返回http.ErrServerClosed - 紧接着再调
srv.Shutdown(ctx),这才是真正等活跃请求完成的环节 -
srv.Close()是立即返回的;srv.Shutdown()是阻塞的,必须配context.WithTimeout() - 别在
Shutdown()之后再调Close(),会 panic
数据库和消息消费者怎么同步退出
HTTP server 关了,但后台 goroutine 还在往 DB 写日志、消费者还在拉 Kafka 消息——这些资源不关,等于没关干净。
立即学习“go语言免费学习笔记(深入)”;
- 所有可关闭组件(
*sql.DB、*amqp.Connection、自定义 worker)都得实现Close() error方法 - 关闭顺序必须反向依赖:先停消息消费 → 再等 DB 连接池空闲 → 最后关 HTTP server
-
db.Close()会阻塞,直到所有连接归还或 ctx 超时,所以必须传入和Shutdown()同源的 context - 别把
db.Close()放defer里——main 函数在Shutdown()返回后就退出了,defer根本没机会执行
handler 里不响应 cancel 会导致 shutdown 卡死
Shutdown() 只管连接层,不管 handler 里干啥。如果你在 handler 里写了 time.Sleep(20 * time.Second) 或没带 context 的 db.Query(),那整个 shutdown 就卡在这儿不动。
- 所有 I/O 操作必须用
Context版本:db.QueryContext()、http.NewRequestWithContext() - 长耗时操作(如文件导出、第三方调用)要定期检查
ctx.Err() == context.Canceled - 别用
select {}或无超时的time.Sleep,它们无视 cancel 信号 - 上线前用
db.SetConnMaxLifetime()和db.SetMaxOpenConns()控制连接老化,避免旧连接拖慢db.Close()
真正卡住 shutdown 的,往往不是 HTTP 层,而是某个没设超时的 DB 查询,或者一个忘了监听 ctx.Done() 的后台 goroutine。上线前务必用 pprof 查 goroutine 堆栈,确认所有清理路径都走到了。


















