Spring Boot自定义健康指标短时间被多次调用,日志泛滥求助
问题描述
我创建了一个自定义健康指标用于检查外部服务的可用性,原本预期它每15分钟由任务执行一次,但实际日志中发现methodToCheckAvailability在同一秒内被多次调用,导致日志泛滥。我猜测这是因为Kubernetes多次调用健康检查端点验证服务状态导致的。
自定义健康指标代码
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.Status; import org.springframework.stereotype.Component; import java.util.Optional; import java.util.function.Consumer; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.function.Predicate.not; import static java.util.stream.Collectors.toList; @Slf4j @Component @RequiredArgsConstructor public class BananaHealthIndicator implements HealthIndicator { public static final Status PARTIAL_DOWN = new Status("PARTIAL_DOWN"); @Override public Health health() { var builder = Health.up(); var someValues = SomeService.getMap(); doHealthCheck(builder, value, "ExternalService", ExternalService::methodToCheckAvailability); return builder.build(); } private void doHealthCheck(Health.Builder builder, String value, String component, Consumer<String> healthCheck) { try { healthCheck.accept(value); } catch (Exception e) { log.atError().setCause(e) .log("Health check failed for {}", component); builder.status(PARTIAL_DOWN) .withDetail(value, component + ": " + ExceptionUtils.getMessage(e)); } } }
配置文件(application.yml)
management: endpoint: health: status: http-mapping: partial_down: 500 show-details: when_authorized probes: enabled: true group: banana: include: banana show-details: when_authorized show-components: when_authorized status: order: "partial_down,unknown,up"
/actuator/health 响应结果
{ "status": "UP", "components": { "clientConfigServer": { "status": "UP", "details": { "propertySources": [ "configClient" ] } }, "banana": { "status": "UP" }, "livenessState": { "status": "UP" }, "ping": { "status": "UP" }, "readinessState": { "status": "UP" }, "redis": { "status": "UP", "details": { "cluster_size": 2, "slots_up": 16384, "slots_fail": 0 } }, "refreshScope": { "status": "UP" } }, "groups": [ "banana", "liveness", "readiness" ] }
解决方案
1. 缓存健康检查结果
既然外部服务状态不会频繁变化,给健康检查结果添加缓存,避免每次调用端点都执行真实检查:
@Slf4j @Component @RequiredArgsConstructor public class BananaHealthIndicator implements HealthIndicator { public static final Status PARTIAL_DOWN = new Status("PARTIAL_DOWN"); private Health cachedHealth; private long lastCheckTime = 0; // 缓存有效期15分钟 private static final long CACHE_TTL = 15 * 60 * 1000; @Override public Health health() { long now = System.currentTimeMillis(); // 缓存未过期则直接返回 if (cachedHealth != null && (now - lastCheckTime) < CACHE_TTL) { return cachedHealth; } // 执行真实检查并更新缓存 var builder = Health.up(); var someValues = SomeService.getMap(); doHealthCheck(builder, value, "ExternalService", ExternalService::methodToCheckAvailability); cachedHealth = builder.build(); lastCheckTime = now; return cachedHealth; } // 原doHealthCheck方法保持不变 private void doHealthCheck(Health.Builder builder, String value, String component, Consumer<String> healthCheck) { try { healthCheck.accept(value); } catch (Exception e) { log.atError().setCause(e) .log("Health check failed for {}", component); builder.status(PARTIAL_DOWN) .withDetail(value, component + ": " + ExceptionUtils.getMessage(e)); } } }
2. 调整Kubernetes探针配置
修改K8s存活/就绪探针的调用频率,且让探针只调用轻量的健康检查端点(避免触发外部服务检查):
# Kubernetes Deployment配置示例 livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 5 periodSeconds: 15 timeoutSeconds: 5
3. 拆分健康检查逻辑
把外部服务定期检查和K8s探针检查完全分开:
- K8s探针只检查服务自身存活/就绪状态(如进程、内存)
- 单独用定时任务每15分钟执行外部服务检查,自定义健康指标直接返回预检查结果
定时任务示例:
@Component @Slf4j public class ExternalServiceHealthChecker { private Health cachedExternalHealth = Health.up().build(); private static final String COMPONENT_NAME = "ExternalService"; @Scheduled(fixedRate = 15 * 60 * 1000) public void checkExternalServiceHealth() { var builder = Health.up(); var someValues = SomeService.getMap(); doHealthCheck(builder, value, COMPONENT_NAME, ExternalService::methodToCheckAvailability); cachedExternalHealth = builder.build(); } private void doHealthCheck(Health.Builder builder, String value, String component, Consumer<String> healthCheck) { try { healthCheck.accept(value); } catch (Exception e) { log.atError().setCause(e) .log("Health check failed for {}", component); builder.status(BananaHealthIndicator.PARTIAL_DOWN) .withDetail(value, component + ": " + ExceptionUtils.getMessage(e)); } } public Health getCachedExternalHealth() { return cachedExternalHealth; } }
修改自定义健康指标:
@Slf4j @Component @RequiredArgsConstructor public class BananaHealthIndicator implements HealthIndicator { private final ExternalServiceHealthChecker healthChecker; @Override public Health health() { return healthChecker.getCachedExternalHealth(); } }
内容的提问来源于stack exchange,提问作者Mokz
相关产品推荐
相关产品推荐

