Spring Boot FeignClient 與 WebClient
一、概述
在本教程中,我們將比較 Spring Feign——一個聲明式 REST 客戶端和 Spring WebClient
一個在 Spring 5 中引入的反應式 Web 客戶端。
2. 阻塞與非阻塞客戶端
在當今的微服務生態系統中,通常需要後端服務使用 HTTP 調用其他 Web 服務。因此,Spring 應用程序需要一個 Web 客戶端來執行請求。
接下來,我們將檢查阻塞 Feign 客戶端和非阻塞WebClient
實現之間的區別。
2.1. Spring Boot 阻塞 Feign 客戶端
Feign 客戶端是一個聲明式 REST 客戶端,可以更輕鬆地編寫 Web 客戶端。使用 Feign 時,開發人員只需定義接口並相應地對其進行註釋。實際的 Web 客戶端實現隨後由 Spring 在運行時提供。
在幕後,用@FeignClient
註釋的接口生成一個基於 thread-per-request 模型的同步實現。因此,對於每個請求,分配的線程都會阻塞,直到它收到響應。保持多個線程存活的缺點是每個打開的線程佔用內存和 CPU 週期。
接下來,假設我們的服務遇到流量高峰,每秒接收數千個請求。最重要的是,每個請求都需要等待幾秒鐘,以便上游服務返回結果。
根據分配給託管服務器的資源和流量峰值的長度,一段時間後,所有創建的線程將開始堆積並佔用所有分配的資源。因此,這一系列事件將降低服務的性能並最終導致服務崩潰。
2.2. Spring Boot 非阻塞WebClient
WebClient
是 Spring WebFlux 庫的一部分。它是 Spring Reactive Framework 提供的一種非阻塞解決方案,用於解決 Feign clients 等同步實現的性能瓶頸。
當 Feign 客戶端為每個請求創建一個線程並阻塞它直到它收到響應時, WebClient
執行 HTTP 請求並將“等待響應”任務添加到隊列中。稍後,“等待響應”任務在收到響應後從隊列中執行,最終將響應傳遞給訂閱者函數。
Reactive 框架實現了一個由 Reactive Streams API 提供支持的事件驅動架構。正如我們所見,這使我們能夠編寫以最少數量的阻塞線程執行 HTTP 請求的服務。
因此, WebClient
通過使用更少的系統資源處理更多的請求,幫助我們構建在惡劣環境中始終如一地執行的服務。
3.比較例
要查看 Feign 客戶端和WebClient
之間的區別,我們將實現兩個 HTTP 端點,它們都調用返回產品列表的相同慢速端點。
我們將看到,在阻塞 Feign 實現的情況下,每個請求線程阻塞兩秒鐘,直到收到響應。
另一方面,非阻塞WebClient
將立即關閉請求線程。
首先,我們需要添加三個依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
接下來,我們有慢端點定義:
@GetMapping("/slow-service-products")
private List<Product> getAllProducts() throws InterruptedException {
Thread.sleep(2000L); // delay
return Arrays.asList(
new Product("Fancy Smartphone", "A stylish phone you need"),
new Product("Cool Watch", "The only device you need"),
new Product("Smart TV", "Cristal clean images")
);
}
3.1.使用 Feign 調用慢速服務
現在,讓我們開始使用 Feign 實現第一個端點.
第一步是定義接口並使用FeignCleint
對其進行註釋:
@FeignClient(value = "productsBlocking", url = "http://localhost:8080")
public interface ProductsFeignClient {
@RequestMapping(method = RequestMethod.GET, value = "/slow-service-products", produces = "application/json")
List<Product> getProductsBlocking(URI baseUrl);
}
最後,我們將使用定義的ProductsFeignClient
接口來調用慢速服務:
@GetMapping("/products-blocking")
public List<Product> getProductsBlocking() {
log.info("Starting BLOCKING Controller!");
final URI uri = URI.create(getSlowServiceBaseUri());
List<Product> result = productsFeignClient.getProductsBlocking(uri);
result.forEach(product -> log.info(product.toString()));
log.info("Exiting BLOCKING Controller!");
return result;
}
接下來,讓我們執行一個請求並查看日誌的樣子:
Starting BLOCKING Controller!
Product(title=Fancy Smartphone, description=A stylish phone you need)
Product(title=Cool Watch, description=The only device you need)
Product(title=Smart TV, description=Cristal clean images)
Exiting BLOCKING Controller!
正如預期的那樣,在同步實現的情況下,請求線程正在等待接收所有產品。之後,它會將它們打印到控制台並退出控制器功能,然後最終關閉請求線程。
3.2.使用WebClient
調用慢速服務
其次,讓我們實現一個非阻塞WebClient
來調用同一個端點:
@GetMapping(value = "/products-non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Product> getProductsNonBlocking() {
log.info("Starting NON-BLOCKING Controller!");
Flux<Product> productFlux = WebClient.create()
.get()
.uri(getSlowServiceBaseUri() + SLOW_SERVICE_PRODUCTS_ENDPOINT_NAME)
.retrieve()
.bodyToFlux(Product.class);
productFlux.subscribe(product -> log.info(product.toString()));
log.info("Exiting NON-BLOCKING Controller!");
return productFlux;
}
控制器函數不返回產品列表,而是返回Flux
發布者并快速完成該方法。在這種情況下,消費者將訂閱Flux
實例並在產品可用時對其進行處理。
現在,讓我們再次查看日誌:
Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Product(title=Fancy Smartphone, description=A stylish phone you need)
Product(title=Cool Watch, description=The only device you need)
Product(title=Smart TV, description=Cristal clean images)
正如預期的那樣,控制器功能立即完成,由此,它也完成了請求線程。一旦Product
可用,訂閱的函數就會處理它們。
4。結論
在本文中,我們比較了 Spring 中兩種編寫 Web 客戶端的風格。
首先,我們探索了 Feign 客戶端,一種編寫同步和阻塞 Web 客戶端的聲明式風格。
其次,我們探索了WebClient
,它支持 Web 客戶端的異步實現。
儘管 Feign 客戶端在許多情況下是一個很好的選擇,並且生成的代碼具有較低的認知複雜性,但WebClient
的非阻塞風格在高流量高峰期間使用的系統資源要少得多。考慮到這一點,最好為這些情況選擇WebClient
。
與往常一樣,可以在 GitHub 上找到本文中的代碼。