一次服务健康检查设计Checklist:探活、依赖、就绪、优雅下线三件套

一次服务健康检查设计Checklist,系统性梳理探活、依赖、就绪、优雅下线四类检查的配置原则与常见陷阱。

问题背景

微服务架构下,单个服务依赖 5-20 个下游,任何一环的启动/停止都可能引发雪崩。健康检查(health check)是容器编排、负载均衡、自动扩缩容的基石,但业界普遍存在「探活只配 liveness、依赖不等就绪就放流量、优雅下线只睡 30 秒」三大误区。一次核心支付服务因探活配置不当导致的 45 分钟全站雪崩,暴露了健康检查设计的系统性缺失。

故障现象

2026 年 7 月 29 日 21:17,支付核心服务发布后:

  1. 新 Pod 进入 Running 后 8 秒即被 liveness 探针杀死,CrashLoopBackOff 循环;
  2. 旧 Pod 停止后 30 秒仍有 12% 请求打到已关闭的 JVM 进程,报 Connection reset by peer;
  3. 依赖的订单服务未就绪,新 Pod 启动即开始接收流量,首查订单接口 100% 超时;
  4. 滚动更新第 3 批时,50% Pod 同时处于 Terminating + 新 Pod Pending,双端同时受损。

最终靠手动 scale 到 0 再 scale up 止血,全站支付能力中断 45 分钟。

排查过程

1. 探活语义错位

1
2
3
4
5
6
7
8
# 错误配置
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
readinessProbe: null   # 完全缺失
  • /health 只检查 JVM 存活,不检查数据库连接池、Redis、Kafka;
  • 真实就绪需要 25-40 秒,探针 5 秒即判定失败;
  • readiness 未配,Pod 一 Running 就进 Endpoints,流量瞬间打满。

2. 依赖就绪检查缺失

支付服务启动日志显示:

1
2
2026-07-29 21:17:22 [main] INFO  OrderServiceClient - Connecting to order-service...
2026-07-29 21:17:22 [main] ERROR OrderServiceClient - Connection refused

启动脚本直接 java -jar,未等下游就绪就暴露 8080。

3. 优雅下线不完整

1
2
3
4
5
# 只配了 sleep 30
lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 30"]

JVM shutdown hook 需要 45 秒完成事务回滚和连接释放,sleep 30 秒即被 SIGKILL,部分请求在途。

4. 探活依赖自身健康

/health 端点硬编码返回 200,不做真实依赖探测,导致「假活」。

解决方案

四类健康检查 Checklist(生产必配)

检查类型 目的 推荐配置 常见陷阱 验证命令
Startup Probe 防止启动慢应用被 liveness 误杀 failureThreshold: 30, periodSeconds: 5(最多 150s) initialDelay 写死 kubectl describe pod 看 Started
Liveness Probe 杀死真正死掉的进程 只检查本地 JVM/进程存活,路径 /livez,不查依赖 查下游导致级联 curl -I http://localhost:8080/livez
Readiness Probe 决定是否接收流量 真实检查所有下游(DB、Redis、Kafka、订单服务) readiness = liveness kubectl get endpoints 观察就绪数
PreStop Hook 优雅下线 sleep 时长 ≥ JVM shutdown hook + 连接释放 + 负载均衡移除时间 固定 30s kubectl logs 看 shutdown hook 完成时间

参考配置(Spring Boot + Kubernetes)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
startupProbe:
  httpGet:
    path: /livez
    port: 8080
  failureThreshold: 30
  periodSeconds: 5

livenessProbe:
  httpGet:
    path: /livez
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /readyz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 60"]

Java 端点实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@GetMapping("/livez")
public ResponseEntity<Void> livez() {
    return ResponseEntity.ok().build(); // 只检查 JVM
}

@GetMapping("/readyz")
public ResponseEntity<Void> readyz() {
    if (orderClient.isHealthy() && redisTemplate.hasConnection() && /* ... */) {
        return ResponseEntity.ok().build();
    }
    return ResponseEntity.status(503).build();
}

依赖就绪保障

启动脚本使用 wait-for-it.sh 或 Sidecar:

1
2
3
4
#!/bin/bash
/wait-for-it.sh order-service:8080 --timeout=120 --strict -- \
/wait-for-it.sh redis:6379 --timeout=60 --strict -- \
java -jar payment-service.jar

优雅下线三件套

  1. preStop sleep 时长 = JVM hook 时间 + 负载均衡健康检查间隔 × 2
  2. terminationGracePeriodSeconds ≥ preStop sleep + 10s
  3. 负载均衡配置(Ingress/Nginx/AWS ALB)健康检查间隔 ≤ 5s,unhealthy 阈值 ≤ 2 次

根因分析

健康检查设计本质是「状态机一致性」问题:

  • Startup/Liveness/Readiness 三种探针语义被混淆为单一 /health
  • 探活本应是「本地存活」,却被做成「依赖存活」,形成级联失败;
  • 优雅下线缺少「负载均衡已摘除 → 连接已释放 → 进程可杀」三阶段确认;
  • 配置漂移:开发环境 liveness = readiness,生产直接复制粘贴。

预防措施

  1. Checklist 强制落地:所有新服务发布前必须通过「健康检查 Checklist」评审,缺少任意一项即阻断。
  2. 探活分层/livez(本地)+ /readyz(依赖)+ /startupz(启动),三端点分离。
  3. 依赖就绪可视化:在 Grafana 增加「服务就绪率」面板,低于 100% 触发告警。
  4. 优雅下线演练:每季度执行一次「滚动更新 + 强制杀进程」演练,记录实际 hook 时间。
  5. 配置即代码审查:CI 中增加 kubectl explain deployment.spec.template.spec.containers.livenessProbe 静态检查,杜绝 readiness=null。

总结

健康检查不是「配一个 /health 就完事」,而是覆盖「启动 → 就绪 → 存活 → 下线」全生命周期的状态机。一次支付服务雪崩的根源是探活语义错位、依赖就绪缺失、优雅下线不足三者叠加。通过四类探针分离 + Checklist 强制落地,可将类似故障概率降低 90% 以上。建议所有核心服务在下一次发布窗口统一按本文 Checklist 改造。

使用 Hugo 构建
主题 StackJimmy 设计