但无论就绪探针此时是什么状态,只要存活探针失败了,K8s 就会强制重启容器,而在这个“即将重启”的瞬间,K8s 都不会去动 Endpoints 列表。在这短暂的间隙,依然可能会有流量打进来。这就是为什么Liveness 探针不能用来做业务级别的流量控制,它只负责“杀人(重启)”。
Web 服务器(如 Tomcat、Uvicorn、Nginx)内部是“多线程/多协程”的,探针请求和业务请求用的是不同的线程!
在真实的微服务中,死锁通常只会导致一小部分工作线程卡住,而不是所有的线程瞬间全死。
● Readiness 探针:去查 Redis,碰巧分配到了一个还没卡死的工作线程,线程连上 Redis,返回 200 OK。K8s 认为:“依赖正常,继续给流量”。
● Liveness 探针:它的设计初衷就是为了对付这种“部分线程卡死”的情况。Liveness 探针通常会去检查一个非常轻量级的内部状态(比如检查后台专门负责监控的线程是否还活着,或者检查内存状态),它甚至根本不去连 Redis!它发现:“哎呀,核心处理线程已经卡死 5 分钟了”,于是返回 500 失败。
千万不要把 Liveness 和 Readiness 配置成同一个接口!
很多新手图省事,只写了一个 /health 接口,既检查 DB,又检查内部状态。当发生死锁时,Liveness 失败触发重启;但在重启期间,Readiness 也会跟着失败,导致 Pod 被从 Endpoints 中摘除。如果此时恰好遇到 GC 停顿或启动较慢,就会引发“频繁重启 + 流量中断”的雪崩效应。
正确的做法是严格分离(各司其职):
● Readiness 探针:只检查外部依赖(DB、Redis),决定“接不接流量”。
● Liveness 探针:只做最轻量的纯内存检查(如检查后台线程是否存活),绝对不要连数据库,决定“要不要重启”。
现代 Web 架构中,并发是常态。只要还有哪怕 1 个工作线程是活的,探针请求就能得到响应。而 Liveness 探针的伟大之处,就在于它能穿透“表面上的响应”,直接看穿程序内部的“脑死亡”状态!