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 次