Go微服务健康检查必须区分liveness与readiness:/health/live仅做轻量进程检查,/health/ready需同步校验DB、Redis、gRPC等依赖并设独立超时,失败返回具体错误项,避免雪崩。

Go语言微服务的健康检查不能只返回200就完事——它必须区分进程存活和业务可用,否则Kubernetes会反复重启Pod,Consul会错误剔除节点,Prometheus告警永远滞后。
为什么/health/ready必须校验DB、Redis等依赖
很多团队把/health写成固定返回{"status":"ok"},结果上线后发现:数据库连接池耗尽、Redis超时、下游gRPC服务不可达时,服务仍被标记为“就绪”,流量持续打进来,最终雪崩。
-
/health/ready要真实反映服务能否处理请求:必须同步执行DBPING、RedisCLIENT LIST、关键下游gRPCCheck()调用 - 每个依赖检测需设独立超时(如DB 2s、Redis 500ms),避免单个慢依赖拖垮整个探针
- 失败时不直接返回
503,而应记录具体失败项(如{"db":"timeout","redis":"ok"}),方便定位
如何避免健康检查自身成为性能瓶颈
高频探测(比如Kubernetes默认每10秒一次)若每次都新建HTTP连接、重连DB,会快速吃光文件描述符和连接池。
- DB连接复用:用
sql.DB全局实例,调用db.PingContext(ctx, timeout)而非新建连接 - Redis检测用
client.Ping(ctx).Err(),不要Do(ctx, "PING")再解析响应 - 下游gRPC健康检查走
grpc_health_v1.NewHealthClient(conn).Check(),别手写HTTP请求 - 所有检测加
context.WithTimeout,超时立即cancel,防止goroutine堆积
liveness和readiness必须拆开设计
混淆两者是线上事故高发区。liveness探针失败触发容器重启,readiness失败仅摘流量——但很多人共用一个handler,导致数据库短暂抖动就整机重启。
立即学习“go语言免费学习笔记(深入)”;
-
/health/live只做轻量检查:进程内存未OOM、goroutine数未爆炸、本地缓存未满 -
/health/ready才做全链路依赖检测,且允许短时失败(比如DB连续3次失败才置为unready) - Gin路由里别用
c.JSON(200, ...)硬编码,封装成HealthResponse{Status: "ready", Dependencies: deps}结构体,便于扩展字段 - Kubernetes配置中
initialDelaySeconds至少设为10,给DB连接池、gRPC连接建立留出时间
熔断器不配窗口期等于没开
用sony/gobreaker或自研熔断器时,如果只传Settings{MaxRequests: 1},它默认按100次请求统计错误率——在低QPS服务上可能几小时才触发熔断,完全失去意义。
- 设置
RequestVolumeThreshold为实际QPS×60(比如QPS=5,则设300),确保分钟级统计窗口 -
Interval设为time.Minute,避免滑动窗口导致误判 - 熔断后
OnFailure回调里记录日志+上报指标(如health_check_failure_total{service="user",dep="mysql"}) - 半开状态(
HalfOpen)下只放行1~2个请求试探,成功才恢复,失败继续熔断
真正难的不是写一个能返回JSON的/health接口,而是让每次探测都带着上下文决策:这次失败是网络抖动还是DB宕机?该摘流量还是该重启?该报警还是该静默?这些逻辑藏在超时设置、错误分类、状态聚合的细节里,而不是某一行return c.JSON(200, ...)里。


















