Go服务收到SIGTERM后应优雅关闭:用signal.Notify捕获信号,调用http.Server.Shutdown()配合context.WithTimeout控制等待时间,再手动关闭DB、Redis等依赖,并确保反向代理和K8s探针协同。

Go 服务收到 SIGTERM 后直接退出?那是没接住信号
Go 默认不处理系统信号,os.Exit(0) 或进程被 kill -15 杀掉时,正在执行的 HTTP handler、数据库写入、消息发送都会被粗暴中断。关键不是“要不要关”,而是“怎么让正在跑的活干完再退”。
核心是用 signal.Notify 拦住 SIGTERM 和 SIGINT,然后主动触发 shutdown 流程。
-
http.Server自带Shutdown()方法,它会关闭监听、拒绝新连接,并等待已有连接完成(默认无超时) - 必须配合
context.WithTimeout控制最长等待时间,否则可能卡死 - 别在
Shutdown()前调用server.Close()—— 那会立刻断开所有连接,优雅就没了
Shutdown 超时设成 30 秒够吗?得看你的最长请求耗时
超时不是越长越好,也不是随便拍个数。它代表“我最多等多久,之后强制收尾”。如果业务里有导出大文件、批量写库、调第三方慢接口等操作,这个值就得覆盖住最坏情况。
- 查日志或监控,确认 P99 请求耗时;若最高到 8 秒,建议设为
15s起步 - 设太短(如
1s),大量正常请求会被中断,表现为http: Server closed错误日志 - 设太长(如
5m),Kubernetes 的terminationGracePeriodSeconds可能先到期,容器被 SIGKILL 强杀 - 生产环境建议把超时时间做成可配置项,比如从环境变量读:
time.ParseDuration(os.Getenv("SHUTDOWN_TIMEOUT"))
DB 连接池、消息客户端这些怎么一起关?别只关 HTTP
http.Server.Shutdown() 只管 HTTP 层。如果你用了 sql.DB、redis.Client、kafka.Producer 等,它们内部有连接、缓冲、后台 goroutine,得手动协调关闭顺序和时机。
立即学习“go语言免费学习笔记(深入)”;
- 先停新任务:关掉定时器、暂停消费者、设置标志位拒绝新 job
- 再等存量处理完:比如等一个自定义的
jobQueue.Wait()或workerGroup.Wait() - 最后关依赖:调
db.Close()、redis.Close()—— 它们通常非阻塞,但要确保前面没人在用 - 注意顺序:不能先
db.Close()再等 HTTP handler,否则 handler 里查库会 panic
为什么加了 Shutdown 还看到 “connection reset by peer”?可能是反向代理没配好
常见于 Nginx / ALB / Traefik 前置场景。Go 服务已开始 shutdown,但反向代理还没感知到连接断开,继续往里转发请求,导致连接被重置。
- Nginx 需配
proxy_ignore_client_abort off;(默认 on),并确保proxy_read_timeout≥ Go 的 shutdown 超时 - ALB 要检查健康检查路径是否返回 200,且在 shutdown 期间仍能响应几秒(可用
/healthz+ 状态标记) - Kubernetes 中,
readinessProbe失败后流量才停止流入,所以要在收到信号后立刻让 readiness 探针失败(比如改个全局变量) - 本地测试时用
curl -v http://localhost:8080+kill -15 $(pidof your-app),观察是否真等到响应返回才退出
真正难的不是写那几行 Shutdown(),而是理清你整个程序里哪些 goroutine 在跑、哪些资源占着连接、哪些状态需要原子切换。漏掉一个,就可能丢数据或卡住进程。


















