在这个演示中,Spring
团队的Rossen Stoyanchev
解释了一些要点。
WebClient
将使用有限数量的线程-每核心2个线程,在我的本地机器上总共为12个线程
来处理应用程序中所有请求及其响应。所以如果你的应用程序接收了100个请求
并针对每个请求向外部服务器发起一次请求,WebClient
将使用这些线程以非阻塞/异步
方式处理所有这些请求和响应。
当然,正如你提到的那样,一旦调用block
,您的原始线程将被阻塞,因此处理这些请求需要112个线程
(100个请求线程+ 12个WebClient线程)。但请记住:这12个线程不会随着您发出更多请求而增长,并且它们不会执行I/O重任务。 因此,WebClient
不会生成线程来实际执行请求,也不会像逐个请求一样保持它们忙碌。
我不确定当线程正在block
时,它是否与通过RestTemplate
进行阻塞调用时相同-在前者中,线程应该处于非活动状态
等待NIO
调用完成,而在后者中,线程应该处理I/O
工作,因此可能存在差异。
如果您开始使用reactor
,例如处理彼此依赖的请求或并行处理多个请求,则变得有趣。然后,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个线程并发处理。很棒!
如果您认为这种并行处理方式对您的逻辑有益,那么尝试使用WebClient
可能是值得的。
如果不需要这种并行处理,虽然我不认为有额外的资源开销可以担忧,但考虑到以上原因,我认为为此添加整个reactor/webflux
依赖项不值得——除了额外的负担,在日常操作中使用RestTemplate
和基于请求的线程
模型更容易进行推理和调试。
当然,正如其他人所提到的,您应该运行负载测试以获得正确的指标。
WebClient
,因此需要考虑是否转向RestTemplate
或者jdk的HttpClient
以减少线程消耗并放弃对webflux的需求。阅读您的回答,我相信更改该工具的成本超过了性能的提高。在执行并发操作时获得优势的添加是一个不错的建议,我会在进一步开发中记住这一点。 - ODDminus1flatMap
并行运行Queues.SMALL_BUFFER_SIZE
,但您可以控制并发性或使用其他运算符,如按顺序处理数据的concatMap
。您还可以控制调度程序(线程池运行)。无论如何,WebClient
的优势不在于响应式API,而在于NiO客户端(默认为Netty),其中所有IO操作都是异步和非阻塞的。您可以找到许多Netty基准测试,证明它的性能。 - Alex