健康检查是怎么回事?K8s 的 liveness、readiness、startup 探针有什么区别?
简化版
**健康检查(Health Check)是「让外部(K8s、负载均衡、注册中心)判断一个服务实例是否健康、能不能接流量」的机制。**在 K8s 里,健康检查通过「探针(Probe)」实现,有三种,各管一件事:① Liveness Probe(存活探针)——判断「容器是不是还活着」,失败就重启容器(用于自愈:进程假死、死锁时重启救活);② Readiness Probe(就绪探针)——判断「容器是不是准备好接流量了」,失败就把它从 Service 的负载均衡里摘掉(不给它转发流量),但不重启(用于「暂时不能服务」:如启动中、依赖没就绪、正在优雅停机);③ Startup Probe(启动探针)——判断「容器是不是启动完成了」,启动慢的应用用它,启动期间不让 liveness/readiness 生效(避免启动慢被 liveness 误杀)。核心区别:liveness 管「活没活」(失败重启)、readiness 管「能不能服务」(失败摘流量)、startup 管「启动完没」(保护慢启动)。Spring Boot 的 Actuator 提供了 /actuator/health(含 liveness/readiness 分组)供探针调用。
详细版
三种探针对比:
| 探针 | 判断什么 | 失败动作 | 典型场景 |
|---|---|---|---|
| Liveness | 容器还活着吗 | 重启容器 | 进程假死、死锁 → 重启自愈 |
| Readiness | 准备好接流量吗 | 摘除流量(不重启) | 启动中、依赖未就绪、优雅停机 |
| Startup | 启动完成了吗 | 重启(保护期内) | 慢启动应用,避免被 liveness 误杀 |
# K8s 探针配置
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
initialDelaySeconds: 30 # 启动后多久开始检查
periodSeconds: 10 # 每 10 秒检查一次
failureThreshold: 3 # 连续失败 3 次才算失败(重启)
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
failureThreshold: 3 # 失败 3 次 → 摘流量
startupProbe:
httpGet: { path: /actuator/health, port: 8080 }
failureThreshold: 30 # 给 30*10=300 秒启动时间
periodSeconds: 10
Spring Boot Actuator 健康端点:
/actuator/health 总健康状态
/actuator/health/liveness 存活(应用没死锁/能响应)
/actuator/health/readiness 就绪(依赖 DB/Redis 等都 OK、能服务)
⚠️ liveness 和 readiness 最容易搞混,记住核心区别:「liveness 失败会重启,readiness 失败只摘流量不重启」——用错会出大问题。典型的错误是「把依赖外部服务的检查放进 liveness」:比如 liveness 探针里检查「数据库连得上吗」,一旦数据库短暂抖动,liveness 失败→K8s 重启容器→重启后数据库还没好→又失败→又重启……陷入无限重启的死循环,而且重启根本救不了「数据库挂了」这种外部问题。正确做法是:liveness 只检查「应用进程本身是否正常」(能不能响应、有没有死锁),不依赖外部;把「依赖是否就绪」放进 readiness(依赖挂了就摘流量、不接请求,但不重启,等依赖恢复了 readiness 自动通过、重新接流量)。这样依赖抖动时,实例是「暂时不服务」而非「反复重启」。
完整版教学
一、为什么需要健康检查
先理解健康检查的价值:
分布式系统里,一个服务有多个实例,实例可能出各种问题:
- 进程假死、死锁(还活着但不响应)
- 启动中(还没准备好)
- 依赖挂了(DB/Redis 连不上,暂时没法服务)
- 正在优雅停机(准备退出,不该再接新请求)
如果没有健康检查:
- 假死的实例还在接流量 → 用户请求打到它、超时/报错
- 启动中的实例被转发请求 → 还没就绪、报错
- 挂了的实例不被发现、不被处理
健康检查的作用:
让外部(K8s、负载均衡、注册中心)知道每个实例的健康状态
→ 不健康的实例:不给它转流量、或重启它
→ 保证流量只打到"健康、能服务"的实例
谁用健康检查:
① K8s:用探针决定重启(liveness)、摘流量(readiness)
② 负载均衡/网关:用健康检查决定转不转流量给某实例
③ 注册中心(Nacos/Eureka):用心跳/健康检查剔除不健康实例
健康检查的价值是「让外部判断实例健康状态、保证流量只打到健康实例」——分布式系统里实例可能假死/死锁(活着但不响应)、启动中、依赖挂了、优雅停机。没有健康检查,不健康的实例还在接流量、用户请求超时报错。健康检查让 K8s、负载均衡、注册中心知道每个实例状态,不健康的不转流量或重启。理解「健康检查让外部判断实例状态(假死/启动中/依赖挂/停机)、保证流量只打到健康实例、K8s/负载均衡/注册中心都用」,就理解了健康检查的价值。
二、Liveness:活没活,失败重启
Liveness Probe(存活探针)——判断「活没活」,失败重启:
Liveness Probe(存活探针):
判断"容器还活着吗、进程本身是否正常"
失败动作:★ 重启容器
用途(自愈):
应用进程"假死"了——还在跑,但不响应(死锁、内存耗尽卡住、
内部状态错乱无法恢复)
→ liveness 检测到"不响应" → 重启容器 → 恢复服务
关键原则:liveness 只检查"应用进程本身",不依赖外部
✓ 检查:应用能不能响应一个简单请求(如 /health/liveness 返回 200)
✗ 不检查:数据库连得上吗、下游服务好不好(这些是外部依赖)
为什么不能依赖外部(重要):
如果 liveness 检查"数据库连通性":
数据库抖动 → liveness 失败 → 重启容器
→ 重启后数据库还没好 → 又失败 → 又重启 → 无限重启死循环
→ 而且重启救不了"数据库挂了"这种外部问题
→ 所以 liveness 只管"我自己活没活",外部问题交给 readiness
配置要点:
initialDelaySeconds:给应用足够的启动时间再开始检查
failureThreshold:连续失败几次才重启(避免偶发抖动误重启)
periodSeconds:检查间隔
Liveness Probe(存活探针)判断「活没活、进程本身是否正常」,失败重启容器(自愈)。用途:应用假死(还跑但不响应,死锁/卡住)→ liveness 检测到不响应 → 重启恢复。关键原则:liveness 只检查应用进程本身、不依赖外部(检查能不能响应简单请求,不检查数据库连通性)。为什么不依赖外部:如果检查数据库连通性,数据库抖动→liveness 失败→重启→数据库还没好→又失败→无限重启死循环(且重启救不了外部问题)。理解「Liveness 判断活没活失败重启(自愈假死)、只检查应用进程本身不依赖外部(否则数据库抖动会无限重启死循环)、外部问题交给 readiness」,就掌握了 Liveness。
三、Readiness:能不能服务,失败摘流量
Readiness Probe(就绪探针)——判断「能不能服务」,失败摘流量:
Readiness Probe(就绪探针):
判断"容器准备好接流量了吗、现在能不能对外服务"
失败动作:★ 从 Service 的负载均衡里摘掉(不给它转流量),但不重启
用途(暂时不能服务时摘流量):
① 启动中——应用还在初始化(加载配置、建连接池、预热缓存)
还没准备好 → readiness 失败 → 不接流量 → 就绪后再接
② 依赖未就绪——DB/Redis/下游服务连不上
→ readiness 失败 → 摘流量(不接请求,避免报错)
→ 依赖恢复 → readiness 通过 → 重新接流量
③ 优雅停机——准备退出时,先 readiness 失败 → 摘流量
→ 不再接新请求,处理完存量后退出(配合优雅停机)
④ 过载保护——太忙时 readiness 失败 → 暂时不接新流量
关键:readiness 失败"只摘流量、不重启"
→ 实例还活着,只是"暂时不对外服务"
→ 等它 ready 了(依赖恢复、初始化完成),自动重新接流量
和 liveness 的核心区别:
liveness 失败 → 重启(用于"死了要救活")
readiness 失败 → 摘流量(用于"暂时不能服务、但没死")
→ readiness 可以依赖外部(依赖挂了就摘流量、不接请求)
**Readiness Probe(就绪探针)**判断「能不能服务、准备好接流量了吗」,失败就从 Service 负载均衡摘掉(不转流量)但不重启。用途:① 启动中(还在初始化,摘流量、就绪后再接)、② 依赖未就绪(DB/Redis 连不上,摘流量避免报错,依赖恢复自动重新接)、③ 优雅停机(先 readiness 失败摘流量、处理完存量再退出)、④ 过载保护。关键:readiness 失败只摘流量不重启(实例还活着、暂时不服务、ready 了自动重新接)。和 liveness 核心区别:liveness 失败重启(死了救活)、readiness 失败摘流量(暂时不能服务但没死,可依赖外部)。理解「Readiness 判断能不能服务失败摘流量不重启、用途:启动中/依赖未就绪/优雅停机/过载保护、只摘流量不重启(ready 了自动重新接)、和 liveness 区别:失败摘流量 vs 重启、可依赖外部」,就掌握了 Readiness。
四、Startup:保护慢启动
Startup Probe(启动探针)——保护启动慢的应用:
Startup Probe(启动探针,K8s 1.16+):
判断"容器启动完成了吗"
作用:保护"启动慢"的应用,避免被 liveness 误杀
问题(没有 startup 探针时):
应用启动很慢(如 2 分钟,Spring Boot 大应用、要预热)
liveness 探针在应用启动期间就开始检查:
应用还没起来 → liveness 失败 → 重启
→ 重启后又要 2 分钟 → 又被误杀 → 永远起不来
虽然 initialDelaySeconds 能延迟 liveness,但很难精确设置
(设短了误杀、设长了故障发现慢)
startup 探针的解决:
startup 探针先检查"启动完成没"
★ startup 探针成功之前,liveness 和 readiness 都不生效
→ 给应用充足的启动时间(failureThreshold × periodSeconds)
→ 启动完成(startup 通过)后,liveness/readiness 才开始工作
好处:
① 启动期间被 startup 保护,不会被 liveness 误杀
② 启动完后,liveness 立即以正常频率工作(快速发现故障)
→ 解决"慢启动"和"快速故障发现"的矛盾
配置:
startupProbe failureThreshold: 30, periodSeconds: 10
→ 给 300 秒启动时间;启动完成后 liveness 接管
Startup Probe(启动探针,K8s 1.16+)判断「启动完成了吗」,作用是保护启动慢的应用、避免被 liveness 误杀。问题:应用启动慢(2 分钟),liveness 在启动期间就检查→还没起来→失败→重启→又要 2 分钟→永远起不来(initialDelaySeconds 难精确设置)。startup 探针解决:startup 成功之前 liveness/readiness 都不生效(给充足启动时间),启动完成后 liveness/readiness 才工作。好处:启动期间被保护不被误杀 + 启动完后 liveness 正常频率快速发现故障(解决慢启动和快速故障发现的矛盾)。理解「Startup 判断启动完成没、保护慢启动避免被 liveness 误杀、startup 成功前 liveness/readiness 不生效(给充足启动时间)、启动完后 liveness 接管、解决慢启动和快速故障发现矛盾」,就掌握了 Startup。
五、探针的检查方式
三种探针都支持几种检查方式:
探针的检查方式(三种探针通用):
① httpGet:发 HTTP 请求,看返回码
httpGet: { path: /actuator/health/liveness, port: 8080 }
→ 返回 2xx/3xx 算成功,否则失败
→ 最常用(配合应用的健康端点)
② tcpSocket:尝试建立 TCP 连接
tcpSocket: { port: 8080 }
→ 能连上算成功
→ 适合非 HTTP 服务
③ exec:在容器里执行命令,看退出码
exec: { command: ["cat", "/tmp/healthy"] }
→ 退出码 0 算成功
→ 灵活,能执行自定义检查脚本
④ grpc:gRPC 健康检查(较新)
关键配置参数:
initialDelaySeconds:容器启动后多久开始探测
periodSeconds:探测间隔
timeoutSeconds:单次探测的超时
successThreshold:连续成功几次算成功(readiness 常用)
failureThreshold:连续失败几次算失败
→ 这些参数控制探针的灵敏度(避免偶发抖动误判)
失败阈值的意义:
failureThreshold=3 → 连续 3 次失败才算真失败
→ 避免单次偶发抖动就触发重启/摘流量
三种探针的检查方式:① httpGet(发 HTTP 请求看返回码,2xx/3xx 成功,最常用配合健康端点)、② tcpSocket(尝试 TCP 连接,适合非 HTTP 服务)、③ exec(容器内执行命令看退出码,灵活自定义)、④ grpc。关键参数:initialDelaySeconds(启动后多久开始探测)、periodSeconds(间隔)、timeoutSeconds(超时)、failureThreshold(连续失败几次算失败,避免偶发抖动误判)。理解「探针检查方式:httpGet(HTTP 返回码,最常用)/tcpSocket(TCP 连接)/exec(命令退出码)/grpc、参数 initialDelaySeconds/periodSeconds/failureThreshold(连续失败几次避免偶发抖动误判)」,就掌握了探针的检查方式。
六、Spring Boot 集成与实践
Spring Boot 怎么提供健康检查,以及实践建议:
Spring Boot Actuator 的健康端点:
/actuator/health 总健康状态(含各组件)
/actuator/health/liveness 存活探针组(应用能响应)
/actuator/health/readiness 就绪探针组(依赖 DB/Redis 等 OK)
开启(application.yml):
management.endpoint.health.probes.enabled=true
→ 暴露 liveness/readiness 分组端点
Spring Boot 会自动集成 K8s 探针(检测到在 K8s 里运行时)
自定义健康指示器(HealthIndicator):
实现 HealthIndicator,检查自定义依赖(如某个下游服务)
→ 纳入 /actuator/health
实践建议:
① liveness 只检查应用本身(能响应即可),别依赖外部
(否则外部抖动导致无限重启)
② readiness 检查依赖(DB/Redis/下游)——依赖挂了摘流量
③ 慢启动应用用 startup 探针(避免被 liveness 误杀)
④ failureThreshold 别设太小(避免偶发抖动误判)
⑤ initialDelaySeconds 给足启动时间(或用 startup 探针)
⑥ 优雅停机配合 readiness(停机前先 readiness 失败摘流量)
一句话:
liveness 管"活没活"(失败重启、只查自己)、
readiness 管"能不能服务"(失败摘流量、可查依赖)、
startup 管"启动完没"(保护慢启动)
Spring Boot 用 Actuator 健康端点提供健康检查:/actuator/health(总)、/actuator/health/liveness(存活组)、/actuator/health/readiness(就绪组,检查依赖),开启 management.endpoint.health.probes.enabled=true,可自定义 HealthIndicator。实践:① liveness 只检查应用本身别依赖外部、② readiness 检查依赖、③ 慢启动用 startup、④ failureThreshold 别太小、⑤ initialDelaySeconds 给足启动时间、⑥ 优雅停机配合 readiness。理解「Spring Boot Actuator 健康端点(/health/liveness、/health/readiness 检查依赖)、probes.enabled 开启、自定义 HealthIndicator;实践:liveness 只查自己/readiness 查依赖/慢启动用 startup/failureThreshold 别太小/优雅停机配 readiness」,就掌握了集成和实践。
记忆钩子:「K8s 三探针:①Liveness 存活探针(判断活没活、失败重启容器、自愈假死死锁、★只检查应用本身不依赖外部否则数据库抖动会无限重启死循环)②Readiness 就绪探针(判断能不能服务、失败从 Service 摘流量但不重启、用于启动中/依赖未就绪/优雅停机/过载、可依赖外部、ready 了自动重新接)③Startup 启动探针(判断启动完没、保护慢启动避免被 liveness 误杀、startup 成功前 liveness/readiness 不生效);核心区别:liveness 失败重启 vs readiness 失败摘流量 vs startup 保护慢启动;检查方式 httpGet/tcpSocket/exec;Spring Boot Actuator /health/liveness /health/readiness;liveness 只查自己、readiness 查依赖」。
七、常见误区与追问
- 误区:liveness 和 readiness 都是「检查健康」,差不多。 核心区别是失败动作——liveness 失败会重启容器(用于「死了救活」,如假死死锁);readiness 失败只把实例从负载均衡摘掉、不转流量、但不重启(用于「暂时不能服务但没死」,如启动中、依赖挂了);用错会出大问题。
- 误区:liveness 探针里检查数据库连通性。 大忌——如果 liveness 依赖外部(检查数据库),数据库抖动会导致 liveness 失败→重启容器→数据库还没好→又失败→无限重启死循环,而且重启救不了「数据库挂了」这种外部问题;liveness 只能检查应用进程本身(能否响应),依赖检查放 readiness。
- 误区:启动慢的应用只能调大 liveness 的 initialDelaySeconds。 initialDelaySeconds 很难精确设置(设短了启动期间被误杀、设长了故障发现慢);更好的是用 startup 探针——它在启动完成前保护应用(liveness/readiness 不生效),启动完成后 liveness 立即以正常频率工作,同时解决「慢启动」和「快速故障发现」。
- 误区:readiness 失败会重启容器。 不会——readiness 失败只是把实例从 Service 的负载均衡里摘掉(不给它转流量),容器还在正常运行;等它 ready 了(依赖恢复、初始化完成),会自动重新加入负载均衡接流量;只有 liveness 失败才重启。
- 追问:liveness 和 readiness 探针有什么本质区别? liveness 判断「容器是否还活着」,失败会重启容器(用于自愈假死、死锁)、只该检查应用进程本身不依赖外部;readiness 判断「容器是否准备好接流量」,失败会把它从负载均衡摘掉(不转流量)但不重启(用于启动中、依赖未就绪、优雅停机等「暂时不能服务但没死」的情况)、可以检查外部依赖;一个管「活没活、失败重启」,一个管「能不能服务、失败摘流量」。
- 追问:为什么需要 startup 探针? 保护启动慢的应用——如果没有 startup 探针,liveness 会在应用启动期间就检查,应用还没起来就 liveness 失败→重启→又要重新启动→陷入被反复误杀起不来的循环;startup 探针在应用启动完成前生效(liveness/readiness 此时不工作),给应用充足的启动时间,启动完成后 liveness/readiness 才接管;这样既保护慢启动、又能在启动后快速发现故障。
- 追问:优雅停机怎么配合 readiness 探针? 应用收到停机信号时,先让 readiness 探针失败→K8s 把它从负载均衡摘掉(不再转发新流量给它)→应用处理完存量请求→再真正退出;这样停机过程中新流量不会打到正在停止的实例上(避免请求失败),实现优雅停机;如果只靠优雅停机不配合 readiness,摘流量和应用停止之间可能有时间差导致部分请求失败。
八、加强记忆
健康检查让外部(K8s、负载均衡、注册中心)判断实例是否健康、能否接流量。K8s 用三种探针:① Liveness Probe(存活探针)——判断「活没活、进程本身是否正常」,失败重启容器(自愈假死/死锁),关键:只检查应用本身、不依赖外部(否则数据库抖动会导致无限重启死循环);② Readiness Probe(就绪探针)——判断「能不能服务、准备好接流量了吗」,失败就从 Service 负载均衡摘掉(不转流量)但不重启,用于启动中、依赖未就绪、优雅停机、过载保护(可依赖外部,依赖挂了摘流量、恢复了自动重新接);③ Startup Probe(启动探针)——判断「启动完成了吗」,保护慢启动应用避免被 liveness 误杀(startup 成功前 liveness/readiness 不生效,给充足启动时间)。核心区别:liveness 失败重启(死了救活)、readiness 失败摘流量(暂时不服务但没死)、startup 保护慢启动。检查方式:httpGet(最常用)/tcpSocket/exec。Spring Boot Actuator 提供 /actuator/health/liveness(存活)和 /actuator/health/readiness(就绪,检查依赖)供探针调用。实践:liveness 只查自己、readiness 查依赖、慢启动用 startup、failureThreshold 别太小、优雅停机配合 readiness。一句话「K8s 三探针:Liveness(活没活/失败重启/只查自己不依赖外部否则无限重启)、Readiness(能不能服务/失败摘流量不重启/可查依赖/用于启动中优雅停机)、Startup(启动完没/保护慢启动避免被 liveness 误杀);核心区别 liveness 重启 vs readiness 摘流量 vs startup 保护慢启动;Spring Boot Actuator /health/liveness /health/readiness」。