Iris 的 app.Shutdown() 不会自动拒绝新请求,因为它仅停止接受新连接,不关闭已建立的 TCP 连接,也不干预路由层;新请求若在 LB 摘流前抵达,可能因监听器关闭而返回 502 或超时,需依赖 readinessProbe、preStop 等外部机制在请求抵达前拦截。

为什么 Iris 的 app.Shutdown() 不会自动拒绝新请求
Iris 本身不拦截已建立连接上正在抵达的 HTTP 请求,app.Shutdown() 底层调用的是 Go 标准库 http.Server.Shutdown(),它只停止接受新连接,但不会关闭已握手的 TCP 连接,也不会让路由层返回 503。这意味着:SIGTERM 发出后,若上游 LB(如 Nginx、Istio)还没摘流,新请求仍可能被转发进来,最终因监听套接字已关闭而返回 502 或超时。
这不是 Iris 的缺陷,而是 HTTP 服务器模型的固有行为——应用层无法单方面控制网络层流量走向。
- 必须依赖外部机制在请求抵达 Iris 前就拦截,比如 readinessProbe 失败触发 K8s Endpoint 移除
-
app.Shutdown()的作用是「等活跃请求自然结束」,不是「拒收新请求」 - 别误用
app.Close():它会立即关闭监听器,导致正在处理的请求被中断
如何在 Kubernetes 中配齐 preStop + readinessProbe + Shutdown 超时
K8s 环境下,优雅停机成败取决于三者协同是否及时。preStop 是第一道缓冲,readinessProbe 是流量闸门,Shutdown 是应用层收尾。
- preStop 需包含两步:先触发 readinessProbe 失败(例如写一个临时文件或改健康检查返回码),再
sleep 5等待 Kube-Proxy 刷新 iptables 规则 - readinessProbe 必须配置为可动态失效(比如检查某个本地文件是否存在,或调用内部 /health/ready 接口)
-
srv.Shutdown()必须带 context 超时,否则可能无限等待卡住;建议 15–30 秒,具体看业务最长请求耗时 - 确保容器 PID 1 是你的 Go 进程,不是 shell 脚本;否则 SIGTERM 根本收不到——用
exec ./your-binary启动
怎样让正在处理的请求真正“做完”,而不是被强制中断
Go 的 http.Server.Shutdown() 默认会等待所有活跃连接上的响应写完才返回,但前提是 handler 本身不阻塞、不 panic、不忽略 context 取消信号。
- 所有耗时操作(DB 查询、RPC 调用、MQ 消费)必须接受
context.Context并主动响应取消 - 避免在 handler 中启动无 context 控制的 goroutine,比如
go doSomething();应改为go doSomething(ctx) - 如果用了自定义中间件(如 JWT 验证、日志记录),确认它们不屏蔽或忽略
ctx.Done() - 定时任务、后台 worker 需单独注册 shutdown hook,例如用
sync.WaitGroup管理生命周期
平滑重启的关键:别让新旧实例同时争抢资源
重启不是单纯停旧启新,而是新实例 ready 前,旧实例不能彻底退出;否则会出现短暂 503 或连接拒绝。
- 新实例启动后,必须通过 readinessProbe 确认 DB 连通、缓存加载完成、依赖服务可达,才允许进流量
- 旧实例 shutdown 期间,要避免与新实例竞争共享资源(如 Redis 锁、MySQL 表锁、文件锁)
- 如果用了全局单例(如全局连接池、计数器),确保 shutdown 时不释放,直到新实例完全接管
- 别在
main()里直接写log.Fatal()—— 它会绕过 defer 和 signal handler,导致资源泄漏
Shutdown(),而是让整个链路——从 K8s Endpoint 更新、LB 摘流、TCP 连接自然断开、到每个 goroutine 主动退出——全部对齐时间窗口。任何一个环节延迟或错位,都会暴露为线上请求失败。


















