2. gRPC 健康检查协议基础:gRPC Health Checking Protocol (v1) 定义、Watch与Check RPC方法详解

gRPC 健康检查协议(Health Checking Protocol v1)是 gRPC 生态中用于探测后端服务是否能够正常处理请求的标准机制。它定义在 grpc.health.v1.Health 服务中,通过两个核心 RPC 方法——Check 和 Watch——来实现对服务实例存活状态与就绪状态的实时监控。该协议是构建高可用、自愈分布式系统的基石,也是实现优雅流量摘除与负载均衡的前提。

2.1 协议定义与服务模型

健康检查协议的核心是 Health 服务,它使用 Protocol Buffers 定义,并通过 gRPC 暴露。服务端需要实现该接口,客户端(如负载均衡器、Kubernetes 探针、服务网格 Sidecar)通过调用这些方法来判断服务状态。

核心 Protobuf 定义:
syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
  string service = 1;  // 要检查的服务名称,空字符串表示检查整个服务
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;         // 未知状态,通常表示尚未初始化或无法判断
    SERVING = 1;         // 服务正常,可以接收请求
    NOT_SERVING = 2;     // 服务异常,不应接收请求
    SERVICE_UNKNOWN = 3; // 请求的服务名称不存在
  }
  ServingStatus status = 1;
}

service Health {
  // 单向 RPC:客户端发起一次请求,服务端返回当前状态
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

  // 服务端流式 RPC:客户端发起请求,服务端持续推送状态变更
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

2.2 Check RPC 方法详解

Check 是一个简单的 Unary RPC(一元 RPC),客户端发送一个 HealthCheckRequest,服务端立即返回当前的 HealthCheckResponse。它适用于定时轮询场景,例如 Kubernetes 的 livenessProbe 和 readinessProbe。

特性 说明
RPC 类型 Unary(一元)
请求参数 service(string):指定要检查的服务名。空字符串表示检查整个服务的整体健康状态。
响应参数 status(ServingStatus):当前服务状态。
典型用途 负载均衡器定期探测后端实例是否存活;Kubernetes 探针判断 Pod 是否就绪。
超时控制 客户端应设置合理的超时(如 1-5 秒),避免阻塞。
错误处理 如果服务端返回 NOT_SERVING 或 RPC 调用失败(如连接断开),客户端应将该实例标记为不健康。
最佳实践: Check 方法应尽可能轻量,避免执行复杂的业务逻辑或数据库查询。它只应检查服务是否能够处理请求,例如验证内部状态机、资源池是否就绪。如果 Check 本身耗时过长,会拖慢整个探测周期。

2.3 Watch RPC 方法详解

Watch 是一个 Server-Streaming RPC(服务端流式 RPC)。客户端发起一次请求后,服务端会持续推送状态变更通知,直到客户端取消调用或连接断开。它适用于需要实时感知状态变化的场景,例如服务网格中的端点发现与流量切换。

特性 说明
RPC 类型 Server-Streaming(服务端流式)
请求参数 service(string):与 Check 相同,指定要监听的服务名。
响应流 服务端持续发送 HealthCheckResponse,每次状态变化时推送最新状态。
初始状态 服务端应在建立流后立即发送当前状态,即使状态未发生变化。
典型用途 Envoy、Linkerd 等 Sidecar 代理实时监听后端服务健康状态,动态更新负载均衡端点。
连接管理 客户端应实现重连逻辑;服务端应在检测到状态变化时立即推送,避免客户端轮询。
注意事项: Watch 方法要求服务端维护长连接。如果服务端频繁重启或网络不稳定,会导致大量连接重建。建议在服务端实现 状态变更去重 机制,避免连续推送相同状态造成客户端不必要的处理开销。

2.4 状态机与转换规则

健康检查协议定义了四种状态,其转换规则如下:

  • UNKNOWN (0):初始状态或无法判断。服务端在启动时通常处于此状态,直到完成初始化。
  • SERVING (1):服务正常。这是唯一允许接收业务请求的状态。
  • NOT_SERVING (2):服务异常。服务端不应接收请求,客户端应立即将流量摘除。
  • SERVICE_UNKNOWN (3):请求的服务名称不存在。通常用于多服务共存的进程,表示客户端请求了一个未注册的服务。
状态转换规则:
  1. 服务启动 → UNKNOWN(初始化中)
  2. 初始化完成 → SERVING(就绪)
  3. 服务过载/依赖故障 → NOT_SERVING(摘除流量)
  4. 故障恢复 → SERVING(重新接入流量)
  5. 服务关闭 → NOT_SERVING(优雅退出)
  6. 请求不存在的服务名 → SERVICE_UNKNOWN
gRPC 健康检查状态机与转换流程 UNKNOWN (初始化中) SERVING (正常服务) NOT_SERVING (异常/摘除) SERVICE_UNKNOWN (服务不存在) 初始化完成 故障/过载 故障恢复 初始化失败 请求未知服务 说明:实线表示正常转换路径,虚线表示异常或边界情况。只有 SERVING 状态允许接收业务流量。

2.5 Check 与 Watch 的对比与选型

在实际部署中,应根据场景选择合适的健康检查方式。下表总结了两种方法的核心差异:

维度 Check (Unary) Watch (Server-Streaming)
通信模式 请求-响应(一次) 请求-流式响应(持续)
实时性 取决于轮询间隔(通常 5-15 秒) 状态变化后立即推送(毫秒级)
网络开销 每次探测产生一次 RPC 调用 建立长连接后仅推送变更,空闲时无流量
服务端压力 高并发轮询时压力较大 需要维护长连接状态,内存开销略高
适用场景 Kubernetes 探针、简单负载均衡 服务网格、实时流量调度、动态端点发现
实现复杂度 低 中(需处理流取消、重连、去重)
选型建议: 对于大多数微服务场景,推荐同时实现 Check 和 Watch。Check 用于外部探针(如 K8s),Watch 用于内部服务网格通信。如果资源受限,优先实现 Check,因为它的实现更简单,且与现有编排系统兼容性更好。

2.6 协议实现要点与常见陷阱

  • 服务名设计: 对于单服务进程,通常使用空字符串 "" 作为服务名。对于多服务进程(如 gRPC Gateway),应为每个子服务注册独立的健康检查端点。
  • Watch 初始状态发送: 服务端必须在 Watch 流建立后立即发送当前状态,即使状态是 UNKNOWN。客户端应等待初始状态后再决定是否路由流量。
  • 状态变更去重: 如果服务状态频繁抖动(例如依赖的外部服务不稳定),服务端应实现去重逻辑,避免连续推送相同状态导致客户端负载升高。
  • 超时与重试: Check 请求应设置超时(建议 1-3 秒)。Watch 流应实现心跳或 keepalive 机制,检测连接是否中断。
  • 优雅关闭: 服务关闭前应主动将状态切换为 NOT_SERVING,并等待一段时间(如 5 秒)再退出,让负载均衡器感知到状态变化并摘除流量。
Check (Unary) vs Watch (Server-Streaming) 通信模式 Check (Unary RPC) 客户端 服务端 Check() SERVING 轮询间隔:5-15 秒 每次探测独立 RPC 适合 K8s 探针 Watch (Server-Streaming RPC) 客户端 服务端 Watch() 初始状态 状态变更推送 状态变更推送 长连接,实时推送 适合服务网格

2.7 小结