Spring WebClient vs. RestTemplate

鍋外的大佬發表於2019-07-31

1. 簡介

本教程中,我們將對比 Spring 的兩種 Web 客戶端實現 —— RestTemplate 和 Spring 5 中全新的 Reactive 替代方案 WebClient

2. 阻塞式 vs 非阻塞式客戶端

Web 應用中,對其他服務進行 HTTP 呼叫是一個很常見的需求。因此,我們需要一個 Web 客戶端工具。

2.1. RestTemplate 阻塞式客戶端

很長一段時間以來,Spring 一直提供 RestTemplate 作為 Web 客戶端抽象。在底層,RestTemplate 使用了基於每個請求對應一個執行緒模型(thread-per-request)的 Java Servlet API。

這意味著,直到 Web 客戶端收到響應之前,執行緒都將一直被阻塞下去。而阻塞程式碼帶來的問題則是,每個執行緒都消耗了一定的記憶體和 CPU 週期。

讓我們考慮下有很多傳入請求,它們正在等待產生結果所需的一些慢服務。

等待結果的請求遲早都會堆積起來。因此,程式將建立很多執行緒,這些執行緒將耗盡執行緒池或佔用所有可用記憶體。由於頻繁的 CPU 上下文(執行緒)切換,我們還會遇到效能下降的問題。

2.2. WebClient 非阻塞式客戶端

另一方面,WebClient 使用 Spring Reactive Framework 所提供的非同步非阻塞解決方案。

RestTemplate 為每個事件(HTTP 請求)建立一個新的 執行緒 時,WebClient 將為每個事件建立類似於“任務”的東東。幕後,Reactive 框架將對這些 “任務” 進行排隊,並僅在適當的響應可用時執行它們。

Reactive 框架使用事件驅動的體系結構。它提供了通過 Reactive Streams API 組合非同步邏輯的方法。因此,與同步/阻塞方法相比,Reactive 可以使用更少的執行緒和系統資源來處理更多的邏輯。

WebClientSpring WebFlux 庫的一部分。因此,我們還可以使用流暢的函式式 API 編寫客戶端程式碼,並將響應型別(Mono 和 Flux)作為宣告來進行組合。

3. 案例對比

為了演示兩種方法間的差異,我們需要使用許多併發客戶端請求來執行效能測試。在一定數量的併發請求後,我們將看到阻塞方法效能的顯著下降。

另一方面,無論請求數量如何,反應式/非阻塞方法都可以提供恆定的效能。

就本文而言,讓我們實現兩個 REST 端點,一個使用 RestTemplate,另一個使用 WebClient。他們的任務是呼叫另一個響應慢的 REST Web 服務,該服務返回一個 Tweet List。

首先,我們需要引入 Spring Boot WebFlux starter 依賴

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

接下來,這是我們的慢服務 REST 端點:

@GetMapping("/slow-service-tweets")
private List<Tweet> getAllTweets() {
    Thread.sleep(2000L); // delay
    return Arrays.asList(
      new Tweet("RestTemplate rules", "@user1"),
      new Tweet("WebClient is better", "@user2"),
      new Tweet("OK, both are useful", "@user1"));
}

3.1. 使用 RestTemplate 呼叫慢服務

現在,讓我們來實現另一個 REST 端點,它將通過 Web 客戶端呼叫我們的慢服務。

首先,我們來使用 RestTemplate

@GetMapping("/tweets-blocking")
public List<Tweet> getTweetsBlocking() {
    log.info("Starting BLOCKING Controller!");
    final String uri = getSlowServiceUri();

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<List<Tweet>> response = restTemplate.exchange(
      uri, HttpMethod.GET, null,
      new ParameterizedTypeReference<List<Tweet>>(){});

    List<Tweet> result = response.getBody();
    result.forEach(tweet -> log.info(tweet.toString()));
    log.info("Exiting BLOCKING Controller!");
    return result;
}

當我們呼叫這個端點時,由於 RestTemplate 的同步特性,程式碼將會阻塞以等待來自慢服務的響應。只有當收到響應後,才會執行此方法中的其餘程式碼。通過日誌,我們可以看到:

Starting BLOCKING Controller!
Tweet(text=RestTemplate rules, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)
Exiting BLOCKING Controller!

3.2. 使用 WebClient 呼叫慢服務

其次,讓我們使用 WebClient 來呼叫慢服務:

@GetMapping(value = "/tweets-non-blocking", 
            produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> getTweetsNonBlocking() {
    log.info("Starting NON-BLOCKING Controller!");
    Flux<Tweet> tweetFlux = WebClient.create()
      .get()
      .uri(getSlowServiceUri())
      .retrieve()
      .bodyToFlux(Tweet.class);

    tweetFlux.subscribe(tweet -> log.info(tweet.toString()));
    log.info("Exiting NON-BLOCKING Controller!");
    return tweetFlux;
}

本例中,WebClient 返回一個 Flux 生產者後完成方法的執行。一旦結果可用,釋出者將開始向其訂閱者傳送 tweets。注意,呼叫 /tweets-non-blocking 這個端點的客戶端(本例中的 Web 瀏覽器)也將訂閱返回的 Flux 物件。

讓我們來觀察這次的日誌:

Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Tweet(text=RestTemplate rules, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)

注意,此端點的方法在收到響應之前就已完成。

4. 結論

本文中,我們探討了在 Spring 中使用 Web 客戶端的兩種不同方式。

RestTemplate 使用 Java Servlet API,因此是同步和阻塞的。相反,WebClient 是非同步的,在等待響應返回時不會阻塞正在執行的執行緒。只有當程式就緒時,才會產生通知。

RestTemplate 仍將會被使用。但在某些情況下,與阻塞方法相比,非阻塞方法使用的系統資源要少得多。因此,在這些情況下,WebClient 不失為是更好的選擇。

文中提到的所有程式碼片段,均可在 GitHub 上找到。

原文:https://www.baeldung.com/spring-webclient-resttemplate

作者:Drazen Nikolic

譯者:萬想------

送福利啦~ 近期將之前已翻譯文章,整理成了PDF。

在公眾號後臺回覆:002即可領取哦~

後續也會不斷更新PDF的內容,敬請期待!

img

相關文章