go优雅停机必须配context.withtimeout,因srv.shutdown(context.background())无超时会导致永久阻塞;正确做法是用context.withtimeout设置10–30秒超时,检查返回错误并继续清理db、grpc等依赖资源。

Go 优雅停机必须配 context.WithTimeout
不设超时的 srv.Shutdown(context.Background()) 在生产环境等于埋雷——只要有一个请求卡在下游(比如没设 DB 查询超时、gRPC 客户端阻塞、HTTP 调用没 timeout),Shutdown 就永远不返回,进程 hang 死。
- 超时值不是随便写的:取业务最长响应时间的 1.5–2 倍;普通 REST API 建议 10–30 秒;含长轮询或流式接口的,得按实际最大耗时设
-
context.WithCancel也不行:没有自动超时机制,一旦某个 goroutine 不响应ctx.Done(),整个关闭流程就卡住 - 正确写法:
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second); defer cancel(); srv.Shutdown(ctx) -
Shutdown返回context.DeadlineExceeded是预期行为,不是 panic,应记录日志但继续执行后续清理
HTTP Server 关闭后别再往连接写响应
常见 panic:panic: http: Server closed,根本原因不是 Shutdown 出错,而是其他 goroutine 还在往已关闭的 http.ResponseWriter 写数据。
- 检查所有 handler 是否用了
time.Sleep、select{}或未设超时的 I/O 操作 - 避免在 handler 里启动无管控的 goroutine(如
go func() { w.Write(...) }())——主线程已退出,w已失效 - DB 连接池、MQ 消费者等依赖组件不能靠
defer关闭:main 函数在Shutdown后立刻退出,defer来不及执行 - 所有可关闭组件(
*sql.DB、*grpc.Server、自定义 worker)必须统一接收同一个ctx,监听ctx.Done()主动退出
gRPC 服务必须用 GracefulStop,不是 Stop
Stop() 是硬关,会立即断开所有连接、中断正在处理的 RPC;GracefulStop() 才是生产唯一合法选项。
-
GracefulStop()先置drain = true拒绝新连接,再 close listener,最后等活跃连接和 handler 全部退出 - 如果启用了
waitForHandlers选项,需确保所有 handler 都能响应ctx.Done(),否则会卡在handlersWG.Wait() - gRPC client 端也要配超时:
context.WithTimeout(ctx, 5*time.Second),否则 server 关闭时 client 可能无限等待 - 与 HTTP server 共存时,两个
ShutdownFunc必须注册到同一个graceful.Manager,共享同一超时ctx
信号监听要覆盖 SIGTERM 和 SIGINT,不能只监听一个
Kubernetes 发 SIGTERM,本地调试按 Ctrl+C 是 SIGINT——漏掉任何一个,服务就无法被正常终止。
-
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)是最小安全集;SIGHUP一般留给 reload config,不参与 shutdown 流程 - 不要用
os.Exit(0)强退:事务中断、缓存丢失、注册中心残留实例,客户端收到connection reset - 主 goroutine 收到信号后,应先触发
cancel(),再调用所有ShutdownFunc,最后os.Exit(0)—— 顺序错了资源就漏关 - 容器环境下,
SIGKILL(kill -9)无法捕获,所以必须确保GracefulStop+Shutdown在 Kubernetes 的 terminationGracePeriodSeconds 内完成
ctx 驱动,且各自内部必须尊重超时,否则优雅停机就变成“优雅等待”。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











