正确绑定 context.Context 需显式传入 HTTP/gRPC/server listener,用 http.Server.Shutdown 配合 signal.Notify 监听中断信号,确保 handler 和中间件使用 ctx 控制超时与取消,避免 goroutine 泄漏和 fd 传递错误。

Go 服务启动时怎么正确绑定 context.Context 做退出控制
服务启动后无法被外部信号中断,本质是没把 context.Context 传进关键阻塞点。HTTP server、gRPC server、自定义 listener 都得显式接收 ctx 并监听取消信号。
常见错误:直接调用 http.ListenAndServe,它不接受 context.Context;必须用 http.Server 的 Shutdown 方法配合 context.WithTimeout 或 signal.Notify 触发。
-
http.Server实例必须保存引用,不能声明后立即丢弃,否则无法调用Shutdown - 监听
os.Interrupt或syscall.SIGTERM时,要用signal.Notify注册到一个chan os.Signal,再用select等待它触发cancel() - 不要在
main函数里用time.Sleep模拟“服务运行中”——这会阻塞主 goroutine,导致信号无法及时响应
为什么 http.Server.Shutdown 有时卡住不返回
因为有正在处理的请求没结束,而 Shutdown 默认只等 30 秒(由 Context 超时决定),超时后强制关闭连接,但底层 listener 可能还在 accept 新连接。
典型现象:Shutdown 返回了,但日志里仍有新请求被打印;或者 Shutdown 阻塞超过预期时间,CPU 占用突降但进程未退出。
立即学习“go语言免费学习笔记(深入)”;
- 确保所有 handler 内部也使用传入的
ctx做超时控制,比如数据库查询用db.QueryContext(ctx, ...),而不是db.Query(...) -
http.Server.ReadTimeout和WriteTimeout不影响Shutdown行为,它们只管单次读写,不是整个请求生命周期 - 如果用了中间件(如 JWT 验证、日志),检查是否在中间件里启动了未受控的 goroutine,这些 goroutine 可能持有
ctx但没做 cancel 传播
平滑重启时子进程怎么继承监听 socket 文件描述符
Linux 下不能直接把 fd 从父进程“传给”子进程,必须用 SO_REUSEPORT + net.FileConn + os.NewFile 手动传递,否则重启瞬间会丢连接。
核心逻辑是:父进程收到 SIGHUP 后,调用 ln.(*net.TCPListener).File() 获取底层 fd,通过 syscall.Exec 把 fd 编号作为环境变量(如 LISTEN_FD=3)传给新进程,新进程用 os.NewFile(3, "...") 恢复 listener。
- Go 标准库没有开箱即用的平滑重启支持,
net/http更不提供该能力,必须自己封装或借助github.com/braintree/manners(已归档)或github.com/fvbock/endless(注意维护状态) - 务必验证新旧进程是否同时监听同一端口:用
lsof -i :8080查看,应看到两个 PID;若只看到一个,说明 fd 传递失败或新进程没正确读取 - 新进程启动后,父进程应在确认新服务健康(如 HTTP GET /health)后再调用
Shutdown,否则可能形成“双空窗”
goroutine 泄漏导致 Context 关闭失败的典型场景
哪怕 Shutdown 成功返回,如果还有 goroutine 在后台跑且没监听 ctx.Done(),服务进程也不会真正退出——这是最隐蔽的“假平滑”。
常见泄漏点:定时器未停止、channel 接收未加 select + default、第三方库内部启的 goroutine 没暴露关闭接口。
- 用
runtime.NumGoroutine()在Shutdown前后打日志,数值应明显下降;若持平或缓慢下降,大概率有泄漏 - 所有
time.Ticker必须调用ticker.Stop();所有for range ch改成for { select { case v := - 第三方 client(如
redis.Client、mongo.Client)务必调用其Close()方法,它们内部通常持有长连接和心跳 goroutine
Context 控制的是“可中断的等待”,不是“自动回收的资源”。每个阻塞点、每个 goroutine、每个外部依赖,都得主动对齐同一个 ctx,漏掉任意一环,平滑就只是看起来平滑。










