如何实现整合多个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")); }); } }
问题分析与解决方案
核心问题有两点:
- 直接将
Mono<Map>放入Health.Builder的details中,Spring Actuator不会自动订阅这些Mono,导致实际健康值未被解析,最终返回的是Mono对象的序列化结果,而非预期的状态数据。 - 使用全局变量
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
相关产品推荐
相关产品推荐

