健康检查模式:让系统自己告诉你它还好吗
说实话,我做了十几年分布式系统,最怕的就是半夜被电话吵醒——「服务挂了!」。你火急火燎地登录服务器,发现进程还在,但就是不给响应。这种「僵尸服务」比直接挂掉更可怕。后来我学乖了,给每个服务都装上健康检查,说白了就是让系统自己告诉你:「我还活着,而且能干活」。
健康检查模式,是微服务和容器化时代的必备技能。Kubernetes 用它来决定要不要重启你的 Pod,负载均衡器用它来决定要不要把流量打过来。今天我们就把它彻底讲透。
三个核心概念:健康端点、存活探针、就绪探针
这三个东西经常被搞混,我刚开始也分不清。咱们一个一个说。
健康端点(Health Endpoint)
就是一个 HTTP 接口,比如 /health。你访问它,它返回一个 JSON,告诉你服务状态。最简单的实现就是返回 {"status": "UP"}。但实际项目中,我习惯让它检查更多东西——数据库能不能连上、Redis 通不通、下游服务是否可达。
核心原则:健康端点应该轻量、快速。别在里面做复杂计算,否则健康检查本身就成了性能瓶颈。
存活探针(Liveness Probe)
这个探针问的是:「进程还活着吗?」如果挂了,Kubernetes 会重启 Pod。我遇到过一种情况:服务因为死锁导致线程全部阻塞,进程还在,但啥也干不了。存活探针就能发现这种问题。
就绪探针(Readiness Probe)
这个探针问的是:「准备好接收流量了吗?」启动时可能需要加载数据、预热缓存,这时候虽然进程活着,但还不能对外服务。就绪探针返回失败,负载均衡器就不会把流量打过来。
嗯,这里要注意:存活探针和就绪探针的失败处理方式完全不同。一个重启,一个只是摘掉流量。千万别搞混。
Spring Boot Actuator:开箱即用的健康检查
Spring Boot 提供了 Actuator 模块,内置了健康检查功能。我个人觉得这是 Spring Boot 最实用的模块之一,没有它,你就要自己写一堆重复代码。
快速集成
加个依赖就行:
// Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后在 application.yml 里配置:
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always
启动后访问 /actuator/health,你会看到类似这样的响应:
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963170816,
"free": 327680000000,
"threshold": 10485760
}
},
"ping": {
"status": "UP"
}
}
}
你看,它自动检查了数据库连接、磁盘空间。如果你用了 Redis、Elasticsearch 等,它也会自动检测。
自定义健康指示器
我在项目中遇到过需要检查一个第三方支付接口是否可达的情况。Actuator 允许你自定义健康指示器:
@Component
public class PaymentServiceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
try {
// 模拟检查支付服务
boolean isReachable = checkPaymentService();
if (isReachable) {
return Health.up()
.withDetail("paymentService", "available")
.build();
} else {
return Health.down()
.withDetail("paymentService", "unreachable")
.build();
}
} catch (Exception e) {
return Health.down(e)
.withDetail("paymentService", "error")
.build();
}
}
private boolean checkPaymentService() {
// 实际项目中这里会发一个轻量级的请求
return true;
}
}
这样,/actuator/health 返回的结果里就会多一个 paymentService 的状态。
Kubernetes 中的配置示例
有了健康端点,Kubernetes 的配置就很简单了:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: my-app
image: my-app:latest
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
注意这里我用了两个不同的路径。Spring Boot 2.3+ 支持将存活和就绪探针分开:
management:
endpoint:
health:
probes:
enabled: true
group:
liveness:
include: livenessState
readiness:
include: readinessState,db
这样 /actuator/health/liveness 只检查进程是否存活,/actuator/health/readiness 会额外检查数据库是否就绪。
避坑指南
我曾经踩过的坑:
- 把健康检查做得太重。有一次我在健康端点里做了全量数据校验,结果每次检查耗时 5 秒,导致 Kubernetes 频繁重启 Pod。记住:健康检查要轻量。
- 存活探针和就绪探针用同一个端点。如果数据库挂了,存活探针也失败,Pod 会被重启。但有时候你只是想暂时摘掉流量,等数据库恢复。所以存活探针只检查进程状态,就绪探针才检查依赖。
- 忘记设置
initialDelaySeconds。服务启动需要时间,如果探针在启动完成前就开始检查,会导致反复重启。
知识体系总览
下面这张图总结了健康检查模式的核心逻辑:
Java 原生实现(不依赖 Spring Boot)
如果你不用 Spring Boot,自己实现也不复杂。我习惯用 Java 的 HttpServer 快速搭一个:
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class HealthCheckServer {
private static boolean isHealthy = true;
private static boolean isReady = false;
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
// 健康端点
server.createContext("/health", exchange -> {
String response = "{\"status\":\"" + (isHealthy ? "UP" : "DOWN") + "\"}";
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
});
// 存活探针
server.createContext("/liveness", exchange -> {
String response = isHealthy ? "OK" : "FAIL";
int code = isHealthy ? 200 : 503;
exchange.sendResponseHeaders(code, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
});
// 就绪探针
server.createContext("/readiness", exchange -> {
String response = isReady ? "OK" : "NOT_READY";
int code = isReady ? 200 : 503;
exchange.sendResponseHeaders(code, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
});
server.setExecutor(Executors.newFixedThreadPool(4));
server.start();
System.out.println("Health check server started on port 8080");
}
}
这个例子虽然简单,但核心逻辑都在了。实际项目中,你会在 isHealthy 和 isReady 背后做真正的检查——数据库连接池状态、队列积压情况、缓存是否预热完成等等。
C++ 实现思路
C++ 里实现健康检查,我一般用 libmicrohttpd 或者 cpp-httplib。这里用 cpp-httplib 演示:
#include <httplib.h>
#include <iostream>
#include <atomic>
std::atomic<bool> isHealthy{true};
std::atomic<bool> isReady{false};
int main() {
httplib::Server svr;
// 健康端点
svr.Get("/health", [](const httplib::Request& req, httplib::Response& res) {
res.set_content(
"{\"status\":\"" + std::string(isHealthy ? "UP" : "DOWN") + "\"}",
"application/json"
);
});
// 存活探针
svr.Get("/liveness", [](const httplib::Request& req, httplib::Response& res) {
if (isHealthy) {
res.set_content("OK", "text/plain");
} else {
res.status = 503;
res.set_content("FAIL", "text/plain");
}
});
// 就绪探针
svr.Get("/readiness", [](const httplib::Request& req, httplib::Response& res) {
if (isReady) {
res.set_content("OK", "text/plain");
} else {
res.status = 503;
res.set_content("NOT_READY", "text/plain");
}
});
std::cout << "Health check server started on port 8080" << std::endl;
svr.listen("0.0.0.0", 8080);
return 0;
}
C++ 版本要注意线程安全,std::atomic 在这里就派上用场了。我在一个高并发网关项目里就用过这种方案,性能完全没问题。
总结一下
健康检查模式说白了就三件事:告诉别人你还活着、告诉别人你准备好了、告诉别人你具体哪里有问题。Spring Boot Actuator 帮你做了 80% 的工作,剩下的 20% 就是根据业务自定义检查逻辑。
我个人建议,不管项目大小,都加上健康检查。它就像汽车的仪表盘,平时你可能不看,但出问题的时候,它是你第一个求助的地方。
小技巧:健康检查的日志级别可以设高一点,比如 WARN 或 ERROR。否则每次 Kubernetes 几秒就扫一次,日志会刷屏。我曾经就被这个坑过,一天产生了 10GB 的日志。
公众号:蓝海资料掘金营,微信deep3321