router.run()无法优雅停机,因其内部直接调用阻塞式http.listenandserve()且不暴露*http.server实例,导致无法调用shutdown();必须手动构造server实例、异步启动、监听sigint/sigterm/sigquit信号并配合带超时的context调用shutdown(),同时设置read/writetimeout及清理后台goroutine。

router.Run() 为什么不能优雅停机
因为 router.Run() 内部直接调用阻塞式 http.ListenAndServe(),不暴露 *http.Server 实例,你根本拿不到句柄去调 Shutdown()。按 Ctrl+C 或收 SIGTERM 后进程立刻退出,正在执行的 time.Sleep(5 * time.Second)、数据库事务、Kafka 消息发送全被硬中断——用户收到 502,后台日志里没有“事务已提交”记录。
必须用 *http.Server 显式启动并监听信号
核心三件事:构造 server 实例、开 goroutine 启动、监听信号后调 Shutdown()。漏掉任一环节,“优雅”就失效。
-
srv := &http.Server{Addr: ":8080", Handler: router}—— 必须自己 hold 住这个实例 - 启动要放在 goroutine 里:
go func() { srv.ListenAndServe() }(),否则主线程卡死 - signal channel 必须带缓冲:
make(chan os.Signal, 1),否则首次信号可能丢失 - 只监听
syscall.SIGINT、syscall.SIGTERM、syscall.SIGQUIT——SIGQUIT是 systemd/k8s 常用信号,别漏
Shutdown() 的 context 超时和读写超时怎么设
Shutdown() 只等 HTTP 连接层完成,不等你的业务 goroutine;它也不管慢客户端,全靠你提前设好超时。
- context 超时建议从
5 * time.Second起步,电商类接口可设10 * time.Second,别超过30 * time.Second - server 必须提前设
ReadTimeout和WriteTimeout(如15 * time.Second),否则慢请求会让连接 hang 住,Shutdown()死等 - 调完
srv.Shutdown(ctx)后,要用select等它返回,不能直接往下走 - 别在
Shutdown()后立刻os.Exit(),得等 DB.Close()、Redis.Close()、自定义 ticker 停止等清理完成
哪些 goroutine 容易漏关导致进程卡住
Shutdown() 结束 ≠ 程序退出。HTTP 层关了,但你的后台任务还在跑,main 就卡在最后一步不动。
-
go time.AfterFunc(...)没监听ctx.Done()→ 改成select { case - 数据库连接池没调
db.Close(),Redis 客户端没调rdb.Close() - handler 里启了
go sendToKafka()却没传 cancel channel,消息发一半断电 - 健康上报 goroutine 用
time.Tick()循环,没加select { case
验证是否真优雅:起服务后立刻 curl -s http://localhost:8080/ping &(后台发一个 5 秒响应),马上 Ctrl+C。再 ps aux | grep yourapp,进程没了且日志有 "Server closed under request" 才算过关。否则一定是某处 goroutine 没响应取消信号,或者资源没 Close。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











