Gin本身不支持零停机热更新,必须依赖endless等外部库实现;开发可用Air/realize自动reload,但仅限本地调试。

直接上结论:Gin 本身不自带不停机更新能力,必须依赖外部机制;生产环境推荐用 endless 库做热重启,开发阶段可用 realize 或 Air 实现文件变更自动 reload,但二者都不适用于线上。
为什么 Gin.Default().Run() 不能热更新
因为 router.Run() 内部调用的是 http.ListenAndServe(),它会阻塞当前 goroutine 并独占监听套接字。一旦进程退出,内核立即回收 socket,所有未完成连接(包括正在读 body、写响应、长轮询、WebSocket)都会被 RST 中断。
- 现象:用户收到
ERR_CONNECTION_RESET或空响应 - 后果:事务未提交、日志截断、客户端重连风暴
- 关键点:不是 Gin 框架的问题,而是标准库
http.Server缺乏对监听器复用和子进程接管的支持
用 endless.ListenAndServe 替换 http.ListenAndServe
endless 的核心是让新进程继承父进程的监听 socket 文件描述符(FD),再通过 SIGUSR2 触发平滑升级。它不修改 Gin 路由逻辑,只替换启动入口。
- 安装:
go get github.com/fvbock/endless - 替换原启动代码:
endless.ListenAndServe(":8080", router)(代替router.Run(":8080")) - 触发更新:
kill -USR2 $(pidof your-app),旧进程会等待endless.DefaultHammerTime(默认 30s)后退出 - 注意:需手动设置超时,否则慢请求可能拖垮新进程 —— 建议显式配置
server.ReadTimeout和WriteTimeout
realize 和 Air 只适合开发,别上生产
realize 和 Air 都是通过 fsnotify 监控文件变化,然后 exec.Command("go", "run", ...) 启动新进程并 kill 旧进程。它们根本没做 socket 继承,每次 reload 都是硬重启。
- 现象:
connection refused瞬间出现,健康检查失败 - 适用场景:本地调试、CI 构建前验证、单机 demo
- 坑点:
realize依赖过时的gopkg.in/urfave/cli.v2,需在go.mod加replace才能安装 - 替代建议:开发期用
Air(更轻量、配置简单),但上线前务必切回endless或原生http.Server+ 自定义 graceful shutdown
Docker/K8s 环境下必须配合信号与就绪探针
容器化部署时,endless 仍有效,但需确保宿主环境支持 SIGUSR2 且不被 Docker 默认信号拦截。Kubernetes 滚动更新失败,往往不是 Gin 的问题,而是探针或信号配置缺失。
- 关键配置:在
deployment.yaml中设置terminationGracePeriodSeconds: 60,给旧进程留出处理时间 - 就绪探针(readinessProbe)必须检查服务是否真正可接受流量,不能只 ping 端口 —— 否则新 Pod 还没完成 socket 接管就被打入流量
- 避免陷阱:Docker 默认将
SIGTERM发给 PID 1 进程,若你的镜像用了sh -c启动,信号会被丢弃;应使用exec形式或init进程(如tini)
最容易被忽略的一点:热重启只解决连接层平滑,不解决业务状态迁移。比如内存缓存、计数器、WebSocket 连接池,这些不会自动跨进程同步。需要你主动设计共享存储或重建逻辑 —— endless 不负责这部分。


















