1. 概述
在本教程中,我們將比較 Spring 的兩個 Web 客户端實現——RestTemplate 和 Spring 5 引入的反應式替代方案 WebClient。
2. 客户端阻塞與非阻塞
在Web應用程序中,向其他服務發出HTTP調用是很常見的需求。因此,我們需要一個Web客户端工具。
2.1. RestTemplate 阻塞客户端
長期以來,Spring一直提供 RestTemplate 作為Web客户端抽象。在底層, RestTemplate 使用Java Servlet API,該API基於線程一請求模型。
這意味着線程將阻塞,直到Web客户端收到響應。阻塞代碼的問題在於,每個線程消耗一定數量的內存和CPU週期。
讓我們考慮有大量傳入請求,這些請求正在等待某個慢速服務產生結果。
總而言之,等待結果的請求會堆積起來。 因此,應用程序會創建許多線程,這些線程會耗盡線程池或佔用所有可用內存。 我們還可能因為頻繁的CPU上下文(線程)切換而導致性能下降。
2.2. WebClient 非阻塞客户端
另一方面, WebClient 使用Spring Reactive框架提供的異步、非阻塞解決方案。
當 RestTemplate 為每個事件使用調用者線程時,WebClient 將為每個事件創建一個類似“任務”。在後台,Reactive框架會排隊這些“任務”並在適當的響應可用時才執行它們。
Reactive框架使用事件驅動架構。它提供了通過Reactive Streams API組合異步邏輯的手段。因此,與同步/阻塞方法相比,反應式方法可以在使用更少的線程和系統資源的同時處理更多邏輯。
WebClient 是Spring WebFlux庫的一部分。 因此,我們也可以使用反應式類型(Mono 和 Flux)的函數式、流暢API編寫客户端代碼,以聲明式的方式組合。
3. Comparison Example
為了演示這兩種方法之間的差異,我們需要運行性能測試,模擬大量併發客户端請求。
使用阻塞方法後,在一定數量的並行客户端請求數達到一定程度時,性能會顯著下降。
然而,反應式/非阻塞方法應該始終保持穩定的性能,無論請求數量如何。
在本文中,我們將實現兩個 REST 端點,一個使用 RestTemplate,另一個使用 WebClient。它們的任務是調用另一個慢速 REST Web 服務,該服務返回一列表的推文。
首先,我們需要 Spring Boot WebFlux 啓動器依賴項:Spring Boot WebFlux starter dependency:
<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. Using RestTemplate to Call a Slow Service
現在,我們將實現另一個 REST 端點,該端點將通過 WebClient 調用我們的慢速服務。
首先,我們將使用 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. Using WebClient to Call a Slow Service
第二,我們將使用 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 發佈器,並且方法執行完成。一旦結果可用,發佈器將開始向其訂閲者發出推文。
客户端(在本例中為 Web 瀏覽器)調用此 /tweets-non-blocking 端點時,也會訂閲返回的 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. 結論
在本文中,我們探討了兩種使用 Web 客户端在 Spring 中的方法。
RestTemplate 使用 Java Servlet API,因此是同步且阻塞的。
相反,WebClient 是異步的,並且在等待響應返回時不會阻塞執行線程。 只有響應準備好時才會產生通知。
RestTemplate 仍然將被使用。 但是在某些情況下,非阻塞方法與阻塞方法相比,使用的系統資源要少得多。 因此,WebClient 在這些情況下是首選的方案。