SpringBoot整合Retry的詳細指南
問題背景
在現(xiàn)代的分布式系統(tǒng)中,服務間的調用往往需要處理各種網絡異常、超時等問題。重試機制是一種常見的解決策略,它允許應用程序在網絡故障或臨時性錯誤后自動重新嘗試失敗的操作。Spring Boot 提供了靈活的方式來集成重試機制,這可以通過使用 Spring Retry 模塊來實現(xiàn)。本文將通過一個具體的使用場景來詳細介紹如何在 Spring Boot 應用中集成和使用 Spring Retry 技術。
場景描述
假如我們正在開發(fā)一個OMS系統(tǒng)(訂單管理系統(tǒng)),其中一個關鍵服務 OrderService 負責訂單創(chuàng)建和調用WMS服務扣減庫存API 。由于網絡不穩(wěn)定或外部 API 可能暫時不可用,我們需要在這些情況下實現(xiàn)重試機制,以確保請求的成功率和系統(tǒng)的穩(wěn)定性。
實現(xiàn)步驟
1. 添加依賴
首先,在你的 pom.xml 文件中添加 Spring Retry 和 AOP 的依賴:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- Spring Boot Starter AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2. 創(chuàng)建配置類
創(chuàng)建一個配置類來配置重試模板:
package com.zlp.retry.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* 全局重試配置
*/
@Configuration
public class CustomRetryConfig {
/**
*
* 這段代碼定義了一個 `CustomRetryConfig` 類,其中包含一個 `retryTemplate` 方法。該方法用于創(chuàng)建并配置一個 `RetryTemplate` 對象,該對象用于處理重試邏輯。
*
* 1. 創(chuàng)建 `RetryTemplate` 實例**:創(chuàng)建一個 `RetryTemplate` 對象。
* 2. 設置重試策略:使用 `SimpleRetryPolicy` 設置最大重試次數(shù)為5次。
* 3. 設置延遲策略:使用 `ExponentialBackOffPolicy` 設置初始延遲時間為1000毫秒,每次重試間隔時間乘以2。
* 4. 應用策略:將重試策略和延遲策略應用到 `RetryTemplate` 對象。
*/
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
// 設置重試策略
SimpleRetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(5);
// 設置延遲策略
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000);
backOffPolicy.setMultiplier(2.0);
template.setRetryPolicy(policy);
template.setBackOffPolicy(backOffPolicy);
return template;
}
}
3. 創(chuàng)建服務類
創(chuàng)建一個服務類 OrderService和接口實現(xiàn)類OrderServiceImpl,并在需要重試的方法上使用 @Retryable 注解:
/**
* @Classname OrderService
* @Date 2024/11/18 21:03
* @Created by ZouLiPing
*/
public interface OrderService {
/**
* 創(chuàng)建訂單
* @param createOrderReq
* @return
*/
String createOrder(CreateOrderReq createOrderReq);
}
package com.zlp.retry.service.impl;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.zlp.retry.dto.CreateOrderReq;
import com.zlp.retry.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @Classname OrderServiceImpl
* @Date 2024/11/18 21:06
* @Created by ZouLiPing
*/
@Service
@Slf4j(topic = "OrderServiceImpl")
public class OrderServiceImpl implements OrderService {
@Override
@Retryable(value = {Exception.class},maxAttempts = 4, backoff = @Backoff(delay = 3000))
public String createOrder(CreateOrderReq createOrderReq) {
log.info("createOrder.req createOrderReq:{}", JSON.toJSONString(createOrderReq));
try {
log.info("createOrder.deductStock.調用時間={}", DateUtil.formatDateTime(DateUtil.date()));
// 扣減庫存服務
this.deductStock(createOrderReq);
} catch (Exception e) {
throw new RuntimeException(e);
}
return UUID.randomUUID().toString();
}
/**
* 模擬扣減庫存
*/
private void deductStock(CreateOrderReq createOrderReq) {
throw new RuntimeException("庫存扣減失敗");
}
/**
* 當重試四次仍未能成功創(chuàng)建訂單時調用此方法進行最終處理
*
* @param ex 異常對象,包含重試失敗的原因
* @param createOrderReq 創(chuàng)建訂單的請求對象,包含訂單相關信息
* @return 返回處理結果,此處返回"fail"表示最終失敗
*/
@Recover
public String recover(Exception ex, CreateOrderReq createOrderReq) {
// 記錄重試四次后仍失敗的日志,包括異常信息和訂單請求內容
log.error("recover.resp.重試四次還是失敗.error:{},createOrderReq:{}",ex.getMessage(),JSON.toJSONString(createOrderReq));
// 處理最終失敗的情況
// 可以記錄日志,或者是投遞MQ,采用最終一致性的方式處理
return "fail";
}
}
4. 啟用重試功能
在主配置類或啟動類上添加 @EnableRetry 注解,以啟用重試功能:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@EnableRetry
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5. 驗證重試方法
@RestController
@RequiredArgsConstructor
public class RetryController {
private final OrderService orderService;
@GetMapping("getRetry")
public String retry(){
CreateOrderReq createOrderReq = new CreateOrderReq();
createOrderReq.setOrderId(UUID.randomUUID().toString());
createOrderReq.setProductId("SKU001");
createOrderReq.setCount(10);
createOrderReq.setMoney(100);
return orderService.createOrder(createOrderReq);
}
}
6.執(zhí)行操作說明
- 添加依賴:引入了 Spring Retry 和 AOP 的依賴,以便使用重試功能。
- 配置重試模板:創(chuàng)建了一個配置類
CustomRetryConfig,配置了重試策略,設置最大重試次數(shù)為5次。 - 創(chuàng)建服務類:在
OrderServiceImpl類中,使用@Retryable注解標記了createOrder方法,指定了當發(fā)生Exception時進行重試,最大重試次數(shù)為4次,每次重試間隔3秒。同時,使用@Recover注解標記了recover方法,當所有重試都失敗后,會調用這個方法。 - 啟用重試功能:在主配置類或啟動類上添加
@EnableRetry注解,以啟用重試功能。
Retry執(zhí)行流程
Retry整體流程圖

打印日志
從日志分析每隔3秒鐘會重試一次,直到到達設置最大重試次數(shù),會調用功<font style="color:#DF2A3F;">recover</font>方法中

7.結論
通過以上步驟,我們成功地在 Spring Boot應用中集成了 Spring Retry 技術,實現(xiàn)了服務調用的重試機制。這不僅提高了系統(tǒng)的健壯性和穩(wěn)定性,還減少了因網絡問題或外部服務暫時不可用導致的請求失敗。希望本文對你理解和應用 Spring Boot 中的重試技術有所幫助。
Retry配置的優(yōu)先級規(guī)則
- 方法級別配置:如果某個配置在方法上定義了,則該方法上的配置會覆蓋類級別的配置和全局配置。
- 類級別配置:如果某個配置在類上定義了,并且該類的方法沒有單獨定義配置,則使用類級別的配置。
- 全局配置:如果沒有在方法或類上定義配置,則使用全局配置。
下面通過一個具體的例子來展示這些優(yōu)先級規(guī)則。假設我們有一個服務類 <font style="color:rgb(44, 44, 54);">MyService</font>,其中包含一些方法,并且我們在不同的層次上進行了重試策略的配置。
示例代碼
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
@Retryable(
value = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000) // 類級別的配置
)
public class MyService {
@Retryable(
value = {RuntimeException.class},
maxAttempts = 5,
backoff = @Backoff(delay = 500) // 方法級別的配置
)
public void retryableMethodWithSpecificConfig() {
System.out.println("Retrying with specific config...");
throw new RuntimeException("Simulated exception");
}
@Retryable(
value = {RuntimeException.class}
)
public void retryableMethodWithoutSpecificDelay() {
System.out.println("Retrying without specific delay...");
throw new RuntimeException("Simulated exception");
}
public void nonRetryableMethod() {
System.out.println("This method does not retry.");
throw new RuntimeException("Simulated exception");
}
}
解釋
- retryableMethodWithSpecificConfig
- 方法級別配置:
<font style="color:rgb(44, 44, 54);">maxAttempts = 5</font><font style="color:rgb(44, 44, 54);">backoff.delay = 500</font>
- 這些配置會覆蓋類級別的配置。
- 方法級別配置:
- retryableMethodWithoutSpecificDelay
- 方法級別配置:
<font style="color:rgb(44, 44, 54);">maxAttempts = 3</font>(繼承自類級別)<font style="color:rgb(44, 44, 54);">backoff.delay = 1000</font>(繼承自類級別)
- 這些配置繼承自類級別的配置。
- 方法級別配置:
- nonRetryableMethod
- 該方法沒有使用
<font style="color:rgb(44, 44, 54);">@Retryable</font>注解,因此不會進行重試。
- 該方法沒有使用
全局配置示例
為了進一步說明全局配置的優(yōu)先級,我們可以配置一個全局的重試模板。
配置類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
SimpleRetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(4);
template.setRetryPolicy(policy);
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(750L);
template.setBackOffPolicy(fixedBackOffPolicy);
return template;
}
}
使用全局配置的服務類
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class AnotherService {
@Autowired
private RetryTemplate retryTemplate;
@Retryable(
value = {RuntimeException.class},
maxAttempts = 6, // 方法級別的配置
backoff = @Backoff(delay = 300) // 方法級別的配置
)
public void retryableMethodWithGlobalAndLocalConfig() {
System.out.println("Retrying with global and local config...");
throw new RuntimeException("Simulated exception");
}
@Retryable(
value = {RuntimeException.class}
)
public void retryableMethodWithOnlyGlobalConfig() {
System.out.println("Retrying with only global config...");
throw new RuntimeException("Simulated exception");
}
}
解釋
- retryableMethodWithGlobalAndLocalConfig
- 方法級別配置:
<font style="color:rgb(44, 44, 54);">maxAttempts = 6</font><font style="color:rgb(44, 44, 54);">backoff.delay = 300</font>
- 這些配置會覆蓋全局配置。
- 方法級別配置:
- retryableMethodWithOnlyGlobalConfig
- 全局配置:
<font style="color:rgb(44, 44, 54);">maxAttempts = 4</font><font style="color:rgb(44, 44, 54);">backoff.delay = 750</font>
- 這些配置繼承自全局配置。
- 全局配置:
總結一下,配置的優(yōu)先級從高到低依次是:
- 方法級別配置
- 類級別配置
- 全局配置
希望這個示例能幫助你理解不同層次配置的優(yōu)先級。
什么樣的場景不適合Retry
在使用重試機制時,確實有一些場景不適合應用重試策略。了解這些場景有助于避免不必要的重試操作,從而提高系統(tǒng)的性能和穩(wěn)定性。以下是幾種不適合使用重試機制的常見場景:
- 冪等性不可保證的操作
- 解釋:如果一個操作不是冪等的(即多次執(zhí)行會產生不同的結果),那么重試可能導致數(shù)據(jù)不一致或其他問題。
- 示例:插入數(shù)據(jù)庫記錄的操作通常不是冪等的,因為重復插入會導致重復的數(shù)據(jù)。
- 長時間運行的操作
- 解釋:對于耗時較長的操作,頻繁重試可能會導致系統(tǒng)資源被大量占用,影響其他任務的執(zhí)行。
- 示例:批量處理大數(shù)據(jù)集、長時間計算的任務。
- 外部依賴不穩(wěn)定但無法恢復
- 解釋:某些外部服務或API可能存在根本性的故障,無法通過簡單的重試解決。在這種情況下,重試只會浪費資源。
- 示例:調用第三方支付接口,如果返回的是明確的失敗狀態(tài)碼(如賬戶余額不足),則不應該重試。
- 網絡超時且無可用備用路徑
- 解釋:在網絡請求超時時,如果沒有任何備用路徑或解決方案,重試可能仍然會失敗。
- 示例:嘗試連接到某個特定IP地址的服務,如果該地址一直不通,則重試沒有意義。
- 用戶交互過程中需要立即反饋的操作
- 解釋:在用戶等待響應的過程中,長時間的重試可能導致用戶體驗不佳。
- 示例:提交表單后立即顯示成功消息,如果在此期間發(fā)生錯誤并進行重試,用戶可能會感到困惑。
- 涉及敏感信息的操作
- 解釋:對于涉及敏感信息的操作(如密碼修改、資金轉賬),重試可能會導致敏感信息泄露或重復操作。
- 示例:更新用戶的銀行賬戶信息,一旦確認操作完成,不應再進行重試。
- 事務邊界內的操作
- 解釋:在事務邊界內,重試可能會導致事務沖突或回滾,增加復雜性。
- 示例:在一個復雜的數(shù)據(jù)庫事務中,部分操作失敗后進行重試可能導致整個事務失敗。
- 已知的永久性錯誤
- 解釋:如果能夠明確判斷出錯誤是永久性的(如配置錯誤、代碼bug),重試不會解決問題。
- 示例:嘗試讀取不存在的文件,這種錯誤通常是永久性的。
- 高并發(fā)環(huán)境下的寫操作
- 解釋:在高并發(fā)環(huán)境下,頻繁的重試可能會加劇數(shù)據(jù)庫負載,導致更多的鎖競爭和死鎖。
- 示例:在電商網站的下單高峰期,對庫存的減少操作不宜頻繁重試。
示例代碼
為了更好地理解這些原則,下面是一個簡單的示例,展示了如何在Spring Boot中使用<font style="color:rgb(44, 44, 54);">@Retryable</font>注解,并根據(jù)上述原則決定哪些操作適合重試。
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
// 適合重試的操作:冪等性強、短時間操作、可恢復的錯誤
@Retryable(
value = {RuntimeException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000)
)
public void retryableFetchData() {
System.out.println("Fetching data...");
// 模擬網絡請求或短暫的外部服務調用
if (Math.random() > 0.5) {
throw new RuntimeException("Simulated transient network error");
}
}
// 不適合重試的操作:冪等性不可保證、長時間運行
public void nonRetryableLongRunningTask() {
System.out.println("Starting long-running task...");
try {
Thread.sleep(10000); // 模擬長時間運行的任務
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Long-running task completed.");
}
// 不適合重試的操作:涉及敏感信息
public void updateSensitiveInformation(String sensitiveData) {
System.out.println("Updating sensitive information...");
// 這里假設更新操作不是冪等的,也不應該重試
throw new RuntimeException("Simulated failure in updating sensitive information");
}
// 不適合重試的操作:已知的永久性錯誤
public void fetchDataFromNonExistentResource() {
System.out.println("Fetching data from a non-existent resource...");
throw new RuntimeException("Permanent error: Resource not found");
}
}
解釋
- retryableFetchData
- 適用條件:
- 冪等性強:每次請求的結果相同。
- 短時間操作:模擬網絡請求或短暫的外部服務調用。
- 可恢復的錯誤:模擬暫時的網絡錯誤。
- 重試策略:
- 最大重試次數(shù)為3次。
- 每次重試間隔1秒。
- 適用條件:
- nonRetryableLongRunningTask
- 不適用原因:
- 長時間運行:模擬長時間運行的任務。
- 重試可能導致資源過度消耗。
- 不適用原因:
- updateSensitiveInformation
- 不適用原因:
- 涉及敏感信息:更新操作不是冪等的,也不應該重試。
- 重試可能導致數(shù)據(jù)不一致或其他安全問題。
- 不適用原因:
- fetchDataFromNonExistentResource
- 不適用原因:
- 已知的永久性錯誤:資源不存在,重試不會解決問題。
- 不適用原因:
通過這些示例,你可以更好地理解哪些操作適合重試以及為什么某些操作不適合重試。
以上就是SpringBoot整合Retry的詳細指南的詳細內容,更多關于SpringBoot整合Retry的資料請關注腳本之家其它相關文章!
相關文章
Jenkins Pipeline 部署 SpringBoot 應用的教程詳解
這篇文章主要介紹了Jenkins Pipeline 部署 SpringBoot 應用的詳細教程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
詳解Java的call by value和call by reference
在本篇文章里小編給大家總結了關于Java的call by value和call by reference的相關用法和知識點內容,需要的朋友們學習下。2019-03-03
Java數(shù)據(jù)庫連接PreparedStatement的使用詳解
這篇文章主要介紹了Java數(shù)據(jù)庫連接PreparedStatement的使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
springboot3.x版本集成log4j遇到Logging?system?failed?to?initial
使用Springboot?3.x集成Log4j時可能會遇到版本沖突的問題,這通常可以通過檢查Maven依賴樹來識別,一旦發(fā)現(xiàn)沖突,將Log4j的版本統(tǒng)一更新到最新的兼容版本,例如2.21.1,即可解決問題,此方法有效解決了日志打印錯誤,是處理類似問題的一個實用參考2024-09-09
SpringBoot如何配置數(shù)據(jù)庫主從shardingsphere
這篇文章主要介紹了SpringBoot如何配置數(shù)據(jù)庫主從shardingsphere問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
解決使用this.getClass().getResource()獲取文件時遇到的坑
這篇文章主要介紹了解決使用this.getClass().getResource()獲取文件時遇到的坑問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12

