本教程中,我們將對比 Spring 的兩種 Web 客戶端實(shí)現(xiàn) —— RestTemplate 和 Spring 5 中全新的 Reactive 替代方案 WebClient。
創(chuàng)新互聯(lián)公司專注于企業(yè)成都營銷網(wǎng)站建設(shè)、網(wǎng)站重做改版、臺(tái)江網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、HTML5建站、商城開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為臺(tái)江等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
Web 應(yīng)用中,對其他服務(wù)進(jìn)行 HTTP 調(diào)用是一個(gè)很常見的需求。因此,我們需要一個(gè) Web 客戶端工具。
很長一段時(shí)間以來,Spring 一直提供 RestTemplate 作為 Web 客戶端抽象。在底層,RestTemplate 使用了基于每個(gè)請求對應(yīng)一個(gè)線程模型(thread-per-request)的 Java Servlet API。
這意味著,直到 Web 客戶端收到響應(yīng)之前,線程都將一直被阻塞下去。而阻塞代碼帶來的問題則是,每個(gè)線程都消耗了一定的內(nèi)存和 CPU 周期。
讓我們考慮下有很多傳入請求,它們正在等待產(chǎn)生結(jié)果所需的一些慢服務(wù)。
等待結(jié)果的請求遲早都會(huì)堆積起來。因此,程序?qū)?chuàng)建很多線程,這些線程將耗盡線程池或占用所有可用內(nèi)存。由于頻繁的 CPU 上下文(線程)切換,我們還會(huì)遇到性能下降的問題。
另一方面,WebClient 使用 Spring Reactive Framework 所提供的異步非阻塞解決方案。
當(dāng) RestTemplate 為每個(gè)事件(HTTP 請求)創(chuàng)建一個(gè)新的 線程 時(shí),WebClient 將為每個(gè)事件創(chuàng)建類似于“任務(wù)”的東東。幕后,Reactive 框架將對這些 “任務(wù)” 進(jìn)行排隊(duì),并僅在適當(dāng)?shù)捻憫?yīng)可用時(shí)執(zhí)行它們。
Reactive 框架使用事件驅(qū)動(dòng)的體系結(jié)構(gòu)。它提供了通過 Reactive Streams API 組合異步邏輯的方法。因此,與同步/阻塞方法相比,Reactive 可以使用更少的線程和系統(tǒng)資源來處理更多的邏輯。
WebClient 是 Spring WebFlux 庫的一部分。因此,我們還可以使用流暢的函數(shù)式 API 編寫客戶端代碼,并將響應(yīng)類型(Mono 和 Flux)作為聲明來進(jìn)行組合。
為了演示兩種方法間的差異,我們需要使用許多并發(fā)客戶端請求來運(yùn)行性能測試。在一定數(shù)量的并發(fā)請求后,我們將看到阻塞方法性能的顯著下降。
另一方面,無論請求數(shù)量如何,反應(yīng)式/非阻塞方法都可以提供恒定的性能。
就本文而言,讓我們實(shí)現(xiàn)兩個(gè) REST 端點(diǎn),一個(gè)使用 RestTemplate,另一個(gè)使用 WebClient。他們的任務(wù)是調(diào)用另一個(gè)響應(yīng)慢的 REST Web 服務(wù),該服務(wù)返回一個(gè) Tweet List。
首先,我們需要引入 Spring Boot WebFlux starter 依賴:
org.springframework.boot
spring-boot-starter-webflux
接下來,這是我們的慢服務(wù) REST 端點(diǎn):
@GetMapping("/slow-service-tweets")
private List 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"));
}
現(xiàn)在,讓我們來實(shí)現(xiàn)另一個(gè) REST 端點(diǎn),它將通過 Web 客戶端調(diào)用我們的慢服務(wù)。
首先,我們來使用 RestTemplate:
@GetMapping("/tweets-blocking")
public List getTweetsBlocking() {
log.info("Starting BLOCKING Controller!");
final String uri = getSlowServiceUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity> response = restTemplate.exchange(
uri, HttpMethod.GET, null,
new ParameterizedTypeReference>(){});
List result = response.getBody();
result.forEach(tweet -> log.info(tweet.toString()));
log.info("Exiting BLOCKING Controller!");
return result;
}
當(dāng)我們調(diào)用這個(gè)端點(diǎn)時(shí),由于 RestTemplate 的同步特性,代碼將會(huì)阻塞以等待來自慢服務(wù)的響應(yīng)。只有當(dāng)收到響應(yīng)后,才會(huì)執(zhí)行此方法中的其余代碼。通過日志,我們可以看到:
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!
其次,讓我們使用 WebClient 來調(diào)用慢服務(wù):
@GetMapping(value = "/tweets-non-blocking",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux getTweetsNonBlocking() {
log.info("Starting NON-BLOCKING Controller!");
Flux 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 返回一個(gè) Flux 生產(chǎn)者后完成方法的執(zhí)行。一旦結(jié)果可用,發(fā)布者將開始向其訂閱者發(fā)送 tweets。注意,調(diào)用 /tweets-non-blocking 這個(gè)端點(diǎn)的客戶端(本例中的 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)
注意,此端點(diǎn)的方法在收到響應(yīng)之前就已完成。
本文中,我們探討了在 Spring 中使用 Web 客戶端的兩種不同方式。
RestTemplate 使用 Java Servlet API,因此是同步和阻塞的。相反,WebClient 是異步的,在等待響應(yīng)返回時(shí)不會(huì)阻塞正在執(zhí)行的線程。只有當(dāng)程序就緒時(shí),才會(huì)產(chǎn)生通知。
RestTemplate 仍將會(huì)被使用。但在某些情況下,與阻塞方法相比,非阻塞方法使用的系統(tǒng)資源要少得多。因此,在這些情況下,WebClient 不失為是更好的選擇。
文中提到的所有代碼片段,均可在 GitHub 上找到。
原文:
作者:Drazen Nikolic
譯者:萬想