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 可以使用更少的執行緒和系統資源來處理更多的邏輯。
WebClient 是 Spring 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
譯者:萬想------
送福利啦~ 近期將之前已翻譯文章,整理成了PDF。
在公眾號後臺回覆:002即可領取哦~
後續也會不斷更新PDF的內容,敬請期待!