(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客戶端來改造其微服務網關的案例。

相關文章
相關標籤/搜索