(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

本系列文章索引《响应式Spring的道法术器》
前情提要 Spring WebFlux快速上手 | Spring WebFlux性能测试
本文源码java

1.4.2 调用带有延迟的服务负载分析

因为微服务架构的盛行,大型系统内服务间基于HTTP API进行调用的会至关频繁。Netflix的系统有500+的微服务,感觉一下~react

咱们的测试以下图所示,服务A调用服务B的API,从服务A发出请求到接收到响应,期间可能存在延迟,好比网络不稳定、服务B不稳定,或由于所请求的API自己执行时间略长等等。对于做为HTTP客户端的服务A来讲,是否可以异步地处理对服务B的请求与响应,也会带来明显的性能差别。咱们经过简单的场景模拟一下:git

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

经过上一个测试,咱们已经肯定WebFlux-with-latency的API /hello/{latency}可以在高并发下,仍然以稳定的latency~latency+5ms的延迟作出响应,所以用来做为被调用的服务B,模拟带有延迟的服务。这样若是测试结果出现明显的差别,那么能够排除服务B的缘由。github

本次测试咱们建立两个服务A的项目:restTemplate-as-callerwebClient-as-caller。它们也都提供URL为/hello/{latency}的API,在API的实现上都是经过Http请求服务A的/hello/{latency},返回的数据做为本身的响应。区别在于:restTemplate-as-caller使用RestTemplate做为Http客户端,webClient-as-caller使用WebClient做为Http客户端。web

1)restTemplate-as-callerspring

使用Spring Initializr建立一个依赖“Web”的项目(也就是WebMVC项目),POM依赖:编程

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

端口号设置为8093,而后开发/hello/{latency}网络

HelloController.java架构

@RestController
    public class HelloController {
        private final String TARGET_HOST = "http://localhost:8092";
        private RestTemplate restTemplate;

        public HelloController() {  // 1
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
            connectionManager.setDefaultMaxPerRoute(1000);
            connectionManager.setMaxTotal(1000);
            this.restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(
                    HttpClientBuilder.create().setConnectionManager(connectionManager).build()
            ));

        }

        @GetMapping("/hello/{latency}")
        public String hello(@PathVariable int latency) {
            return restTemplate.getForObject(TARGET_HOST + "/hello/" + latency, String.class);
        }
    }
  1. 因为测试过程当中,RestTemplate会发出大量请求,咱们在Controller的构造方法中建立一个基于Http链接池构造的RestTemplate,不然可能会把系统能给的端口用尽而出错;
  2. 使用RestTemplate请求服务B,并将响应返回。

启动服务WebFlux-with-latencyrestTemplate-as-caller并发

这个测试咱们并不须要分析1000~10000的不一样用户量场景下的响应时长的变化趋势,只是验证RestTemplate的阻塞性,因此直接测试一下6000用户,测试结果以下:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

吞吐量为1651req/sec,95%响应时长为1622ms。

与1.4.1中mvc-with-latency的6000用户的结果相似,可见RestTemplate确实是会阻塞的。好吧,其实写个小@Test就能测出来是否是阻塞的,不过个人用意不只限于此,下边咱们进行一个响应式改造。首先请回忆前边介绍的两个内容:

  1. 不知道你是否还记得在1.3.3.1的最后提过,用Spring WebMVC + Reactor(spring-boot-starter-web+reactor-core)也能够像WebFlux同样实现基于注解的响应式编程;
  2. 在1.3.2.5介绍过如何利用elastic的调度器将阻塞的调用转化为异步非阻塞的。

基于此,咱们来改一下代码。首先在pom.xml中增长reactor-core

<dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.1.4.RELEASE</version>
        </dependency>

而后RestTemplate的调用转为异步:

@GetMapping("/hello/{latency}")
    public Mono<String> hello(@PathVariable int latency) {
        return Mono.fromCallable(() -> restTemplate.getForObject(TARGET_HOST + "/hello/" + latency, String.class))
                .subscribeOn(Schedulers.elastic());
    }

再次测试,发现结果有了明显改善:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

吞吐量为2169 req/sec,95%响应时长为121ms。

可是,使用Schedulers.elastic()其实就至关于将每一次阻塞的RestTemplate调用调度到不一样的线程里去执行,效果以下:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

由于不只有处理请求的200个线程,还有Schedulers.elastic()给分配的工做线程,因此总的线程数量飙到了1000多个!不过在生产环境中,咱们一般不会直接使用弹性线程池,而是使用线程数量可控的线程池,RestTemplate用完全部的线程后,更多的请求依然会形成排队的状况。

这一点使用Schedulers.newParallel()的调度器一测便知。

@RestController
    public class HelloController {
        private final String TARGET_HOST = "http://localhost:8092";
        private RestTemplate restTemplate;
        private Scheduler fixedPool;

        public HelloController() {
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
            connectionManager.setDefaultMaxPerRoute(1000);
            connectionManager.setMaxTotal(1000);
            this.restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(
                    HttpClientBuilder.create().setConnectionManager(connectionManager).build()
            ));
            fixedPool = Schedulers.newParallel("poolWithMaxSize", 400); // 1
        }

        @GetMapping("/hello/{latency}")
    //    public String hello(@PathVariable int latency) {
    //        return restTemplate.getForObject(TARGET_HOST + "/hello/" + latency, String.class);
    //    }
        public Mono<String> hello(@PathVariable int latency) {
            return Mono.fromCallable(() -> restTemplate.getForObject(TARGET_HOST + "/hello/" + latency, String.class))
                    .subscribeOn(fixedPool);    // 2
        }
    }
  1. 建立一个有最大400个线程的线程池poolWithMaxSize
  2. 调度到这个线程池上。

测试时查看线程数:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

可见,最多有400个名为poolWithMaxSize的线程,RestTemplate就工做在这些线程上,相比请求处理线程多了一倍。看一下最终的测试结果:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

吞吐量2169req/sec,与弹性线程池的那次相同;95%响应时长为236ms,虽然达不到弹性线程池的效果,可是比彻底同步阻塞的方式(RestTemplate在请求处理线程中执行)要好多了。

咱们再看看非阻塞的WebClient表现如何吧。

2)webClient-as-caller

webClient-as-caller基于WebFlux的依赖,端口号8094,很少说,直接看Controller:

@RestController
    public class HelloController {
        private final String TARGET_HOST = "http://localhost:8092";
        private WebClient webClient;

        public HelloController() {
            this.webClient = WebClient.builder().baseUrl(TARGET_HOST).build();
        }

        @GetMapping("/hello/{latency}")
        public Mono<String> hello(@PathVariable int latency) {
            return webClient
                    .get().uri("/hello/" + latency)
                    .exchange()
                    .flatMap(clientResponse -> clientResponse.bodyToMono(String.class));
        }

跑一下6000用户的测试:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

吞吐量2195 req/sec,95%响应时长109ms。

关键的是,WebClient不须要大量并发的线程就能够漂亮地搞定这件事儿了:

(7)Spring WebClient与RestTemplate性能对比——响应式Spring的道法

3)总结

WebClient一样可以以少许而固定的线程数处理高并发的Http请求,在基于Http的服务间通讯方面,能够取代RestTemplate以及AsyncRestTemplate。

异步非阻塞的Http客户端,请认准——WebClient~

下一节,介绍一下微服务先行者、全球最大的视频服务平台Netflix使用异步的Http客户端来改造其微服务网关的案例。

相关文章
相关标签/搜索