You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:34:52