用 kill -sigterm 测试优雅关机需先确认进程存活并已注册 syscall.sigterm 信号,否则会硬终止;执行后应输出“shutting down…”等日志,而非立即退出。

怎么用 kill -SIGTERM 测试优雅关机主流程
本地压测时直接 kill -SIGTERM 进程是最贴近 K8s 环境的测试方式,但必须确保程序已启动且监听了 syscall.SIGTERM。常见失败是进程已退出或信号未注册,导致 kill 后无响应、日志没输出、端口仍被占着。
- 先用
ps aux | grep your-binary确认进程 PID 存在,避免测试对象错误 - 必须提前在代码里调用
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM),只监听SIGINT的话kill -SIGTERM会直接硬终止 - 执行
kill -SIGTERM $PID后,观察日志是否打出 “shutting down…”、“waiting for HTTP server” 等关键提示,而不是立刻退出 - 若无反应,检查是否漏掉了
阻塞等待逻辑——<code>signal.Notify本身不阻塞,不接收就等于没监听
为什么 Ctrl+C 在本地调试中经常“失效”
不是 Ctrl+C 失效,而是你把 http.ListenAndServe 放在了信号监听之后,它一运行就阻塞主 goroutine,后续的 根本没机会执行。
- 正确顺序:先
signal.Notify+ 启动服务(非阻塞方式,如go srv.ListenAndServe()),再 - 如果用了
srv.ListenAndServe()直接阻塞,那信号永远收不到,Ctrl+C 只能触发默认行为(立即退出) - Windows 下尤其明显:残留
your-binary.exe进程、端口报address already in use,本质是套接字没释放,清理逻辑根本没跑 - 验证方法:在
cleanup()开头加log.Println("cleanup started"),按 Ctrl+C 后没这行日志,就是信号没捕获到
怎么验证 Shutdown() 是否真等完了活跃请求
Shutdown 不是“等所有 handler 返回”,而是等每个连接的 Handler 主动退出 —— 如果 handler 没监听 ctx.Request.Context().Done(),它就会卡住,导致 Shutdown 超时返回 context.DeadlineExceeded。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 写个故意 sleep 10 秒的 handler:
time.Sleep(10 * time.Second),然后发一个请求,再发SIGTERM,看 Shutdown 是否真等满 10 秒才返回 - 用
curl -v http://localhost:8080 &发起长连接,再 kill,观察连接是否收到 FIN 包(可用tcpdump或 Wireshark 验证) - 检查
Shutdown()返回值:非 nil 错误基本都是context.DeadlineExceeded,说明有 handler 没响应 cancel;返回 nil 才代表所有连接已干净退出 - 别依赖
netstat -an | grep :8080看端口是否关闭——它只反映 listener 状态,不反映连接是否真正断开
测试多服务协同关闭时最常踩的坑
HTTP server、gRPC server、Kafka consumer、DB 连接池一起关时,顺序错一环,轻则 panic,重则数据丢失。测试阶段最容易忽略的是「资源关闭的可见性」。
- 不要只看日志有没有 “DB closed”,要确认
*sql.DB.Close()是否真返回了——它是阻塞调用,若还有 goroutine 正在用db.Query,它会一直 hang - gRPC server 必须用
grpcServer.GracefulStop(),不是Stop();后者不等流式 RPC,但也不会报错,测试时看不出问题,上线后才发现流中断 - 用
sync.WaitGroup等 worker goroutine 退出时,务必在每个 worker 的defer wg.Done()前加清理逻辑,否则wg.Wait()返回了,worker 还在往 DB 写数据 - 测试时加个
time.Sleep(500 * time.Millisecond)在所有关闭逻辑之后再os.Exit(0),能暴露很多“以为关完了其实没关完”的竞态问题
真实环境里,优雅关机的成败不在 Shutdown() 调得对不对,而在于每个 handler、每个 goroutine、每个外部 client 是否都真正尊重了 context 取消信号——这点没法靠单测覆盖,只能靠压测+信号+日志三者交叉验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










