router.Run() 无法优雅停机,因其直接调用阻塞式 http.ListenAndServe() 且不暴露 *http.Server 实例,导致无法调用 Shutdown();必须显式构建 http.Server、监听 SIGINT/SIGTERM/SIGQUIT 信号、用带超时的 context 调用 Shutdown(),并设置 ReadTimeout/WriteTimeout 防止连接卡死。

router.Run() 为什么不能优雅停机
router.Run() 内部直接调用 http.ListenAndServe(),全程阻塞主线程,且不暴露 *http.Server 实例。你根本拿不到句柄去调 Shutdown()——Ctrl+C 或 kill -15 一发,进程立刻退出,正在执行的 time.Sleep(5 * time.Second)、数据库事务、消息发送全被中断。日志里看不到“已提交”,用户收到 502 或超时,这就是假优雅。
必须显式构造 http.Server 并监听信号
核心就三件事:启动服务 goroutine、监听信号、触发 Shutdown()。漏掉任一环,“优雅”就失效。
- 用
make(chan os.Signal, 1)创建带缓冲的 channel,否则首次信号可能丢失 - 必须监听
syscall.SIGINT(Ctrl+C)、syscall.SIGTERM(k8s 删除 Pod)、syscall.SIGQUIT(systemd 常用),别漏SIGQUIT -
srv.Shutdown()必须传入带超时的context.Context,例如context.WithTimeout(context.Background(), 5*time.Second);设 30 秒以上容易拖慢发布节奏,设 1 秒又可能掐断正常上传或支付回调 - 启动服务要包在 goroutine 里,且需忽略
http.ErrServerClosed错误,否则会误报 panic
ReadTimeout / WriteTimeout 不设等于白做
Shutdown() 只管 HTTP 连接层,不等慢客户端。如果没提前给 http.Server 设 ReadTimeout 和 WriteTimeout(比如都设成 15 * time.Second),一个卡住的 TCP 连接会让 Shutdown() 无限等待,进程永远关不掉。
- 未设
ReadTimeout:客户端发一半请求就挂起,server 卡在读 body - 未设
WriteTimeout:响应写到一半网络抖动,连接 hang 住 - 这两个 timeout 必须小于 Shutdown 的 context 超时,否则它们起不了作用
Shutdown() 后进程还卡着?查你的 goroutine
Shutdown() 完全不管你的业务 goroutine。它只等 HTTP 连接处理完,但下面这些若没响应 ctx.Done(),main 函数就会卡在 Shutdown() 返回后不动:
-
go time.AfterFunc(...):应改用time.AfterFunc前加select { case - 数据库连接池、Redis 客户端没调
Close(),GC 不保证及时回收 - handler 里启了
go sendToKafka()却没传 cancel channel,消息发一半就断电 - 健康上报、定时任务、WebSocket 长连接管理器……只要没监听
ctx.Done(),就还在跑
验证是否真优雅:curl http://localhost:8080/ping & 立即 Ctrl+C,再看进程是否消失、日志是否有 “Server closed under request” —— 如果没有,说明至少有一个 goroutine 没响应取消信号,或者超时设得太短把正常请求掐了。


















