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):请求的服务名称不存在。通常用于多服务共存的进程,表示客户端请求了一个未注册的服务。
状态转换规则:
- 服务启动 → UNKNOWN(初始化中)
- 初始化完成 → SERVING(就绪)
- 服务过载/依赖故障 → NOT_SERVING(摘除流量)
- 故障恢复 → SERVING(重新接入流量)
- 服务关闭 → NOT_SERVING(优雅退出)
- 请求不存在的服务名 → SERVICE_UNKNOWN
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 秒)再退出,让负载均衡器感知到状态变化并摘除流量。