Spring設定異步重試機制
1. 概述
有時,我們要求程式碼執行非同步,以獲得更好的應用程式效能和回應能力。此外,我們可能希望在出現任何異常時自動重新呼叫程式碼,因為我們預計會遇到偶爾的故障,例如網路故障。
在本教程中,我們將學習在 Spring 應用程式中實作具有自動重試的非同步執行。
我們將探討 Spring 對async
和retry
操作的支援。
2. Spring Boot中的範例應用
假設我們需要建立一個簡單的微服務來呼叫下游服務來處理一些資料。
2.1. Maven 依賴項
首先,我們需要包含spring-boot-starter-web
maven 依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2.實施 Spring 服務
現在,我們將實作EventService
類別的呼叫另一個服務的方法:
public String processEvents(List<String> events) {
downstreamService.publishEvents(events);
return "Completed";
}
然後,我們定義DownstreamService
介面:
public interface DownstreamService {
boolean publishEvents(List<String> events);
}
3. 透過重試實現非同步執行
為了透過重試實現非同步執行,我們將使用 Spring 的實作。
我們需要使用async
和retry
支援來配置應用程式。
3.1.添加Retry
Maven依賴
讓我們將spring-retry
加入到 Maven 依賴項:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>2.0.4</version>
</dependency>
3.2. @EnableAsync
和@EnableRetry
配置
接下來我們需要包含@EnableAsync
和@EnableRetry
**註解**:
@Configuration
@ComponentScan("com.baeldung.asyncwithretry")
@EnableRetry
@EnableAsync
public class AsyncConfig {
}
3.3.包括**@Async**
和@Retryable
註釋
要非同步執行方法,我們需要使用@Async
註解。同樣,我們將使用@Retryable annotation
來註解該方法以重試執行.
我們在上面的EventService
方法中配置上面的註解:
@Async
@Retryable(retryFor = RuntimeException.class, maxAttempts = 4, backoff = @Backoff(delay = 100))
public Future<String> processEvents(List<String> events) {
LOGGER.info("Processing asynchronously with Thread {}", Thread.currentThread().getName());
downstreamService.publishEvents(events);
CompletableFuture<String> future = new CompletableFuture<>();
future.complete("Completed");
LOGGER.info("Completed async method with Thread {}", Thread.currentThread().getName());
return future;
}
在上面的程式碼中,我們在發生RuntimeException
的情況下重試該方法,並將結果作為Future
物件傳回。
我們應該注意,我們應該使用Future
來包裝任何非同步方法的回應.
我們應該注意, @Async
註解僅適用於公共方法,不應該在同一個類別中自呼叫。自呼叫方法將繞過 Spring 代理呼叫並在同一執行緒中運行。
4. 實作@Async
和@Retryable
的測試
讓我們測試EventService
方法並透過一些測試案例來驗證其非同步和重試行為。
首先,當DownstreamService
呼叫沒有錯誤時,我們將實作一個測試案例:
@Test
void givenAsyncMethodHasNoRuntimeException_whenAsyncMethodIscalled_thenReturnSuccess_WithoutAnyRetry() throws Exception {
LOGGER.info("Testing for async with retry execution with thread " + Thread.currentThread().getName());
when(downstreamService.publishEvents(anyList())).thenReturn(true);
Future<String> resultFuture = eventService.processEvents(List.of("test1"));
while (!resultFuture.isDone() && !resultFuture.isCancelled()) {
TimeUnit.MILLISECONDS.sleep(5);
}
assertTrue(resultFuture.isDone());
assertEquals("Completed", resultFuture.get());
verify(downstreamService, times(1)).publishEvents(anyList());
}
在上面的測試中,我們等待Future
完成,然後斷言結果。
然後,讓我們執行上面的測試並驗證測試日誌:
18:59:24.064 [main] INFO com.baeldung.asyncwithretry.EventServiceIntegrationTest - Testing for async with retry execution with thread main
18:59:24.078 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
18:59:24.080 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Completed async method with Thread SimpleAsyncTaskExecutor-1
從上面的日誌中,我們確認服務方法在單獨的執行緒中運行。
接下來,我們將使用DownstreamService
方法拋出RuntimeException
來實作另一個測試案例:
@Test
void givenAsyncMethodHasRuntimeException_whenAsyncMethodIsCalled_thenReturnFailure_With_MultipleRetries() throws InterruptedException {
LOGGER.info("Testing for async with retry execution with thread " + Thread.currentThread().getName());
when(downstreamService.publishEvents(anyList())).thenThrow(RuntimeException.class);
Future<String> resultFuture = eventService.processEvents(List.of("test1"));
while (!resultFuture.isDone() && !resultFuture.isCancelled()) {
TimeUnit.MILLISECONDS.sleep(5);
}
assertTrue(resultFuture.isDone());
assertThrows(ExecutionException.class, resultFuture::get);
verify(downstreamService, times(4)).publishEvents(anyList());
}
最後,我們用輸出日誌來驗證上面的測試案例:
19:01:32.307 [main] INFO com.baeldung.asyncwithretry.EventServiceIntegrationTest - Testing for async with retry execution with thread main
19:01:32.318 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
19:01:32.425 [SimpleAsyncTaskExecutor-1] INFO com.baeldung.asyncwithretry.EventService - Processing asynchronously with Thread SimpleAsyncTaskExecutor-1
.....
從上面的日誌中,我們確認服務方法被非同步重新執行了四次。
5. 結論
在這篇文章中,我們學習如何使用Spring中的重試機制來實作非同步方法。
我們已經在範例應用程式中實現了這一點,並嘗試了一些測試來了解它如何處理不同的用例。我們已經了解了非同步程式碼如何在自己的執行緒上運行並且可以自動重試。
與往常一樣,範例程式碼可以在 GitHub 上找到。