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

如何实现整合多个WebClient健康信息的响应式健康指示器?

问题:实现ReactiveHealthIndicator检查多服务健康状态的格式问题

我正尝试实现ReactiveHealthIndicator来检查多个目标服务的健康状态,但对应服务的JSON无法正常解析。我知道返回的Mono<Health>里包含Mono<Map>,但用block()去除Mono包装会报错(响应式调用里不允许用block())。我试过用map()把值填充到另一个Map里,但没有值能传进去,而且调用者也没订阅。因为用的是Spring Boot Actuator,没法强制调用者订阅,所以想请教怎么实现符合预期格式的健康检查响应。

期望响应

"reactiveTarget": {
    "status": "UP",
    "target1": {
        "status": "UP"
    },
    "target2": {
        "status": "UP"
    }
}

当前实现响应

"reactiveTarget": {
    "status": "UP",
    "details": {
        "target1": {
            "scanAvailable": true
        },
        "holdingsWebClient": {
            "target2": true
        }
    }
}

当前使用代码

@Component
@Slf4j
public class ReactiveTargetHealthIndicator implements ReactiveHealthIndicator {

    private final ApplicationContext context;

    private String overallStatus="UP";

    public ReactiveTargetHealthIndicator(@NonNull ApplicationContext context) {
        this.context = context;
    }

    @Override
    public Mono<Health> health() {
        return checkTargetServiceHealth().onErrorResume(
                ex -> Mono.just(new Health.Builder().down(ex).build())
        );
    }

    private Mono<Health> checkTargetServiceHealth() {
        var target = this.context;
        Map<String, Mono<Map<String, String>>> targetServiceBeans= new LinkedHashMap<>();
        while (target != null) {
            target.getBeansOfType(WebClient.class)
                    .forEach((name, webclient) -> targetServiceBeans.put(name, createReport(name,webclient)));
            target = target.getParent();
        }
        log.info("Reactive Webclient Beans [{}]", targetServiceBeans); // Bean is empty
        var heathBuilder = new Health.Builder().withDetails(targetServiceBeans);
        return (overallStatus.equals("DOWN") )? Mono.just(heathBuilder.down().build())
                : Mono.just(heathBuilder.up().build());
    }

    private Mono<Map<String, String>> createReport(String name, WebClient webclient) {
        log.info("Reactive Webclient [{}] is triggered", name); // Here name of bean is coming
        return webclient.get()
                .uri("/actuator/health/liveness")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {
                })
                .onErrorResume(e -> {
                    log.error("Could not retrieve reactive health status of : {}", name, e);
                    overallStatus="DOWN";
                    return Mono.just(Map.of("status", "DOWN"));
                });
    }
}

问题分析与解决方案

核心问题有两点:

  1. 直接将Mono<Map>放入Health.Builder的details中,Spring Actuator不会自动订阅这些Mono,导致实际健康值未被解析,最终返回的是Mono对象的序列化结果,而非预期的状态数据。
  2. 使用全局变量overallStatus记录整体状态存在线程安全问题,响应式环境下多请求会互相干扰。

修改步骤及代码实现

@Component
@Slf4j
public class ReactiveTargetHealthIndicator implements ReactiveHealthIndicator {

    private final ApplicationContext context;

    public ReactiveTargetHealthIndicator(@NonNull ApplicationContext context) {
        this.context = context;
    }

    @Override
    public Mono<Health> health() {
        return checkTargetServiceHealth()
                .onErrorResume(ex -> Mono.just(Health.down(ex).build()));
    }

    private Mono<Health> checkTargetServiceHealth() {
        // 收集所有WebClient对应的健康检查Mono
        Map<String, Mono<Health>> serviceHealthMonos = new LinkedHashMap<>();
        ApplicationContext currentContext = this.context;
        while (currentContext != null) {
            currentContext.getBeansOfType(WebClient.class)
                    .forEach((name, webClient) -> serviceHealthMonos.put(name, checkSingleService(name, webClient)));
            currentContext = currentContext.getParent();
        }

        if (serviceHealthMonos.isEmpty()) {
            return Mono.just(Health.up().build());
        }

        // 合并所有Mono,统一处理结果
        return Mono.zip(serviceHealthMonos.values(), results -> {
            Map<String, Object> serviceDetails = new LinkedHashMap<>();
            boolean overallUp = true;

            int index = 0;
            for (String serviceName : serviceHealthMonos.keySet()) {
                Health serviceHealth = (Health) results[index];
                serviceDetails.put(serviceName, Map.of("status", serviceHealth.getStatus().getCode()));
                if (serviceHealth.getStatus() == Status.DOWN) {
                    overallUp = false;
                }
                index++;
            }

            // 构建符合预期格式的Health对象
            Health.Builder builder = overallUp ? Health.up() : Health.down();
            return builder.withDetails(serviceDetails).build();
        });
    }

    private Mono<Health> checkSingleService(String name, WebClient webClient) {
        return webClient.get()
                .uri("/actuator/health/liveness")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(Health.class) // 直接映射为Spring Health对象,避免JSON解析错误
                .onErrorResume(e -> {
                    log.error("Failed to get health status for service: {}", name, e);
                    return Mono.just(Health.down().withDetail("error", e.getMessage()).build());
                });
    }
}

关键说明

  • 用Mono.zip合并所有服务的健康检查请求,等待所有请求完成后再处理结果,确保所有Mono都被订阅执行。
  • 直接使用bodyToMono(Health.class)解析目标服务的健康响应,适配Spring Actuator的默认返回结构,解决JSON解析问题。
  • 移除线程不安全的全局状态变量,改为在合并结果时计算整体健康状态。
  • 构建Health对象时,将每个服务的状态作为details内容,使Actuator返回的结构与预期一致(服务状态直接在reactiveTarget节点下)。

内容的提问来源于stack exchange,提问作者user2800089

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:12:12