在this presentation-Rossen Stoyanchev
中,Spring
团队解释了其中一些要点.
WebClient
将使用有限数量的线程(在我的本地计算机上,每个核心2个线程,总共12 threads
个线程)来处理应用程序中的所有请求及其响应.因此,如果您的应用程序接收到100 requests
个线程并 for each 线程向外部服务器发出一个请求,那么WebClient
将以non-blocking
/asynchronous
的方式处理所有使用这些线程的线程.
当然,正如您所提到的,一旦您调用block
,您的原始线程将被阻止,因此将有block
个线程+12个线程,总共112 threads
个线程来处理这些请求.但是请记住these 12 threads do not grow in size as you make more requests, and that they don't do I/O heavy lifting,所以它不像WebClient
是产生线程来实际执行请求,或者让它们以每请求一个线程的方式忙碌.
我不确定当线程小于block
时,它的行为是否与通过RestTemplate
进行阻塞调用时的行为相同-在我看来,在前者中,线程应该是inactive
,等待NIO
调用完成,而在后者中,线程应该处理I/O
个工作,因此可能存在差异.
如果您开始使用这reactor
个goodies,例如处理相互依赖的请求,或者并行处理许多请求,这会变得很有趣.然后WebClient
肯定会获得优势,因为它将使用相同的12个线程执行所有并发操作,而不是每个请求使用一个线程.
例如,考虑以下应用程序:
@SpringBootApplication
public class SO72300024 {
private static final Logger logger = LoggerFactory.getLogger(SO72300024.class);
public static void main(String[] args) {
SpringApplication.run(SO72300024.class, args);
}
@RestController
@RequestMapping("/blocking")
static class BlockingController {
@GetMapping("/{id}")
String blockingEndpoint(@PathVariable String id) throws Exception {
logger.info("Got request for {}", id);
Thread.sleep(1000);
return "This is the response for " + id;
}
@GetMapping("/{id}/nested")
String nestedBlockingEndpoint(@PathVariable String id) throws Exception {
logger.info("Got nested request for {}", id);
Thread.sleep(1000);
return "This is the nested response for " + id;
}
}
@Bean
ApplicationRunner run() {
return args -> {
Flux.just(callApi(), callApi(), callApi())
.flatMap(responseMono -> responseMono)
.collectList()
.block()
.stream()
.flatMap(Collection::stream)
.forEach(logger::info);
logger.info("Finished");
};
}
private Mono<List<String>> callApi() {
WebClient webClient = WebClient.create("http://localhost:8080");
logger.info("Starting");
return Flux.range(1, 10).flatMap(i ->
webClient
.get().uri("/blocking/{id}", i)
.retrieve()
.bodyToMono(String.class)
.doOnNext(resp -> logger.info("Received response {} - {}", I, resp))
.flatMap(resp -> webClient.get().uri("/blocking/{id}/nested", i)
.retrieve()
.bodyToMono(String.class)
.doOnNext(nestedResp -> logger.info("Received nested response {} - {}", I, nestedResp))))
.collectList();
}
}
如果运行此应用程序,您可以看到所有30个请求都由相同的12个线程(在我的电脑中)立即并行处理.Neat!
如果你认为你可以从这种逻辑上的并行性中获益,那么给WebClient
一次机会可能是值得的.
如果不是这样,尽管鉴于上述原因,我实际上不会担心"额外的资源支出",但我认为不值得为此添加整个reactor/webflux
依赖项-除了额外的负担之外,在日常操作中,对RestTemplate
和thread-per-request
模型进行推理和调试应该要简单得多.
当然,正如其他人所提到的,您应该运行负载测试以获得适当的度量.