go 1.8+ 唯一推荐的优雅关机方式是 http.server.shutdown(),它先拒新连接再等待存量请求自然完成或超时;必须配合带超时的 context.context,禁用会立即中断连接的 close()。

Go 1.8+ 直接用 http.Server.Shutdown() 做优雅关机
Go 1.8 起,http.Server 原生支持优雅关机,Shutdown() 会拒绝新连接、等待已有请求完成(或超时),无需第三方库。这是最轻量、最可控的方式。
-
Shutdown()不会自动 fork 新进程,它只负责“安全收尾”,重启需配合外部工具(如 systemd、supervisord)或手动 kill + restart - 必须显式启动服务在 goroutine 中,否则
ListenAndServe()会阻塞,信号监听逻辑无法执行 - 超时 context 是硬性要求:不设 timeout,
Shutdown()可能永久阻塞(比如有长轮询或未设 ReadTimeout 的慢客户端) - 注意捕获
http.ErrServerClosed错误——这是正常关闭信号,不是异常,不应 panic 或 fatal
fvbock/endless 实现真正的零停机热重启
endless 是目前最成熟、开箱即用的热重启方案,它通过 fork() + 文件描述符继承,在单进程内完成新旧服务交替,用户无感知。
- 默认监听
SIGHUP(kill -1 $PID)触发重启,SIGINT/SIGTERM触发关机,SIGUSR2触发 HammerTime(强制终止残留连接) - 新进程启动后,旧进程不会立即退出,而是等所有活跃连接自然结束;但若连接卡住,
endless.DefaultHammerTime会强制终结(默认 45s) - 必须替换原生
http.ListenAndServe为endless.ListenAndServe,且不能提前调用os.Exit()或 panic(会破坏 fork 流程) - 不兼容 Windows(依赖 Unix fork 系统调用),生产环境仅限 Linux/macOS
信号选择与陷阱:哪些信号能用,哪些根本没用
Go 程序能捕获并响应的信号有限,kill -9(SIGKILL)和 SIGSTOP 永远无法被 Go 处理,它们由内核直接干预进程生命周期。
- 可靠信号:
SIGINT(Ctrl+C)、SIGTERM(kill $PID)、SIGHUP(kill -1 $PID)、SIGUSR1/SIGUSR2 - 不可靠/禁用信号:
SIGKILL(kill -9)——无法注册 handler,进程立刻终止,请求必丢;SIGSTOP同样无法捕获 - 慎用
SIGUSR1:部分日志库(如 zap)默认用它触发日志轮转,若同时用于自定义重启逻辑,可能冲突 - 信号注册必须在
ListenAndServe()启动前完成,否则 goroutine 可能已阻塞,收不到信号
实际部署时最容易被忽略的三件事
本地跑通不等于线上可用。很多团队在压测或上线后才发现问题,根源往往不在代码逻辑,而在配套环节。
- 没有设置
ReadTimeout和WriteTimeout:导致慢连接长期占用 worker,Shutdown()超时后仍无法退出,最终被 OS 强杀 - 忽略子进程资源清理:用
endless重启时,旧进程的 goroutine、数据库连接、文件句柄若未显式 close,会持续泄漏 - 没验证负载均衡器行为:Nginx/ALB 默认健康检查间隔 > 优雅关机超时时间,可能导致新实例未 ready 就切流量,或旧实例已 shutdown 却还在转发请求
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











