必须在server.Serve()前注册grpc_health_v1.HealthServer,否则registry冻结导致静默失败、探针始终返回SERVICE_UNKNOWN;需用SetServingStatus动态联动DB等依赖状态,并与HTTP /livez、/readyz端点语义分离、协同兜底。

必须在 gRPC Server 启动前注册 grpc_health_v1.HealthServer,否则探针始终返回 SERVICE_UNKNOWN;HTTP 健康端点(如 /readyz)和 gRPC 健康服务需并存且语义分离,不能互相替代。
gRPC Health Server 注册时机为什么不能晚于 server.Serve()
gRPC 的 service registry 在调用 server.Serve() 后冻结,后续调用 grpc_health_v1.RegisterHealthServer() 会静默失败——不 panic、不报错,但所有 Check() 请求都返回 SERVICE_UNKNOWN。Kubernetes 的 grpc-health-probe 工具据此判定服务未就绪,反复重启 Pod。
- 注册必须在
grpc.NewServer()创建后、server.Serve()调用前完成 - 推荐封装进初始化函数,例如
NewGRPCServer()内统一初始化healthServer := health.NewServer()并调用SetServingStatus("", ...) - 若启用了 mTLS 或自定义拦截器,
HealthServer同样受其约束:探针请求必须携带有效证书,否则连接被直接拒绝
如何让 Check() 反映真实业务状态而非永远 SERVING
默认 health.NewServer() 返回的是 stub 实现,对任意 Service 名都返回 SERVING。要联动业务,必须手动管理状态变量,并在关键路径(如 DB 连接断开、下游 gRPC 连接失效)中调用 SetServingStatus() 更新。
- 用
atomic.Value或带锁map[string]grpc_health_v1.HealthCheckResponse_ServingStatus存储各服务状态 - 主业务启动成功后,调用
healthServer.SetServingStatus("my-service", grpc_health_v1.HealthCheckResponse_SERVING) - 监听 DB 连接池健康状态(如
db.PingContext()失败),立即设为NOT_SERVING;恢复后再切回SERVING - 空字符串
""表示整个 server 状态,建议与具体服务名状态保持一致,避免歧义
Kubernetes 中 grpc-health-probe 配置要点
该工具是 exec 类 probe,本质是容器内执行二进制命令调用 gRPC Check() 方法。它不走 HTTP,因此与 /healthz 端点完全无关,也不能替代后者。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Dockerfile 中静态编译并打入二进制:
RUN wget -qO /bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.2.0/grpc_health_probe-linux-amd64 && chmod +x /bin/grpc_health_probe - Kubernetes YAML 中配置 readiness/liveness 均可,但注意:liveness 不应依赖
Check()结果,因它反映的是业务就绪性,不是进程存活性 - probe 命令示例:
command: ["/bin/grpc_health_probe", "-addr=:50051", "-service=my-service"] - 务必加
-connect-timeout=1s和-rpc-timeout=2s,避免因网络抖动或服务卡顿导致误判
HTTP 与 gRPC 健康端点为何必须共存且分工明确
/livez 和 /readyz 是给 Kubernetes HTTP 探针用的,而 grpc_health_v1.HealthServer 是给 grpc-health-probe 或其他 gRPC 客户端调用的。两者协议层、用途、超时逻辑完全不同,混用会导致滚动更新失败或流量误入。
-
/livez只检查进程级快照:runtime.NumGoroutine()、http.Server 是否 Accept、关键 channel 是否阻塞;绝不碰 DB/Redis/gRPC -
/readyz必须同步探测依赖,但要用缓存结果(后台time.Ticker每 5 秒刷新一次),handler 仅读快照,避免每次请求建新连接 -
grpc_health_v1.Check()应只用于服务网格或客户端负载均衡场景,不能作为 liveness 判据——它的UNKNOWN状态常表示服务未注册,而非业务异常 - 两个体系的状态变量最好解耦:一个控制 HTTP handler 返回码,一个控制
healthServer.SetServingStatus(),避免单点故障传导
最易忽略的是状态更新的原子性和时效差:业务检测到 DB 断连后调用 SetServingStatus(),但 grpc-health-probe 默认每 10 秒才查一次,中间窗口期仍可能转发流量;此时必须配合 /readyz 的快速 HTTP 探针做兜底摘流。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










