直接用server.Close()会导致上传中断和流响应截断,因为它立即关闭监听器并强制终止所有活跃连接,不等待handler返回、不保障事务提交、不通知客户端,等同于拔网线;而Shutdown()才先拒新连、再等旧连完成或超时。

为什么直接用 server.Close() 会导致上传中断和流响应截断
server.Close() 是底层 socket 强制关闭:监听器立刻失效,所有活跃连接被 connection reset 中断。它不等 handler 返回,也不管数据库事务是否提交完毕。
常见现象包括:大文件上传卡在 87%,SSE 流突然断开,长轮询返回空响应,gRPC 客户端收到 UNAVAILABLE 状态但服务端日志里没报错。
- HTTP 层、gRPC、WebSocket、自定义 TCP 连接全都不受
Close()保护 - 它绕过 Go 的 HTTP server 内部连接跟踪机制,相当于拔网线
- K8s 场景下,Pod 被删后客户端持续收错,监控看到大量 connection reset by peer
必须用 http.Server.Shutdown() 并传带超时的 context.Context
Shutdown() 才是标准路径:先关闭 listener 拒绝新连接,再逐个等待已有 handler 返回或超时。但它只管 HTTP 连接层,不自动处理 DB、Redis、gRPC 或后台 goroutine。
关键约束有三个:
- ctx 必须带 deadline,例如
context.WithTimeout(ctx, 20*time.Second);传context.Background()可能永久阻塞 - K8s 环境下,这个 timeout 必须比
terminationGracePeriodSeconds小 5–10 秒(比如后者设 30 秒,这里最多设 25 秒) - 若用了自定义
net.Listener(如复用端口、TLS 终止),调Shutdown()前建议先显式ln.Close(),防止信号接收间隙涌入新连接
信号监听必须带缓冲通道 + 显式接收逻辑
只写 signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) 不够——这仅仅是注册,不阻塞也不触发任何行为。主 goroutine 若随后直接跑 srv.ListenAndServe(),就会被阻塞,永远收不到信号。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
正确姿势是:
- 声明带缓冲的 channel:
sigChan := make(chan os.Signal, 1),防丢信号(尤其 Ctrl+C 连续按两次) - 必须有显式接收动作,例如
for range sigChan或,且该逻辑不能被其他阻塞操作挡住 - 推荐把
srv.ListenAndServe()放到 goroutine 里,留出主 goroutine 专门等信号 - 别监听
SIGHUP做关机用途,它语义是重载配置,混用会引发误判
goroutine 泄漏和 context 超时怎么配合优雅退出
仅停止接收新请求不够,已启动的 goroutine(比如 metrics 上报定时器、Kafka 消费循环、WebSocket 心跳)若没监听 ctx.Done(),进程就卡住不退出。
典型泄漏点:
- 后台 goroutine 用
time.Sleep硬等,没加select { case - DB 连接池、Redis client、gRPC server 没调
.Close()或.GracefulStop() - WebSocket 连接没维护在
sync.Map里,shutdown 时无法遍历调conn.Close() - 用
sync.WaitGroup等待子 goroutine 时,Wait()放太早,部分 goroutine 还没收到退出通知
最易被忽略的是:多个组件 shutdown 顺序不对。比如先关 HTTP,但 gRPC 还在收请求,健康探针却已失效——应统一用一个 ctx 控制生命周期,串行执行 grpcServer.GracefulStop() → httpServer.Shutdown() → db.Close() → redisClient.Close(),每步都受同一超时约束。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










