不能直接 kill -9,因其发送 sigkill 无法被捕获,导致 defer、close()、事务回滚等清理动作失效;应使用可捕获的 sigint(-2)或 sigterm(-15),并监听 sigquit;shutdown() 需配合理超时与 read/writetimeout,并避免 context.background()。

为什么不能直接 kill -9?
因为 SIGKILL 信号根本捕获不到,Go 进程连 defer、Close()、数据库事务回滚这些基本清理动作都来不及执行。用户发的请求可能卡在读 c.Request.Body 半途,或刚扣款还没写订单,进程就没了——这不是“关服务”,是“拔电源”。
-
kill -2(即SIGINT)和kill -15(即SIGTERM)能被 Go 捕获,必须靠它们触发退出流程 -
SIGQUIT也要监听:某些容器平台(如旧版 Kubernetes + systemd)会发它,漏掉会导致本地测试 OK、线上不退出 -
SIGKILL别往signal.Notify里加——加了也白加,Go 会静默忽略
怎么用 http.Server.Shutdown() 真正等完请求?
Shutdown() 不是“立刻停”,而是“不再接新连接,但等老请求跑完或超时”。关键在超时时间怎么设、上下文怎么传:
- 超时别拍脑袋定:5 秒适合多数 REST API;文件上传、支付回调等长耗时接口得拉到 10–30 秒,否则
Shutdown()一到时间就强制断开,用户看到 502 或超时 - 必须配
ReadTimeout和WriteTimeout(比如各15 * time.Second),否则慢客户端占着连接不发完数据,Shutdown()就永远卡住 - 别用
context.Background()直接传进Shutdown()——应带超时:ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second),之后defer cancel()
哪些 goroutine 会偷偷拖住进程不退出?
http.Server.Shutdown() 只管 HTTP 连接,不管你的业务 goroutine。常见泄漏点:
- 没用
ctx.Done()做退出判断的定时器:time.Ticker.C会一直吐,得改成select { case - 消息消费者(如 Kafka/Redis pubsub)、健康检查上报、日志 flush goroutine,全得自己加
ctx控制 - 数据库连接池、Redis 客户端、HTTP 客户端等,必须显式调
Close(),GC 不会帮你关底层 TCP 连接
验证优雅关停到底成没成功?
别只看日志有没有 “shutting down”——那只是开始,不是结束。真实验证要动手测:
- 启动服务后,立刻发一个慢请求:
curl "http://localhost:8080/ping" &(后端time.Sleep(8 * time.Second)) - 马上
Ctrl+C或kill -15 $PID - 等 5–10 秒后,执行
ps aux | grep yourapp,进程必须消失;如果还在,说明有 goroutine 卡住没退出 - 更进一步:在慢请求里写个 Redis 计数器,关停前后查值是否一致,确认请求真被处理完了
Gin 本身不碰信号和关闭逻辑,所有细节都在 net/http 和 os/signal 里——这意味着你得自己兜底每一步,少一个 ctx、漏一个 Close(),优雅就变成假优雅。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











