/healthz 和 /livez 必须分开:/livez 仅检查进程存活(如 goroutine 数、server accept 状态),无外部依赖;/healthz 检查 DB/Redis/Kafka 连通性,各依赖带独立超时。

后台任务(如 Kafka 消费者、定时 Job、Redis Stream 处理器)在 Kubernetes 中不能只靠进程存活判断健康状态——exec 探针查 ps 或 kill -0 会漏掉 goroutine 卡死、连接断开未重连、消息积压等真实故障。必须用主动暴露的 HTTP 端点,配合语义分离的探针配置。
为什么 /healthz 和 /livez 必须分开写
把数据库 db.PingContext 塞进 liveness 探针是线上最常见雪崩源头:DB 瞬时抖动 → livenessProbe 失败 → Pod 被杀 → 新 Pod 启动又连 DB → 连接风暴 → DB 更卡 → 循环重启。Kubernetes 不区分“业务不可用”和“进程已死”,它只认返回码。
-
/livez只检查 Go 进程是否还在 accept 连接、主 goroutine 是否 panic 过、runtime.NumGoroutine()是否突增(比如 > 5000),不碰任何外部依赖 -
/healthz或/readyz才检查 DB/Redis/Kafka 连通性,但每个依赖调用都带独立context.WithTimeout(ctx, 2*time.Second) - 两个 handler 必须不同路径、不同逻辑,Kubernetes 的
livenessProbe和readinessProbe分别指向它们
Go 代码里怎么安全暴露 /livez
轻量、无副作用、不阻塞。重点不是“能连上什么”,而是“我是不是还活着”。不要在这里做任何网络 I/O。
- 用原子变量记录主循环是否 panic 过:
var liveStatus int32 = 1,panic 时设为 0 - 检查
http.Server是否仍在Accept:if srv == nil || srv.CloseNotify() == nil { http.Error(w, "server down", http.StatusInternalServerError); return } - 检查 goroutine 数是否异常:
if n := runtime.NumGoroutine(); n > 5000 { http.Error(w, "too many goroutines", http.StatusServiceUnavailable); return } - 响应体保持极简:
w.WriteHeader(http.StatusOK); w.Write([]byte("ok")),避免 JSON 编码开销
Kubernetes 探针参数怎么配才不误杀
后台任务启动慢、依赖恢复慢,硬套默认值(initialDelaySeconds: 5)必然失败。所有参数必须按实际冷启动时间和依赖容忍度推导。
-
initialDelaySeconds:Go 后台服务常需加载配置、初始化 Kafka client、重放本地 checkpoint,至少设30(liveness)和20(readiness) -
periodSeconds和failureThreshold要匹配:若 Redis 抖动持续约 3 秒,设periodSeconds: 10+failureThreshold: 3,避免单次失败就摘流量或重启 -
timeoutSeconds必须比 handler 内部超时短 1 秒:比如/readyz里 DB Ping 设了2s超时,K8s 探针timeoutSeconds就只能设1,否则 kubelet 等不到响应就直接判失败 - 绝对不要让
livenessProbe和readinessProbe共用同一个 handler —— 它们语义冲突,不是偷懒省代码的事
定时巡检状态变量怎么防泄漏和竞争
后台任务常需要每分钟检查一次 Kafka 消费延迟或队列积压,但如果用 time.Ticker 启动后没管 shutdown,热更新或 SIGTERM 时旧 goroutine 还在往共享变量写数据,/healthz 返回的永远是过期状态。
- 启动时用
ctx, cancel := context.WithCancel(context.Background())包裹 ticker 循环,收到SIGTERM时调cancel() - 所有状态字段(如
kafkaLag、redisHealthy)必须用atomic.StoreInt64或sync.RWMutex保护,禁止裸赋值 - HTTP handler 里只读状态,不做实时探测:每次请求都调
db.PingContext会打爆下游,且 K8s 默认 10 秒调一次,你扛不住脉冲 - 缓存有效期要明确:比如依赖状态缓存 30 秒,用
atomic.LoadInt64(&lastCheckTime)+time.Since判断是否过期
长周期任务的健康检查最难的不是写代码,而是定义清楚“什么算坏”——是连接断了?还是消费停了?还是积压超过阈值?这些判断逻辑一旦写进探针,就必须可预测、可测试、可降级。别让一个健康端点成为整个服务的单点故障源。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











