Golang Web服务如何优雅关闭_Golang服务平滑退出方案

2次阅读

必须用带超时的server.Shutdown()实现优雅关闭:先拒新连,再等待存量请求完成或超时,最后释放资源;Close()会强杀连接,Shutdown不带超时则可能永久阻塞。

Golang Web服务如何优雅关闭_Golang服务平滑退出方案

为什么不能直接用 server.Close()

因为 server.Close() 会立刻关闭 listener,所有新连接被拒,**已接受但尚未写响应的请求也会被强制断开**——客户端大概率收到 connection reset 或空响应,相当于“拔电源”。这不是优雅,是强杀。

  • 本地 Ctrl+Ckill -2 发的是 SIGINT,K8s、systemd、docker 默认发 SIGTERM;只监听一个,上线后就关不掉
  • SIGKILLkill -9)无法捕获,也不该处理,别白费劲
  • 没配超时的 server.Shutdown(context.background()) 可能永久卡住——比如 handler 里写了 select{} 或阻塞 channel

必须用 server.Shutdown() 配超时 context

Shutdown()go 1.8+ 官方定义的唯一优雅路径:它先拒新连、再等存量连接自然退出(或超时)、最后释放资源。但必须带 deadline,否则等于没设保险。

  • 超时时间不是越长越好:设 5 秒还是 30 秒,取决于你最长请求的合理耗时;太短 → 强制中断;太长 → CI/CD 卡住、平台强杀
  • srv.ListenAndServe() 不能放在 main 协程里——它会阻塞,后续信号监听和 Shutdown() 根本没机会执行
  • 启动服务必须用 go srv.ListenAndServe(),然后主 goroutine 留给信号监听和关闭逻辑

真实可运行的最小闭环示例

以下代码去掉注释就能跑,覆盖开发(SIGINT)和生产(SIGTERM)两种信号,且含超时兜底:

package main 

import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" )

func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r http.Request) { time.Sleep(2 time.Second) // 模拟业务 w.Write([]byte("OK")) })

srv := &http.Server{Addr: ":8080", Handler: mux} // 启动服务(非阻塞) go func() { log.Println("Server starting on :8080") if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } }() // 监听信号 quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) <-quit log.println("received shutdown signal")>

}

后台 goroutine 怎么一起退出?

Shutdown() 只管 HTTP 连接,不管你的定时任务、消息消费协程或数据库连接池。如果这些 goroutine 不响应取消,服务照样关不干净。

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

  • 所有长期运行的 goroutine 必须接收 ctx.Done(),比如 select { case
  • DB 连接池建议调用 db.Close(),HTTP 客户端应设 Timeout 并在 ctx.Done() 时主动退出
  • 反向代理(如 Nginx、Traefik)需配置健康检查探针,确保流量在 Shutdown() 开始前就切走

真正难的从来不是关服务器,而是确认所有依赖资源都收口了——尤其那些没显式用到 context 的老代码。

text=ZqhQzanResources