如何使用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專案Spring Boot
- surging如何使用swagger 元件測試業務模組Swagger元件
- 如何使用`open-uri`模組
- 如何測試重簽名的應用功能是否正常
- Seata RPC 模組的重構之路RPC
- SpringBoot - 多模組專案的搭建教程Spring Boot
- SAP SD模組中POD功能使用方法
- CRM的功能模組有什麼?
- springboot-多模組構建Spring Boot
- 如何使用python計數模組counter?Python
- wtforms模組的使用ORM
- pymysql模組的使用MySql
- 使用SpringBoot實現微服務超時重試模式 - VinsguruSpring Boot微服務模式
- 模組測試
- SpringBoot多模組專案中無法注入其他模組中的spring beanSpring BootBean
- IDEA建立SpringBoot的多模組專案教程IdeaSpring Boot
- MT2502模組上如何實現echo loop功能OOP
- CRM系統的功能模組有哪些
- 財務模組-總賬功能與明細賬功能使用講解
- Springboot建立maven多模組專案Spring BootMaven
- 如何使用Python經緯座標模組?Python
- openpyxl模組的日常使用
- Flask:sqlalchemy模組的使用FlaskSQL
- Python logging模組的使用Python
- glom模組的使用(一)
- glom模組的使用(二)
- Python中模組的使用Python
- 微服務總體功能模組微服務
- 搜尋模組功能分析
- 模切ERP系統的功能模組分析——點晴ERP
- Istio所有模組、Service、Pod的功能介紹
- springboot模組化開發專案搭建Spring Boot
- 如何使用cgdb + qemu除錯linux核心模組除錯Linux
- 使用策略模式和簡單工廠模式重寫支付模組模式
- springboot mybatis redis shiro 許可權控制(springboot模組化使用,後臺程式碼已經完成)Spring BootMyBatisRedis