我正在开发几个spring boot应用程序,这些应用程序具有按请求线程的传统模式.我们正在使用Spring boot webflux获取WebClient,以在应用程序之间执行RESTful集成.因此,我们的应用程序设计要求我们在收到响应后立即阻止发布者.

最近,我们一直在讨论,在我们原本阻塞的应用程序设计中,使用react 式模块是否会不必要地花费资源.据我所知,WebClient通过分配一个工作线程来执行事件循环中的react 性操作,从而利用了事件循环.因此,将webclient与.block()一起使用将Hibernate 原始线程,同时分配另一个线程来执行http请求.与其他RestTemplate相比,WebClient似乎会通过使用事件循环花费额外的资源.

以这种方式部分引入SpringWebFlux会导致额外的资源消耗,同时不会对性能产生任何积极的贡献,无论是单线程还是并发的,这是正确的吗?我们不希望将当前堆栈升级为完全react 式,因此逐步升级的论点不适用.

推荐答案

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依赖项-除了额外的负担之外,在日常操作中,对RestTemplatethread-per-request模型进行推理和调试应该要简单得多.

当然,正如其他人所提到的,您应该运行负载测试以获得适当的度量.

Java相关问答推荐

如何在Java中使用Rest模板通过查询参数发送GET请求

如果它最终将被转换为int类型,为什么我们在Java中需要较小的integer类型?

为什么我们仍然需要实现noArgsConstructor如果Java默认提供一个非参数化的构造函数?''

调用引发泛型异常的泛型方法时出现编译错误

如何确定springboot在将json字段转换为Dto时如何处理它?

基于调车场算法的科学计算器

Java编译器抛出可能未正确初始化的错误?

安装Java Jar应用程序的Install4j遇到ClassNotFoundException的运行时错误

如何获取Instant#of EpochSecond(?)的最大值

如何在Microronaut中将 map 读取为 map

在Eclipse中数组的可空性

如何在构建Gradle项目时排除com.google.guava依赖项的一个变体

Android应用程序为错误的显示类型 Select 尺寸文件

有没有办法在o(log(N))中以系统的方式将数组中的小块元素复制和移动到新增长的数组中的左侧?

如何在字节数组中反转UTF-8编码?

循环不起作用只有第一个元素重复

模拟JUnit未检测到返回字符串的方法的任何声纳覆盖

在WHILE()循环初始化部分中声明和初始化变量的Java语法?

如何在特定关键字后提取与模式匹配的多个值?

Spring Boot Security-每个端点都被403禁止,Spring记录一个BasicErrorController#错误(HttpServlet请求)