使用Spring Webclient实现Scatter&Gather时如何处理单服务异常
问题解决方案
现有实现存在的问题
- 线程安全风险:你在
zip的map操作中修改外部定义的ArrayList,响应式操作符的执行没有固定线程绑定,多线程场景下会出现并发修改异常或数据丢失。 - 错误传播不符合预期:
Mono.zip的原生特性是任意上游Mono触发错误信号,整个zip流就会直接抛出异常终止,这是你遇到单个服务失败整体报错的核心原因。 - 语法BUG:前面定义的两个
Mono变量是gResp、iResp,zip时使用了未定义的firstResp、secResp;第二个if判断后多了多余的分号,会导致后续addAll无论判断结果是否成立都会执行。
优化后可实现部分失败降级的代码
public Mono<List<Response>> getRecords(String input) { // 第一个服务调用,出错返回空集合 Mono<List<Response>> firstRespList = this.firstWebClient.get().uri(uriBuilder -> uriBuilder .path("/") .queryParam("q", input) .build()) .retrieve() .bodyToMono(FirstApiResponse.class) .log() .timeout(Duration.ofSeconds(50L)) .map(Mapper::convert) // 捕获所有异常降级返回空集合,可在此处添加错误日志打印 .onErrorResume(ex -> Mono.just(Collections.emptyList())); // 第二个服务调用,出错返回空集合 Mono<List<Response>> secondRespList = this.secondWebClient.get().uri(uriBuilder -> uriBuilder .path("/search") .queryParam("term", input) .build()) .retrieve() .bodyToMono(SecondApiResponse.class) .log() .timeout(Duration.ofSeconds(50L)) .map(Mapper::convert) // 捕获所有异常降级返回空集合,可在此处添加错误日志打印 .onErrorResume(ex -> Mono.just(Collections.emptyList())); // 合并两个结果返回 return Mono.zip(firstRespList, secondRespList) .map(tuple -> { List<Response> result = new ArrayList<>(); result.addAll(tuple.getT1()); result.addAll(tuple.getT2()); return result; }); } // Mapper方法保持不变即可 public static List<Response> convert(FirstApiResponse resp) { // 原有转换逻辑 return response; } public static List<Response> convert(SecondApiResponse resp) { // 原有转换逻辑 return response; }
关键改动说明
- 每个独立服务调用后添加
onErrorResume降级逻辑,不管是超时、HTTP报错、序列化失败都不会向上传播错误,只会返回空集合,完全满足单个服务失败时仍返回另一个服务结果的需求。 - 提前完成响应结构转换,
zip时只需合并集合即可,逻辑拆分更清晰。 - 去掉了外部可变集合,所有结果拼接都在
zip的map操作内部完成,彻底规避线程安全问题。
内容的提问来源于stack exchange,提问作者Andromeda
相关产品推荐
相关产品推荐

