Spring Boot 2 WebClient:单Controller内并行调用多个Rest API方案咨询
Spring Boot 单个请求内并行调用下游服务实现方案
核心实现思路是将每个下游服务调用封装为独立异步任务,提交到自定义线程池并行执行,待所有任务执行完成后聚合结果返回。
方案1:原生JDK CompletableFuture 实现(无额外依赖,通用性最强)
第一步:配置自定义业务线程池
避免使用JDK默认公共ForkJoinPool,防止和其他异步任务竞争资源,配置示例:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration public class ThreadPoolConfig { @Bean("bizRestExecutor") public Executor bizRestExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数根据实际业务调整,推荐按:核心数 = 2 * CPU核心数 + 下游服务平均耗时/CPU计算耗时 估算 executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(100); executor.setThreadNamePrefix("rest-call-"); // 拒绝策略根据业务选择,此处用CallerRunsPolicy避免任务丢弃 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
第二步:Controller层并行调用实现
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @RestController public class MyController { @Resource(name = "bizRestExecutor") private Executor bizRestExecutor; // 假设你已经注入了RestTemplate用于下游调用 @Resource private RestTemplate restTemplate; @GetMapping public MyResponse method() { // 提交第一个下游调用任务,异步执行 CompletableFuture<Resp1> resp1Future = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("endpoint1", Resp1.class), bizRestExecutor); // 提交第二个下游调用任务,异步执行 CompletableFuture<Resp2> resp2Future = CompletableFuture.supplyAsync(() -> restTemplate.getForObject("endpoint2", Resp2.class), bizRestExecutor); // 等待所有任务执行完成,可添加超时时间避免无限等待,此处示例超时时间3秒 CompletableFuture.allOf(resp1Future, resp2Future).get(3, TimeUnit.SECONDS); // 聚合结果返回 Resp1 resp1 = resp1Future.join(); Resp2 resp2 = resp2Future.join(); return new MyResponse(resp1, resp2); } }
方案2:Spring @Async 注解实现(代码更解耦,适合调用逻辑复杂场景)
第一步:开启异步注解支持
在启动类或者配置类上加@EnableAsync注解。
第二步:封装下游调用为异步方法
import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; @Service public class DownstreamCallService { @Resource private RestTemplate restTemplate; @Async("bizRestExecutor") // 指定用我们自定义的线程池 public CompletableFuture<Resp1> callEndpoint1() { Resp1 resp = restTemplate.getForObject("endpoint1", Resp1.class); return CompletableFuture.completedFuture(resp); } @Async("bizRestExecutor") public CompletableFuture<Resp2> callEndpoint2() { Resp2 resp = restTemplate.getForObject("endpoint2", Resp2.class); return CompletableFuture.completedFuture(resp); } }
第三步:Controller层调用
@RestController public class MyController { @Resource private DownstreamCallService downstreamCallService; @GetMapping public MyResponse method() throws Exception { CompletableFuture<Resp1> resp1Future = downstreamCallService.callEndpoint1(); CompletableFuture<Resp2> resp2Future = downstreamCallService.callEndpoint2(); CompletableFuture.allOf(resp1Future, resp2Future).get(3, TimeUnit.SECONDS); return new MyResponse(resp1Future.join(), resp2Future.join()); } }
方案3:响应式WebFlux实现(仅适合已使用WebFlux响应式栈的项目)
如果项目用WebFlux替代了Spring MVC,可以直接用Mono.zip实现并行调用,无需额外线程池配置:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import java.time.Duration; @RestController public class MyController { @Resource private WebClient webClient; @GetMapping public Mono<MyResponse> method() { Mono<Resp1> resp1Mono = webClient.get().uri("endpoint1").retrieve().bodyToMono(Resp1.class); Mono<Resp2> resp2Mono = webClient.get().uri("endpoint2").retrieve().bodyToMono(Resp2.class); return Mono.zip(resp1Mono, resp2Mono) .map(tuple -> new MyResponse(tuple.getT1(), tuple.getT2())) .timeout(Duration.ofSeconds(3)); } }
注意事项
- 所有异步调用必须加超时控制,避免下游服务故障导致本服务线程阻塞耗尽
- 线程池参数需根据实际业务压测结果调整,不要直接照搬示例配置
- 单个下游调用失败需做好降级兜底,可在
CompletableFuture后加exceptionally方法返回默认值,避免单个接口失败导致整个请求报错 - 如果下游调用数量较多,建议批量提交任务后统一等待,不要逐个调用join
内容的提问来源于stack exchange,提问作者Uva
相关产品推荐
相关产品推荐

