bigywater.tech
gRPC · 20

健康检查:grpc.health.v1 与 Watch

gRPC 官方的健康检查协议怎么用?Check 和 Watch 的行为有什么不同?服务关闭时怎么表示?客户端怎么才会真的避开不健康的后端?这篇逐项验证。

NotFound vs 成功
Check 对没登记的服务返回 NotFound;登记为 NOT_SERVING 时 RPC 成功、status 为 NOT_SERVING
1000 次 → 14 条
快速翻转 1000 次状态,Watch 客户端实际收到的条数(最终状态正确)
[20 20 20] → [30 0 30]
后端 1 设为 NOT_SERVING 后,60 次调用分布(无 / 有 healthCheckConfig)

协议

实测 · 本机执行输出
  新建的 health.Server,Check(""):RPC 成功,status=SERVING
  Check("demo.Demo")(没登记):RPC 失败 code=NotFound message="unknown service"
  SetServingStatus(demo.Demo, NOT_SERVING) 之后 Check:RPC 成功,status=NOT_SERVING
  SetServingStatus(demo.Demo, SERVING) 之后 Check:RPC 成功,status=SERVING
  List:err=<nil>;"":SERVING "demo.Demo":SERVING

grpc.health.v1.Health 有 Check(一次性)、Watch(流式推送状态变化)、List。服务名为空字符串表示整个服务器的整体状态。health.Server 是官方实现,用 SetServingStatus 修改状态。

Watch 的语义

实测 · 本机执行输出
  Watch("svc") 刚开始(没登记):收到 [SERVICE_UNKNOWN]
  SetServingStatus(SERVING):收到 [SERVING]
  再连续设置 2 次 SERVING:收到 []
  设为 NOT_SERVING:收到 [NOT_SERVING]
  从 SetServingStatus 到客户端收到的延迟:0.2 ms
  紧接着快速交替设置 1000 次(最后一次是 SERVING):客户端收到 14 条,最后一条 SERVING

没登记的服务先返回 SERVICE_UNKNOWN;状态没变化不重复发;变化太快时中间状态会被合并,保证最后收到的是最新状态。

Shutdown

实测 · 本机执行输出
  初始:"" → SERVING;svc → SERVING
  Shutdown 之后:"" → NOT_SERVING;svc → NOT_SERVING
  Shutdown 之后再 SetServingStatus(svc, SERVING):svc → NOT_SERVING(被忽略)
  Resume 之后:"" → SERVING;svc → SERVING

Shutdown() 把所有服务设为 NOT_SERVING 并忽略之后的修改,Resume() 恢复。适合在停机前先让上游摘除自己。

客户端怎么用

实测 · 本机执行输出
  无 healthCheckConfig:全部 SERVING,60 次调用各后端收到 [20 20 20]
  无 healthCheckConfig:后端 1 设为 NOT_SERVING 后 1.5 秒内,最后一次打到后端 1 是在设置之后 1491.5 ms;之后 60 次调用各后端收到 [20 20 20]
  有 healthCheckConfig:全部 SERVING,60 次调用各后端收到 [20 20 20]
  有 healthCheckConfig:后端 1 设为 NOT_SERVING 后 1.5 秒内,最后一次打到后端 1 是在设置之后 0.1 ms;之后 60 次调用各后端收到 [30 0 30]
  有 healthCheckConfig:3 个后端全部 NOT_SERVING 后的调用:code=Unavailable message="pickfirst: health check failure: connection active but health check failed. status=NOT_SERVING"
  后端 2 恢复 SERVING 后的调用:<nil>

客户端只有在服务配置里设置了 healthCheckConfig,才会对每个连接发起 Watch,并避开 NOT_SERVING 的后端。没有这个配置,健康状态不影响负载均衡。

分步演示

健康检查
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。健康服务用 google.golang.org/grpc/health(官方实现);“快速翻转”的条数每次运行不同(两次运行分别是 8 和 14);时间差用 handler 与客户端本地的时钟测;“合并旧状态”“Shutdown 之后忽略”读自 health/server.go;客户端健康检查需要服务配置里的 healthCheckConfig,并导入 google.golang.org/grpc/health 以注册客户端检查
    • 没有验证:grpc_health_probe 等外部工具;Kubernetes 的原生 gRPC 探针;healthCheckConfig 指定非空服务名时的行为;health 检查本身失败(例如服务端没有实现 health 服务)时客户端的行为;大量后端时每个连接一个 Watch 的开销
    京ICP备2026067502号-1京公网安备11010502063487号