根本办法是旧进程等待请求自然结束、新进程复用监听socket并接管新连接,核心靠net.Listener传递与SIGUSR2信号切换,必须用http.Server.Shutdown()配合超时上下文,避免RST中断和goroutine泄漏。
Go 服务如何避免重启时丢请求
根本办法不是“不重启”,而是让旧进程等正在处理的请求自然结束,新进程提前监听端口并接管新连接。核心靠
的传递和
(或
)信号触发切换。
常见错误是直接
或杀掉进程,导致 TCP 连接被 RST 中断,客户端收到 “connection reset” 或超时;另一类是没控制好旧 goroutine 的生命周期,导致内存泄漏或 panic。
必须用
配合上下文超时,不能只靠
新进程启动前,要通过文件描述符继承(
+
)复用父进程的监听 socket,否则会报
旧进程在
返回后才真正退出,期间拒绝新连接但继续服务已有连接
使用
库还是手写
逻辑
手写更可控,依赖少;
类库(如
或
)已过时或停止维护,且多数不兼容 Go 1.8+ 的原生
。现在推荐直接用标准库。
关键差异在信号处理时机和 listener 传递方式:旧进程收到
后 fork 新进程,把 listener fd 传过去;新进程用
恢复 listener,再调
。
立即学习
“
go语言免费学习笔记(深入)
”;
Go 1.8+ 必须用
,它会等待活跃请求完成,而
是立即关闭
超时时间建议设为 30–60 秒,太短可能强制中断长请求,太长拖慢升级节奏
注意
的 parent context 不要带 cancel,否则可能提前终止 shutdown 流程
触发重启时子进程拿不到监听端口
典型错误是子进程没正确继承文件描述符,或者父进程没在 fork 前调用
获取 fd 并传给子进程。Linux 下 fd 默认不继承,需显式设置
(但 Go 的
已自动处理)。
另一个坑是子进程启动后立刻调
,但此时父进程还没完全释放 socket,导致
—— 实际上是因为 socket 处于
状态,或父进程尚未关闭 listener。
父进程应在发送信号前先
,拿到 fd 后通过环境变量(如
)或命令行参数传给子进程
子进程启动后第一件事是
+
,确认能
再启动 HTTP server
父进程在子进程 ready 后再调
,可通过子进程写临时文件或 socket 通信确认
HTTP/2 和 TLS 场景下的优雅重启注意事项
HTTP/2 连接复用程度高,单个连接可能承载多个请求,
会等所有流完成才关闭连接;TLS 的
不影响优雅性,但若用
(如
),证书热加载需额外同步,否则子进程可能因缺证书无法启动 HTTPS 监听。
如果用了反向代理(如 Nginx),它的 keepalive 设置会影响上游连接复用行为:Nginx 默认 keepalive 75 秒,若 Go 服务 shutdown 超时设太短,可能被 Nginx 主动断连。
HTTP/2 下不要在
前关闭 TLS listener 单独,必须统一走
证书热更新需确保父子进程访问同一证书路径,或通过内存缓存共享(如用
)
用
测试时注意观察
是否出现,可验证连接是否复用
最易被忽略的是日志和 metrics 上下文延续性 —— 旧进程 shutdown 期间产生的日志、prometheus metrics 仍应正常上报,否则监控会出现断层。别让 graceful 逻辑本身变成新的单点故障源。
net.Listenersyscall.SIGUSR2SIGHUPos.Exit(0)http.Server.Shutdown()server.Close()os.NewFile()net.FileListener()address already in useShutdown()gracefulShutdowngracefulfacebookgo/gracetylerb/gracefulShutdown()SIGUSR2os.NewFile(uintptr(fd), "")net.FileListener()http.Server.Shutdown(ctx)Close()context.WithTimeoutSIGUSR2listener.(*net.TCPListener).File()fd.CloseOnExec = falseFile()listener.Accept()bind: address already in useTIME_WAITlistener.(*net.TCPListener).File()LISTENER_FD=3os.NewFile()net.FileListener()Accept()Shutdown()Shutdown()http.Server.TLSConfigauto-tlscertmagicShutdown()Server.Shutdown()certmagic.Cachecurl -v --http2Connection #0 to host xxx left intact