bigywater.tech
gRPC · 34

优雅停机:GracefulStop 与 Stop 的区别

服务发版重启时怎么才能不丢请求?Stop 和 GracefulStop 差在哪?会不会关不掉?关闭之后 handler 还在跑吗?这篇用 16 路持续负载把每一种关闭方式的失败数都数出来。

失败 8 个 vs 0 个
负载下关掉一个后端:Stop() 与 GracefulStop() 的失败调用数
2002.1 ms 没返回
有一个永不结束的流时,GracefulStop 等了这么久仍未返回
Stop() 返回时仍有 handler 在跑
默认情况下 Stop() 不等 handler;加 WaitForHandlers(true) 才等

两种关闭

实测 · 本机执行输出
  Stop()          调用本身用了 0.2 ms;客户端的在途调用:code=Unavailable message="error reading from server: EOF",102.7 ms 后返回
  GracefulStop()  调用本身用了 400.4 ms;客户端的在途调用:成功,501.6 ms 后返回

Stop 立刻断开所有连接,在途调用失败。GracefulStop 先不再接受新请求(发 GOAWAY),等在途的调用做完再关,在途调用成功。

可能关不掉

实测 · 本机执行输出
  GracefulStop 等了 2002.1 ms,仍然没有返回
  再调用 Stop():GracefulStop 在 2002.8 ms 后才返回;长流的客户端:code=Unavailable message="closing transport due to: connection error: desc = \"error reading from…"

GracefulStop 没有超时。有不结束的长流时它一直等。常见做法:GracefulStop 加超时,超时后再 Stop。

滚动重启

实测 · 本机执行输出
  Stop()                             总调用  2184,失败  8;关闭动作用了 0.4 ms;关闭之后后端 0 又收到 0 次、后端 1 共 1814 次;第一个失败:code=Unavailable message="error reading from server: EOF"
  GracefulStop()                     总调用  2208,失败  0;关闭动作用了 15.7 ms;关闭之后后端 0 又收到 0 次、后端 1 共 1839 次;第一个失败:(没有失败)
  先 health.Shutdown,等 300 ms,再 GracefulStop() 总调用  2400,失败  0;关闭动作用了 301.0 ms;关闭之后后端 0 又收到 0 次、后端 1 共 2040 次;第一个失败:(没有失败)

在持续负载下,Stop 会让关闭时正在处理的调用失败;GracefulStop 做到 0 失败。先把健康状态设为 NOT_SERVING 再 GracefulStop,让依赖健康检查的负载均衡器有时间摘掉节点。

handler 是否结束

实测 · 本机执行输出
  WaitForHandlers(false) Stop() 用了 0.1 ms;Stop 返回时仍在运行的 handler 1 个
  WaitForHandlers(true ) Stop() 用了 802.3 ms;Stop 返回时仍在运行的 handler 0 个

Stop 默认不等 handler 返回,Stop 之后 handler 可能仍在运行。需要的话用 WaitForHandlers(true)(实验性选项),或自己等待。

分步演示

优雅停机
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。负载是本机 16 个 goroutine 在 round_robin 上连续调用(handler 睡 20 ms);失败数是 3 秒内客户端观察到的非 nil 错误;时间是本机测量,每种关闭方式只跑了一次(失败数 8 这样的数字每次运行会有差别)
    • 没有验证:客户端不使用 healthCheckConfig 和 round_robin 时(pick_first)的重启表现;服务注册/发现系统里“先注销再关闭”的流程;在 Kubernetes 里 preStop 钩子和 terminationGracePeriodSeconds 的配合;流式 RPC 在 GracefulStop 期间的客户端重连;WaitForHandlers 的行为在更多场景下的验证
    京ICP备2026067502号-1京公网安备11010502063487号