go程序需显式调用signal.notify监听sigint和sigterm,用带缓冲通道接收信号;http.server.shutdown需设10–30秒超时并确保外部依赖有超时;db.close前应限流并等待连接归还;调试时须日志验证信号捕获与goroutine泄漏。

Go 程序如何正确捕获 SIGINT 和 SIGTERM
Go 的信号处理不是自动生效的,必须显式调用 signal.Notify 注册监听,否则进程收到 SIGINT(Ctrl+C)或 SIGTERM(kill -15)会直接退出,根本没机会执行清理逻辑。
常见错误是只监听一个信号、漏掉 SIGTERM,或者在 main 函数末尾才启动监听,导致信号在监听前就已到达并被忽略。
- 必须在主 goroutine 启动后、长期运行逻辑(如
http.Serve)之前调用signal.Notify - 推荐同时监听
SIGINT和SIGTERM:signal.Notify(c, os.Interrupt, syscall.SIGTERM) - 通道
c建议用带缓冲的(如make(chan os.Signal, 1)),避免信号丢失 —— 多次快速发送信号时,无缓冲通道可能阻塞或丢弃后续信号
为什么 http.Server.Shutdown 总是超时失败
Shutdown 不是立即终止连接,而是先关闭 listener,再等待活跃请求完成。如果请求卡住(比如数据库慢查询、第三方 API 未设 timeout)、或 goroutine 泄漏(如未关闭的 time.Ticker),就会拖过 context.WithTimeout 设置的截止时间,最终触发强制关闭(Close),丢失数据或返回 500。
- 务必为所有外部依赖设置明确超时:HTTP client、DB query、RPC 调用都不能依赖默认行为
- 检查是否有 goroutine 在后台持续运行却未响应
ctx.Done()—— 比如用for range ch读 channel 时,没配合select监听ctx.Done() -
Shutdown的 context timeout 建议设为 10–30 秒;太短容易误杀,太长影响发布节奏
优雅停机时如何安全关闭数据库连接池
sql.DB 自身不提供 Close 阻塞等待所有连接归还,直接调 db.Close() 只是标记“不再接受新请求”,但已有连接可能仍在使用中。若此时进程退出,连接可能中断在半途,引发事务回滚或连接泄漏。
立即学习“go语言免费学习笔记(深入)”;
- 应先调
db.SetConnMaxLifetime(0)和db.SetMaxOpenConns(1)(或更小值),让活跃连接尽快自然归还 - 接着调
db.PingContext(ctx)等待空闲连接池稳定,再调db.Close() - 更稳妥的做法是:在
Shutdown开始后,主动拒绝新 DB 请求(例如通过全局开关或中间件拦截),再等存量请求完成
调试阶段怎么验证信号和 Shutdown 是否真被触发
本地测试时,常以为按了 Ctrl+C 就进了 shutdown 流程,其实可能因为 panic、panic 恢复机制干扰、或 signal channel 未被 select 到,导致逻辑根本没执行。
- 在
signal.Notify后立刻打印日志,确认监听已建立;在select收到信号后也立刻打日志,确认被捕获 - 在
Shutdown开始前后分别打印活跃 goroutine 数量:runtime.NumGoroutine(),对比变化能快速发现泄漏 - 用
kill -SIGTERM $(pidof your-program)替代 Ctrl+C,排除终端模拟器干扰;观察进程是否真正退出(而非卡住)
信号注册、上下文传播、资源释放顺序这三者稍有错位,整个优雅停机就变成“假装优雅”。尤其在集成中间件、gRPC server、WebSocket 长连接时,每个组件的关闭契约都得对齐,不然总有一环掉链子。










