Go服务需手动监听SIGINT/SIGTERM信号,通过signal.Notify注册并调用server.Shutdown()优雅关闭,避免请求中断和数据不一致。

Go 服务启动后如何监听系统中断信号
Go 的 http.Server 本身不自动响应 SIGINT(Ctrl+C)或 SIGTERM(如 Kubernetes 发送的终止信号),必须手动捕获并触发关闭逻辑。否则进程会直接被杀,正在处理的请求可能被截断、连接重置,甚至导致数据不一致。
关键做法是用 signal.Notify 监听指定信号,并在收到后调用 server.Shutdown():
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("收到退出信号,开始优雅关闭...")
if err := server.Shutdown(context.Background()); err != nil {
log.Printf("Shutdown 失败: %v", err)
}
}()注意:server.Shutdown() 会等待所有活跃连接完成或超时,但不会等待新连接——它先关闭监听器,再逐个等待已接受连接结束。
Shutdown 超时时间设多少才合理
超时不是越长越好,也不是越短越安全。它决定了你愿意为「未完成请求」等待的上限。默认传入 context.Background() 没有超时,会导致关机卡死(比如某个慢查询或阻塞 I/O 永不返回)。
立即学习“go语言免费学习笔记(深入)”;
推荐显式设置带超时的 context:
- Web API 服务:通常
5–10 秒足够让大多数 HTTP 请求收尾(含重试、重定向等) - 后台任务型 HTTP handler(如导出大文件、批量写 DB):需按业务最长耗时评估,但建议拆离主 HTTP 流程,改用异步队列
- 绝对不要用
context.TODO()或空 context,容易掩盖问题
示例:
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("Shutdown 超时或出错: %v", err)
}HTTP handler 中如何配合 Shutdown 做清理
server.Shutdown() 只保证连接层不再接收新请求,但已进入 handler 的 goroutine 仍会继续执行。如果 handler 内部有长耗时操作(如数据库事务、文件写入、第三方 API 调用),需主动感知上下文取消信号。
常见错误写法:time.Sleep(10 * time.Second) 不响应 cancel;正确方式是用 select 等待 ctx.Done():
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(10 * time.Second):
w.Write([]byte("done"))
case <-ctx.Done():
log.Println("请求被中断:", ctx.Err())
return // 提前退出,避免浪费资源
}
}所有阻塞调用(db.QueryContext、http.DefaultClient.Do、time.After 等)都应优先使用带 Context 的变体。
为什么 ListenAndServe 返回 err != nil 时不能直接忽略
很多人写成 log.Fatal(server.ListenAndServe()),这看似简洁,实则埋下隐患:当 server.Shutdown() 成功执行后,ListenAndServe() 会返回 http.ErrServerClosed,这不是异常,而是预期行为。若用 log.Fatal,会导致进程非正常退出,绕过后续清理逻辑(如关闭数据库连接池、释放文件句柄等)。
正确处理方式:
- 检查错误是否为
http.ErrServerClosed,是则视为正常退出 - 其他错误(如端口被占、TLS 配置失败)才应 panic 或记录 fatal 日志
示例:
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}真正的优雅退出,不只在于“不丢请求”,更在于“不漏资源”。http.ErrServerClosed 是 shutdown 流程完成的标志,不是失败信号。










