如何使用SpringBoot的重試功能模組? - Gavin
重試功能是 Spring Batch 模組的一部分。從 2.2.0 開始,此功能從 Spring Batch 中提取出來並作為一個單獨的模組進行維護。要在 Spring 應用程式中啟用此功能,請將此依賴項包含到您的 maven pom.xml 中。
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.1.RELEASE</version> </dependency> |
該庫不帶有自動配置,因此@EnableRetry應將註解新增到 SpringBoot App 或帶有@Configuration註解的類中,以啟用重試功能。
宣告式方法——構建重試邏輯的快速簡便方法
下面的示例程式碼指定了以下設定:
- 最多 3 次重試
- 每次重試與乘法器之間的範圍為 0.5 秒 - 3 秒的隨機間隔
- 僅針對 RuntimeException 觸發重試,這意味著系統會立即針對其他異常(例如客戶端錯誤或驗證拒絕)丟擲異常。
@Service public class RetryableCustomerSrvClient { @Autowired private CustomerSrvClient customerSrvClient; @Retryable(value = RuntimeException.class, maxAttempts = 4, backoff = @Backoff(delay = 500L, maxDelay = 3000L, multiplier = 2, random = true)) public Optional<Customer> getCustomer(Long id) { return customerSrvClient.getCustomer(id); } } |
註釋無需編碼即可神奇地工作。
起先,系統邏輯呼叫 Customer API 客戶端上的一個方法來檢索客戶資料,而無需重試註釋。
應用註解
@Retryable |
引數也可以由系統屬性指定。但是,如果您想要更動態的東西,這種方法可能不適合您。例如,註解不支援基於產品型別的不同重試設定,除非為每個產品型別指定了單獨的方法呼叫。
在這種情況下,使用命令式風格可以通過動態重試設定來實現這樣的系統需求。
命令的方法——支援動態重試策略
Spring 框架為命令式方法提供了一個實用程式類RetryTemplate。這是一種“侵入性”方法,涉及更改程式程式碼,以便系統邏輯使用RetryTemplate來檢索客戶資料。下圖顯示它類似於宣告式方法中的代理,但是它不是在執行時建立的。
下面的示例程式碼根據產品型別應用不同的重試策略。使用RetryTemplate顯然是一個優勢,因為它允許靈活地自定義重試策略作為系統邏輯的一部分。
此示例程式碼使用宣告式方法實現了與上一個類似的重試邏輯。它展示了根據產品程式碼確定最大嘗試次數的邏輯的靈活性。
private Optional<Product> retrieveProduct(String productCode) { int maxAttempts = productCode.startsWith(TRAVEL_INSURANCE_PREFIX)? 5 : 2; RetryTemplate retryTemplate = RetryTemplate.builder() .maxAttempts(maxAttempts) .retryOn(RuntimeException.class) .exponentialBackoff(300L, 2, 5000L, true) .build(); return retryTemplate.execute(arg -> productSrvClient.getProduct(productCode)); } |
重試資料插入/更新
重試不僅適用於資料查詢。它可以應用於其他過程,例如資料插入/更新的 I/O 操作。想象一下,一個消耗資源並涉及多個步驟的系統程式,您絕對不希望該程式僅僅因為它在程式結束時未能將結果儲存到資料庫中而崩潰。系統偶爾會在 I/O 操作上遇到錯誤並不少見,例如,由於併發訪問可能導致記錄鎖定。當系統再次重試時,I/O 操作將成功完成。
請記住,該操作應該是冪等的。換句話說,多次執行操作時,結果應該是不變的。例如,如果多次執行該操作,則只會在資料庫中插入一條新記錄,而不是重複記錄。
如果記錄的主鍵是由 MySQL 根據自增代理 id 生成的,那麼每次應用程式邏輯儲存一個引用記錄時都會建立一個新記錄。因此,將通過重試建立重複的記錄。
因此,應用程式程式碼應該準備主鍵,而不是依賴 MySQL 中的序列號,以實現冪等的資料插入。報價的樣本資料模型表明報價程式碼為記錄ID。
@Data @Builder @Entity @Table(name = "quotation") public class Quotation { @Id private String quotationCode; private Double amount; @JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime expiryTime; private String productCode; private Long customerId; } |
並且應用邏輯為引用程式碼分配一個唯一的 UUID
Quotation generateAndSaveQuotation(QuotationReq request, Customer customer, Product product) { // generate quotation price Double quotationAmount = generateQuotation(request, customer, product); // Construct quotation Quotation quotation = Quotation.builder() .quotationCode(UUID.randomUUID().toString()) .customerId(customer.getId()) .expiryTime(now.plusMinutes(quotationExpiryTime)) .productCode(request.getProductCode()) .amount(quotationAmount) .build(); // Save into data store return saveQuotation(quotation); } Quotation saveQuotation(Quotation quotation) { RetryTemplate retryTemplate = RetryTemplate.builder() .maxAttempts(3) .fixedBackoff(1000L) .build(); return retryTemplate.execute(arg -> quotationRepo.save(quotation)); } |
為重試邏輯構建自動化測試
對重試邏輯的驗證並不容易。大多數情況下,很難模擬外部服務和資料庫中的錯誤。另一種方法是使用 Mockito 模擬錯誤情況。這是用於驗證save()引用儲存庫中方法的示例單元測試程式碼。
- 場景 1 - 所有嘗試都失敗
模擬重試失敗的場景很簡單,你可以將模擬 bean 配置為在涉及目標方法時始終丟擲異常。
@Test void givenAllRetryOnQuotationSaveExhausted_whenRequestForQuotation_thenThrowException() throws IOException { // GIVEN when(quotationRepo.save(any(Quotation.class))) .thenThrow(new RuntimeException("Mock Exception")); // other mock setup // ... // WHEN Quotation quotation = quotationService.generateQuotation(req); // THEN // verify exception RuntimeException exception = assertThrows(RuntimeException.class, () -> quotationService.generateQuotation(req)); } |
- 場景 2 — 前 2 次嘗試失敗,第三次嘗試成功
更復雜的情況是前 2 次嘗試失敗,然後第 3 次成功。此示例程式碼save()在前 2 次呼叫引用儲存庫方法時模擬異常錯誤,並在第 3 次嘗試時返回引用物件。Mockito 是一個方便的工具來模擬連續的函式呼叫。您可以簡單地連結模擬設定方法 -thenThrow()並按thenAnswer()順序連結。
@Test void givenRetryOnQuotationSaveSuccess_whenRequestForQuotation_thenSuccess() throws IOException, RecordNotFoundException, QuotationCriteriaNotFulfilledException { // GIVEN when(quotationRepo.save(any(Quotation.class))) .thenThrow(new RuntimeException("Mock Exception 1")) .thenThrow(new RuntimeException("Mock Exception 2")) .thenAnswer(invocation -> (Quotation) invocation.getArgument(0)); // other mock setup // ... // WHEN Quotation quotation = quotationService.generateQuotation(req); // THEN // verify quotation // .... } |
請參閱此 GitHub 儲存庫以獲取包含重試邏輯和自動化測試用例的完整應用程式程式碼
相關文章
- 如何使用spring測試模組測試請求功能Spring
- 如何測試重簽名的應用功能是否正常
- Python重試模組retryingPython
- springboot 多模組下的單元測試配置Spring Boot
- 使用SpringBoot實現微服務超時重試模式 - VinsguruSpring Boot微服務模式
- 淺談自動化測試功能模組的分解
- 【Parasoft SOAtest】如何減少重複工作以加速功能測試程式
- 如何構建多模組的SpringBoot專案Spring Boot
- 教你springboot maven多模組如何部署Spring BootMaven
- surging如何使用swagger 元件測試業務模組Swagger元件
- 如何實現優雅的重試?
- 使用 HTTP 模組執行 URL 重寫HTTP
- 如何在SpringBoot中優雅地重試呼叫第三方API?Spring BootAPI
- springboot(十二):springboot如何測試打包部署Spring Boot
- Nginx 中 map 模組的使用及效能測試Nginx
- SpringBoot中使用Docker、Zipkin構建模組化Spring BootDocker
- 如何使用grep命令的排除功能
- 如何使用`open-uri`模組
- 使用Gradle構建多模組SpringBoot專案GradleSpring Boot
- unittest系統(八)一文搞定unittest重試功能
- 測試人員如何提高API功能測試效率?API
- SAP SD模組中POD功能使用方法
- 功能測試中遇到不可重現軟體缺陷的解決策略
- springboot(六):如何優雅的使用mybatisSpring BootMyBatis
- 如何編寫功能測試報告測試報告
- 更好的 java 重試框架 sisyphus 的 3 種使用方式Java框架
- 高階測試:如何使用Flink對Strom任務的邏輯功能進行復現測試?
- 財務模組-總賬功能與明細賬功能使用講解
- 軟體測試中的功能測試和非功能測試
- CRM的功能模組有什麼?
- 鐵威馬NAS教程之如何安裝使用Duple Backup(雙重備份)功能?
- 使用 Laravel 進行商品功能測試Laravel
- 模組測試
- 模組化——高效重構
- Seata RPC 模組的重構之路RPC
- Java程式設計師不喜歡Golang的地方 - GavinJava程式設計師Golang
- SpringBoot如何防止重複提交?- Adrian AdendrataSpring Boot
- python 使用 retrying 重試請求Python