我正在处理几个弹簧引导应用程序,这些应用程序具有按请求线程的传统模式。我们正在使用来获取WebClient来执行应用程序之间的RESTful集成。因此,我们的应用程序设计要求我们在收到响应后立即阻止发行者。
最近,我们一直在讨论是否在我们的应用程序设计中不必要地使用一个反应性模块来花费资源。正如我所理解的,WebClient通过分配一个辅助线程来执行事件循环中的反应动作来使用事件循环。因此,使用webclient和.block()
可以休眠原始线程,同时分配另一个线程来执行http请求。与替代的RestTemplate相比,WebClient似乎会通过使用事件循环来花费额外的资源。
以这种方式部分引入spring-webflux会导致额外的资源消耗,而不会对性能做出任何积极的贡献,而不是单线程和并发,这是正确的吗?我们并不期望将当前堆栈升级为完全被动的,因此逐步升级的论点不适用。
发布于 2022-05-19 23:04:41
在这份报告中,来自Spring
团队的Rossen Stoyanchev
解释了其中的一些要点。
WebClient
将使用有限数量的线程来处理应用程序中的所有请求及其响应。因此,如果您的应用程序接收到100 requests
并为每个应用程序向外部服务器发出一个请求,WebClient
将以non-blocking
/ asynchronous
方式处理所有使用这些线程的线程。
当然,正如您提到的,一旦调用block
,您的原始线程就会阻塞,因此总共需要100个线程+12个线程来处理这些请求。但是请记住,这12个线程不会随着您提出更多的请求而增长,而且它们也不会执行I/O繁重的,所以WebClient
并不是在生成线程来实际执行请求,或者让它们按照每个请求线程的方式忙碌。
我不确定当线程在block
下时,它的行为是否与通过RestTemplate
进行阻塞调用时的行为相同--在我看来,在前者中,线程应该是等待NIO
调用完成的inactive
,而在后面的线程中,线程应该处理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个线程(在我的计算机中)并行处理。Neat!
如果您认为您可以从逻辑中的这种并行性中获益,那么给WebClient
一次机会可能是值得的。
如果不是,考虑到上述原因,我实际上不会担心“额外的资源支出”,但我不认为为此添加整个reactor/webflux
依赖关系是值得的--除了额外的负担之外,在日常操作中,对RestTemplate
和thread-per-request
模型进行推理和调试应该要简单得多。
当然,正如其他人所提到的,您应该运行负载测试以获得适当的度量。
发布于 2022-05-19 13:30:08
根据RestTemplate的官方Spring文档,它处于维护模式,在以后的版本中可能不受支持。
从5.0开始,这个类处于维护模式,只需要对修改和bug进行小的请求。请考虑使用
org.springframework.web.reactive.client.WebClient
,它具有更现代的API,支持同步、异步和流方案。
至于系统资源,这确实取决于您的用例,我建议运行一些性能测试,但是对于低工作负载,使用阻塞客户端可以获得更好的性能,因为每个连接都有一个专用线程。随着负载的增加,NIO客户端往往表现得更好。
更新-反应性API vs Http客户端
理解反应性API (项目反应堆)和http客户端之间的区别是很重要的。尽管WebClient
使用Reactive,但它不会添加任何附加的并发,直到我们显式使用诸如flatMap
或delay
这样的操作符,它们可以在不同的线程池上调度执行。如果我们只是用
webClient
.get()
.uri("<endpoint>")
.retrieve()
.bodyToMono(String.class)
.block()
代码将在与阻塞客户端相同的调用线程上执行。
如果我们为该代码启用调试日志,我们将看到WebClient
代码在调用方线程上执行,但是对于网络操作,执行将切换到reactor-http-nio-...
线程。
主要区别在于内部WebClient
使用基于非阻塞IO (NIO)的异步客户端。这些客户端使用反应堆模式(事件循环)来维护一个单独的线程池,它允许您处理大量并发连接。
I/O反应堆的目的是对I/O事件作出反应,并向个别I/O会话发送事件通知。I/O反应器模式的主要思想是脱离经典阻塞I/O模型强加的单线程连接模型。
默认情况下,使用的是Rective,但如果您创建所需的适配器(不确定它是否已经存在),则可以考虑JettyR票面Http客户端、Apache HttpComponents (异步)甚至AWS公共运行时(CRT) Http客户端。
通常,您可以看到整个行业使用异步I/O (NIO)的趋势,因为它们对于高负载下的应用程序来说更节省资源。
此外,为了有效地处理资源,整个流程必须是异步的。通过使用block()
,我们隐式地重新引入了每个连接线程的方法,这将消除NIO的大部分好处。同时,将WebClient
与block()
结合使用可作为迁移到完全反应性应用程序的第一步。
发布于 2022-05-19 21:30:30
问得好。
上周,我们考虑从Last模板迁移到webclient。本周,我开始测试阻塞的webclient和were模板之间的性能,令我惊讶的是,在响应负载很大的情况下,were模板的性能更好。两者之间的差异相当大,less模板所需的响应时间不到一半,使用的资源也较少。
我仍然在进行性能测试,现在我开始测试的用户范围更广,以满足要求。
应用程序是mvc,使用的是Spring5.13.19和spring 2.6.7。
在性能测试中,我使用了j测量仪和用于健康检查的visualvm/j控制台。
https://stackoverflow.com/questions/72300024
复制相似问题