Go 服务优雅关闭:收到退出信号时把在途请求跑完
test12026-09-224 次阅读
直接 kill 会丢请求
线上部署时,Kubernetes 滚动更新或手动重启都会向进程发 SIGTERM。如果进程立刻退出,那些正在处理的 HTTP 请求会被强制中断,客户端拿到的是连接重置而不是正常响应。优雅关闭的目标是先停止接收新请求,再把已经在飞的请求跑完,最后才退出。
用 signal.NotifyContext 捕获信号
Go 1.16 之后推荐用 signal.NotifyContext,它把操作系统信号包装成一个会被取消的 context,比手动注册 channel 更干净。下面是一段可直接运行的完整示例。
package main
import (
"context"
"log"
"net/http"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
srv := &http.Server{Addr: ":8080", Handler: mux}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server error: %v", err)
}
}()
log.Println("server started on :8080")
<-ctx.Done()
log.Println("shutting down...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("forced shutdown: %v", err)
}
log.Println("server exited cleanly")
}
这里发生了什么
- ListenAndServe 放在 goroutine 里,主协程阻塞在 <-ctx.Done(),等信号到来。
- 收到信号后,srv.Shutdown 会先关闭监听端口,拒绝新连接,再等待旧连接自然结束。
- 10 秒超时是兜底:万一有长连接一直不结束,到点就强制退出,避免进程卡死。
还要注意的几点
如果业务里还有后台 goroutine(比如消费消息队列、定时任务),也要监听同一个 ctx 并在退出前等待它们结束。只关闭 HTTP Server 不够,要让整条处理链路都参与优雅退出,才算真正不丢工作。
T
test1
文章作者
直接 kill 会丢请求 线上部署时,Kubernetes 滚动更新或手动重启都会向进程发 SIGTERM。如果进程立...
- 分类
- 技术
- 发布时间
- 2026-09-22
- 字数
- 约 1394 字
- 阅读
- 4 次
