Golang标准库之os/signal系统信号处理 Go语言实现平滑关机技巧

1次阅读

go程序需用os/signal.notify监听sigterm,配合context.withcancel和http.server.shutdown实现优雅退出:先停listener,再等请求完成,最后清理资源;须避免信号丢失、并发shutdown、pid 1问题及长耗时操作无超时。

Golang标准库之os/signal系统信号处理 Go语言实现平滑关机技巧

Go 程序怎么捕获 SIGTERM 实现优雅退出

直接上核心:用 os/signal.Notify 注册信号通道,配合 context.WithCancel 或显式关闭逻辑。别等主 goroutine 退出才处理——那时 HTTP server 可能已拒收新请求,但还在跑的 handler 却没机会收尾。

常见错误是只监听 SIGINT(Ctrl+C),线上环境发的是 SIGTERM;还有人把 signal.Notify 放在 goroutine 里却忘了阻塞主流程,导致程序秒退。

  • signal.Notify 第二个参数必须显式列出要监听的信号,os.Interrupt 不等于 syscall.SIGTERM
  • 推荐用 make(chan os.Signal, 1),缓冲区为 1 防止信号丢失(比如快速连发两次 SIGTERM
  • 收到信号后,应先关闭 listener(如 http.Server.Shutdown),再等待活跃请求完成,最后退出

http.Server.Shutdown 为什么总超时失败

不是函数有问题,而是你没给它足够时间,或者没提前停止接收新连接。

Shutdown 只负责“不再接受新请求 + 等待已有 handler 返回”,它不会强制杀掉卡住的 goroutine。如果你的 handler 里有没设 timeout 的 http.Client.Do数据库查询或 channel receive,就会拖垮整个关机流程。

立即学习go语言免费学习笔记(深入)”;

  • 调用 Shutdown 前,确保已调用 srv.Close() 或类似操作停掉 listener(否则新连接仍可能进来)
  • Shutdown 的 context 要带合理超时,比如 context.WithTimeout(ctx, 10*time.Second),别用 context.background()
  • 检查所有长耗时操作是否都加了 context 控制,尤其是第三方库调用(如 redis.Client.Getsql.DB.QueryRowContext

多个服务共存时,如何统一信号响应逻辑

一个进程里跑 HTTP server、gRPC server、定时任务、后台 worker —— 不能每个都写一套 signal 处理。关键在于“信号只通知一次,退出动作可分层触发”。

典型踩坑是多个 goroutine 同时监听 SIGTERM,结果关了两遍 DB 连接,或 shutdown 被并发调用 panic。

  • 全局只用一个 signal.Notify,把信号转发到一个中心 chan os.Signal
  • sync.Once 包裹 shutdown 流程,确保无论哪个模块先响应,都只执行一次清理
  • 各组件暴露 Close() Error 方法,主 shutdown 函数按依赖顺序调用(比如先停 HTTP,再停 worker,最后关 DB)
  • 避免在 signal handler 里做重操作,只负责发通知、启 shutdown,具体逻辑放到单独 goroutine

linux 容器环境下 SIGTERM 收不到的常见原因

本地测试好好的,一上 docker 就“直接被 kill -9”,根本没走你的 shutdown 逻辑。大概率是进程没跑在 PID 1,或者容器启动方式绕过了信号转发。

Docker 默认把应用作为 PID 1 启动,但如果你用了 shell wrapper(如 ENTRYPOINT ["sh", "-c", "go run main.go"]),那真正跑 Go 的进程就不是 PID 1,SIGTERM 会被 sh 拦截,不透传。

  • 确认 Go 进程确实是 PID 1:进容器执行 ps -o pid,comm,看输出第一行是不是你的二进制名
  • 避免 sh -c 启动,改用直接执行:ENTRYPOINT ["./myapp"]
  • kubernetes 中,确保没有设置 terminationGracePeriodSeconds: 0,否则会跳过 graceful shutdown 直接发 SIGKILL
  • 某些 init 系统(如 tini)能帮你透传信号,可在 Dockerfile 中加入 RUN apt-get install -y tini 并设为 entrypoint

最常被忽略的一点:信号处理本身不保证原子性。比如你在 shutdown 里关 DB 连接,同时另一个 goroutine 正在用这个连接发 query —— 没加锁或 context 取消,就会 panic。平滑关机不是加个 signal 监听就完事,得从 handler 层、client 层、资源层全链路对齐生命周期。

text=ZqhQzanResources