Spring REST API設置請求超時
1.概述
在本教程中,我們將探討幾種可能的方法來實現Spring REST API的請求超時。
我們將討論每種方法的優缺點。請求超時對於防止不良的用戶體驗很有用,尤其是在存在資源佔用時間過長的情況下,我們可以默認使用其他方法時。這種設計模式稱為“斷路器”模式,但在此不再贅述。
2. @ @Transactional
超時
我們可以對數據庫調用實現請求超時的一種方法是利用Spring的@Transactional
註釋。它具有我們可以設置的timeout
屬性。此屬性的默認值為-1,這等效於根本沒有任何超時。對於超時值的外部配置,必須使用另一個屬性timeoutString
代替。
例如,假設我們將此超時設置為30。如果帶註釋的方法的執行時間超過了此秒數,則將引發異常。這對於回滾長時間運行的數據庫查詢可能很有用。
為了了解這一點,讓我們編寫一個非常簡單的JPA存儲庫層,該層將代表一個外部服務,該外部服務需要很長時間才能完成,並且會導致超時。此JpaRepository擴展中有一個耗時的方法:
public interface BookRepository extends JpaRepository<Book, String> {
default int wasteTime() {
int i = Integer.MIN_VALUE;
while(i < Integer.MAX_VALUE) {
i++;
}
return i;
}
}
如果我們在事務內部以1秒的超時時間調用wasteTime()
方法,則超時將在該方法完成執行之前過去:
@GetMapping("/author/transactional")
@Transactional(timeout = 1)
public String getWithTransactionTimeout(@RequestParam String title) {
bookRepository.wasteTime();
return bookRepository.findById(title)
.map(Book::getAuthor)
.orElse("No book found for this title.");
}
調用此端點會導致500 HTTP錯誤,我們可以將其轉換為更有意義的響應。它還只需很少的設置即可實施。
但是,此超時解決方案有一些缺點。
首先,它依賴於具有Spring管理的事務的數據庫。它也不是全局適用於項目的,因為註釋必須出現在需要它的每個方法或類上。它還不允許亞秒精度。最後,在達到超時時,它不會縮短請求的時間,因此,請求實體仍然必須等待完整的時間。
讓我們考慮其他一些選擇。
3. Resilience4j TimeLimiter
Resilience4j是一個庫,主要用於管理遠程通信的容錯能力。我們在這裡感興趣的是它的TimeLimiter
模塊。
首先,我們必須在項目中包含resilience4j-timelimiter
依賴項:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-timelimiter</artifactId>
<version>1.6.1</version>
</dependency>
接下來,讓我們定義一個簡單的TimeLimiter
,其超時時間為500毫秒:
private TimeLimiter ourTimeLimiter = TimeLimiter.of(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofMillis(500)).build());
這可以很容易地在外部配置。
我們可以使用TimeLimiter
來包裝與@Transactional
示例所使用的邏輯相同的邏輯:
@GetMapping("/author/resilience4j")
public Callable<String> getWithResilience4jTimeLimiter(@RequestParam String title) {
return TimeLimiter.decorateFutureSupplier(ourTimeLimiter, () ->
CompletableFuture.supplyAsync(() -> {
bookRepository.wasteTime();
return bookRepository.findById(title)
.map(Book::getAuthor)
.orElse("No book found for this title.");
}));
}
與@Transactional
解決方案相比, TimeLimiter
許多優點。即,它支持亞秒精度和超時響應的立即通知。但是,它仍然必須手動包含在所有需要超時的端點中,它需要一些冗長的包裝代碼,並且它產生的錯誤仍然是通用的500 HTTP錯誤。另外,它需要返回Callable<String>
而不是原始String.
TimeLimiter
僅包含Resilience4j的功能的子集,並且與Circuit Breaker模式很好地接口。
4. Spring MVC request-timeout
Spring為我們提供了一個名為spring.mvc.async.request-timeout
的屬性。此屬性使我們可以定義毫秒級的請求超時。
讓我們以750毫秒的超時時間定義屬性:
spring.mvc.async.request-timeout=750
該屬性是全局的,並且可以在外部配置,但是與TimeLimiter
解決方案一樣,它僅適用於返回Callable
端點。讓我們定義一個類似於TimeLimiter
示例的端點,但是不需要將邏輯包裝在Futures
或提供TimeLimiter
:
@GetMapping("/author/mvc-request-timeout")
public Callable<String> getWithMvcRequestTimeout(@RequestParam String title) {
return () -> {
bookRepository.wasteTime();
return bookRepository.findById(title)
.map(Book::getAuthor)
.orElse("No book found for this title.");
};
}
我們可以看到代碼不太冗長,並且在定義應用程序屬性時,Spring會自動實現配置。一旦達到超時,響應將立即返回,它甚至返回更具描述性的503 HTTP錯誤,而不是通用的500錯誤。此外,我們項目中的每個端點都將自動繼承此超時配置。
讓我們考慮另一個選項,該選項使我們可以更詳細地定義超時。
5. WebClient
超時
與其為整個端點設置超時,不如我們只想為單個外部呼叫設置超時。 WebClient
是Spring的反應式Web客戶端,允許我們配置響應超時。
也可以在Spring較舊的RestTemplate
對像上配置超時。但是,大多數開發人員現在更喜歡WebClient
不是RestTemplate
。
要使用WebClient,我們必須首先將Spring的WebFlux依賴項添加到我們的項目中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.4.2</version>
</dependency>
讓我們定義一個響應時間為250毫秒的WebClient
,我們可以使用它在其基本URL中通過localhost進行調用:
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8080")
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create().responseTimeout(Duration.ofMillis(250))
))
.build();
}
顯然,我們可以在外部輕鬆配置此超時值。我們還可以在外部配置基本URL以及其他幾個可選屬性。
現在,我們可以將WebClient
注入到控制器中,並使用它來調用自己的/transactional
端點,該端點的超時時間仍為1秒。因為我們將WebClient
配置為在250毫秒內超時,所以我們應該看到它失敗的速度比1秒快得多。
這是我們的新端點:
@GetMapping("/author/webclient")
public String getWithWebClient(@RequestParam String title) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/author/transactional")
.queryParam("title", title)
.build())
.retrieve()
.bodyToMono(String.class)
.block();
}
調用此終結點後,我們看到確實以500 HTTP錯誤響應的形式收到WebClient
的超時。我們還可以檢查日誌以查看下游@Transactional
超時。但是,當然,如果我們調用外部服務而不是本地主機,則它的超時將被遠程打印。
為不同的後端服務配置不同的請求超時可能是必需的,並且使用此解決方案是可能的。此外, WebClient
返回的Mono
或Flux
響應發布者包含許多錯誤處理方法,用於處理通用超時錯誤響應。
六,結論
在本文中,我們剛剛探討了幾種用於實現請求超時的解決方案。在決定使用哪一個時,要考慮幾個因素。
如果我們想對數據庫請求設置超時,則可能要使用Spring的@Transactional
方法及其timeout
屬性。如果我們試圖與更廣泛的斷路器模式集成,則使用Resilience4j的TimeLimiter
會很有意義。使用Spring MVC request-timeout
屬性最適合為所有請求設置全局超時,但是我們可以使用WebClient
輕鬆為每個資源定義更精細的超時。