signal.notify需配接收循环,通道须带缓冲并显式接收;http.server.listenandserve()需启goroutine;shutdown()必须用带超时的context;websocket等长连接需手动管理;后台goroutine须监听ctx.done()。

signal.Notify 必须配接收循环,否则信号收不到
Go 默认不响应任何系统信号,signal.Notify 只是注册监听,不自动阻塞或触发逻辑。常见错误是调用完就继续往下走,main 函数结束进程直接退出,根本等不到信号。
- 通道必须带缓冲:
sigChan := make(chan os.Signal, 1),避免信号发送时无 goroutine 接收而丢失 - 必须有显式接收动作,比如
(单次)或 <code>for range sigChan(持续),且该操作不能被其他阻塞逻辑挡住 - 若主 goroutine 正在跑
http.Server.ListenAndServe(),它会阻塞,导致信号接收逻辑永远不执行——得把 server 启动放到 goroutine 里
http.Server.Shutdown() 必须带超时 context,不能用 background
http.Server.Shutdown() 是优雅关机的核心,但它本身是非阻塞的:调用后立刻返回,连接仍在处理中。不加超时控制,程序可能卡死,或在 K8s terminationGracePeriodSeconds 到期后被强杀。
- 必须用
context.WithTimeout(context.Background(), 10*time.Second)这类带 deadline 的 context,不能传context.Background() - 超时时间建议设为略小于你最长请求预期耗时(如 15 秒)或 K8s 的
terminationGracePeriodSeconds - 5s -
srv.Shutdown(ctx)返回非 nil 错误时,大概率是超时,此时应记录日志,但不必 panic;连接会被强制断开,这是设计内的兜底行为
长连接(WebSocket / SSE)不会被 Shutdown 自动等待
http.Server.Shutdown() 只管理 HTTP 连接生命周期,对已升级的 WebSocket 或 text/event-stream 响应无感知——它们复用底层 TCP 连接,但 net/http 不跟踪其内部状态。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 这类连接不会计入 Shutdown 的等待队列,即使还有活跃客户端,Shutdown 也可能提前完成
- 必须手动维护连接池:在 handler 中把升级后的
conn存入sync.Map,收到 shutdown 信号后遍历主动Close() - 更稳妥的做法是在 preStop hook 或 readinessProbe 切换后,给客户端发 disconnect 指令,再等几秒再触发 Shutdown
后台 goroutine 必须监听 ctx.Done(),不能靠 sleep 硬等
很多 handler 或 worker 内部含异步逻辑(如发消息、写 DB、轮询),若没响应上下文取消,Shutdown 后它们还在跑,资源不释放,甚至写脏数据。
- 所有阻塞 I/O 操作必须用带 context 的版本:
http.Client.Do(req.WithContext(ctx))、db.QueryRowContext(ctx, ...)、redis.Conn.DoWithContext(ctx, ...) - 自定义 goroutine 必须用
select { case 退出,不能写 <code>time.Sleep(5 * time.Second)这种无视 cancel 的代码 - 别在 handler 里启动无 context 控制的 goroutine,比如
go sendToKafka(...)—— 应该是go func() { select { case
真正难的不是写 shutdown 调用,而是让每个组件都“知道自己该什么时候停”。handler、中间件、worker、DB 连接池、长连接管理器,全得统一用同一个 context 驱动,且各自清理逻辑不能互相阻塞。漏掉一个,就可能让整个关机流程卡住或出错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










