Java接口請(qǐng)求重試機(jī)制的幾種常見方法
1.幾種方法

1.1循環(huán)重試
它的基本思路是:
- 定義重試次數(shù),如最大重試5次
- 發(fā)送請(qǐng)求,如果失敗則進(jìn)入重試邏輯
- 在循環(huán)內(nèi)部,記錄當(dāng)前已重試次數(shù),如當(dāng)前已重試2次
- 判斷當(dāng)前重試次數(shù)是否達(dá)到最大次數(shù),如果達(dá)到則終止循環(huán),否則進(jìn)行重試
- 在循環(huán)內(nèi)部,可以添加定時(shí)重試間隔,也可以使用指數(shù)退避算法
- 發(fā)送重試請(qǐng)求,重復(fù)判斷是否成功,直到成功、達(dá)到最大次數(shù)或其他終止條件
示例
public class Retry {
private static final int MAX_RETRIES = 5;
public static Response request() throws Exception {
int retries = 0;
while (true) {
try {
// 發(fā)送請(qǐng)求,返回響應(yīng)
Response response = HttpClient.sendRequest();
// 請(qǐng)求成功則返回響應(yīng)
if (response.isSuccess()) {
return response;
}
} catch (Exception e) {
// 請(qǐng)求失敗進(jìn)行重試
}
// 判斷是否超過最大重試次數(shù)
if (++retries >= MAX_RETRIES) {
throw new Exception("Exceeded max retries");
}
// 增加間隔后重試
int interval = (int) (Math.random() * 1000);
Thread.sleep(interval);
}
}
public static void main(String[] args) throws Exception {
Response response = request();
// ...
}
}
1.2 使用Spring Retry庫
使用 Spring Retry 庫可以很方便地實(shí)現(xiàn)接口請(qǐng)求的重試機(jī)制。
1.2.1 添加 Maven 依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
1.2.2 添加 @EnableRetry 注解啟用重試功能
1.2.3 在需要重試的方法上添加 @Retryable 注解
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 5000))
public User getUser(String id) {
// 遠(yuǎn)程調(diào)用接口
}
@Retryable 定義了重試規(guī)則:- value - 重試的異常類型
- maxAttempts - 最大重試次數(shù)
- backoff - 重試等待策略
1.2.4. 還可以自定義 RetryTemplate 進(jìn)行更復(fù)雜的重試控制
RetryTemplate template = new RetryTemplate();
template.execute(context -> {
// 可在此處自定義重試邏輯
return remoteClient.invoke();
});
Spring Retry 為接口請(qǐng)求重試提供了完善和易用的解決方案,可以靈活控制各種重試參數(shù),適用于復(fù)雜系統(tǒng)的容錯(cuò)要求。
1.3 并發(fā)框架異步重試
使用并發(fā)框架的異步請(qǐng)求方式可以較簡單地實(shí)現(xiàn)接口請(qǐng)求的重試機(jī)制。以CompletableFuture為例:
1.3.1 發(fā)送請(qǐng)求使用CompletableFuture封裝:
CompletableFuture<Response> future = CompletableFuture.supplyAsync(() -> {
return service.call();
});
1.3.2 當(dāng)請(qǐng)求失敗時(shí),使用retryAsync自動(dòng)完成重試:
future = future.exceptionally(e -> {
return service.retryAsync();
});
1.3.3 可以鏈?zhǔn)秸{(diào)用,自定義重試邏輯:
future
.exceptionally(e -> {
// 處理異常
})
.thenApplyAsync(resp -> {
// 處理響應(yīng)
})
.retryAsync(retryCount, delay);
主要優(yōu)點(diǎn)是:
- 線程安全的異步請(qǐng)求
- 自動(dòng)重試失敗任務(wù)
- 簡潔的鏈?zhǔn)骄幊谭绞?/li>
- 避免阻塞主線程
使用并發(fā)框架可以便捷地實(shí)現(xiàn)異步重試機(jī)制,提高系統(tǒng)容錯(cuò)性。其他框架如RxJava也有類似的重試機(jī)制。
1.4 消息隊(duì)列重試
使用消息隊(duì)列可以實(shí)現(xiàn)接口請(qǐng)求的異步重試機(jī)制。
基本思路是:
接口請(qǐng)求發(fā)送失敗后,將請(qǐng)求信息封裝為消息,發(fā)送到請(qǐng)求重試的隊(duì)列中。
消息消費(fèi)者從隊(duì)列中獲取失敗的請(qǐng)求,根據(jù)策略進(jìn)行重試。
重復(fù)重試直到成功、重試次數(shù)用盡或其他終止條件。
成功后將消息移除隊(duì)列,失敗則保留消息供再次重試。
主要步驟:
創(chuàng)建請(qǐng)求重試隊(duì)列,如“request.retry.queue”
接口請(qǐng)求失敗后,生成重試消息,發(fā)送到隊(duì)列
消費(fèi)者啟動(dòng)線程從隊(duì)列中取消息重試
根據(jù)重試策略進(jìn)行定時(shí)重試或最大重試數(shù)
成功則確認(rèn)消息,失敗則重新入隊(duì)
使用消息隊(duì)列進(jìn)行重試有利于:
- 異步重試,不阻塞主線程
- 可靠地完成重試任務(wù)
- 靈活控制重試策略
示例
// 1. 創(chuàng)建隊(duì)列
Queue retryQueue = new Queue("request.retry.queue");
// 2. 請(qǐng)求失敗,發(fā)送重試消息
public void request() {
try {
// 調(diào)用接口
httpClient.post(url, payload);
} catch (Exception e) {
// 發(fā)送重試消息
Message msg = new Message(url, payload, maxRetries);
retryQueue.send(msg);
}
}
// 3. 消費(fèi)者線程進(jìn)行重試
class RetryConsumer implements Runnable {
public void run() {
while (true) {
Message msg = retryQueue.take();
for (int i = 0; i < msg.getMaxRetries(); i++) {
try {
// 重試請(qǐng)求
httpClient.post(msg.getUrl(), msg.getPayload());
// 請(qǐng)求成功,結(jié)束循環(huán)
break;
} catch (Exception e) {
// 等待后繼續(xù)重試
}
}
// 重試完成后,確認(rèn)消息
retryQueue.confirm(msg);
}
}
}
這就是使用消息隊(duì)列實(shí)現(xiàn)接口重試的基本流程,可以根據(jù)需求擴(kuò)展重試策略、異常處理等邏輯。
1.5 自定義重試工具類
使用自定義的重試工具類來實(shí)現(xiàn)接口請(qǐng)求的重試機(jī)制,提高代碼的復(fù)用性和可維護(hù)性。
重試工具類的實(shí)現(xiàn)思路:
- 提供重試方法,參數(shù)包括請(qǐng)求函數(shù)、重試策略等
- 在重試方法內(nèi)部執(zhí)行循環(huán)請(qǐng)求
- 每次請(qǐng)求失敗時(shí),根據(jù)策略等待一段時(shí)間
- 記錄當(dāng)前重試次數(shù),與最大次數(shù)比較
- 請(qǐng)求成功或者達(dá)到最大重試次數(shù)則結(jié)束循環(huán)
示例:
public class RetryUtil {
public static <T> T retry(RetryCallable<T> callable, RetryPolicy policy) {
int retries = 0;
while(true) {
try {
return callable.call();
} catch(Exception e) {
if (retries >= policy.maxRetries) {
throw e;
}
// 等待
policy.delay();
// 重試次數(shù)加1
retries++;
}
}
}
}
// 執(zhí)行請(qǐng)求的函數(shù)接口
interface RetryCallable<T> {
T call();
}
// 重試策略
class RetryPolicy {
int maxRetries;
int delay;
}
// 使用示例
RetryUtil.retry(() -> {
// 接口請(qǐng)求
return httpClient.get(url);
}, policy);
這樣可以提高重試相關(guān)邏輯的復(fù)用性,避免寫重復(fù)代碼。
1.6 使用遞歸結(jié)構(gòu)
使用遞歸結(jié)構(gòu)也可以實(shí)現(xiàn)接口請(qǐng)求的重試機(jī)制。
基本思路是設(shè)計(jì)一個(gè)遞歸函數(shù),在函數(shù)內(nèi)部發(fā)送請(qǐng)求,如果失敗則繼續(xù)遞歸調(diào)用自身再次重試。
示例:
public class RetryRequest {
private static final int MAX_RETRIES = 3;
public static Response request(int retries) {
try {
// 發(fā)送請(qǐng)求
Response response = HttpClient.get("http://example.com");
return response;
} catch (Exception e) {
// 處理異常
// 判斷是否需要重試
if (retries < MAX_RETRIES) {
// 增加重試次數(shù)
retries++;
// 延遲1秒鐘
Thread.sleep(1000);
// 遞歸調(diào)用自身進(jìn)行重試
return request(retries);
}
// 重試失敗
throw new RuntimeException("Request failed after " + MAX_RETRIES + " retries!");
}
}
public static void main(String[] args) {
Response response = request(0);
// 處理響應(yīng)
}
}
主要邏輯是通過遞歸不斷調(diào)用自身來實(shí)現(xiàn)重試。優(yōu)點(diǎn)是邏輯較簡單清晰,缺點(diǎn)是遞歸層次過深時(shí)可能會(huì)導(dǎo)致堆棧溢出。需要合理設(shè)置最大遞歸深度,也可以通過循環(huán)改寫遞歸來避免深層遞歸。
1.7 使用Resilience4j
Resilience4j是一個(gè)很好的Java重試庫,可以用它來實(shí)現(xiàn)接口請(qǐng)求的重試機(jī)制。
主要步驟:
1.7.1添加Resilience4j依賴
<dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> </dependency>
1.7.2 定義重試邏輯
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(500))
.build();
Retry retry = Retry.of("backend", config);
1.7.3 使用重試邏輯調(diào)用接口
String result = retry.executeSupplier(() -> {
// 發(fā)送請(qǐng)求
return backendService.callAPI();
});
1.7.4 自定義重試異常predicate
RetryConfig config = RetryConfig.custom() .retryOnException(e -> isRetryable(e)) .build();
Resilience4j提供了線程安全的重試 decorator,可以通過配置靈活控制重試策略,很好地支持了接口請(qǐng)求重試。
1.8 使用網(wǎng)絡(luò)工具重試
我們常用的一些網(wǎng)絡(luò)工具來做重試
示例
public class RetryExample {
private static final int MAX_RETRIES = 3;
public static String request(String url) throws Exception {
int retries = 0;
while (true) {
try {
// 使用HttpClient發(fā)送請(qǐng)求
return HttpClientUtils.get(url);
} catch (Exception e) {
if (retries >= MAX_RETRIES) {
throw e;
}
// 增加重試次數(shù)
retries++;
// 延遲1秒鐘
TimeUnit.SECONDS.sleep(1);
}
}
}
public static void main(String[] args) throws Exception {
String result = request("http://example.com/api");
System.out.println(result);
}
}
// 網(wǎng)絡(luò)工具類
class HttpClientUtils {
public static String get(String url) throws IOException {
// 發(fā)送GET請(qǐng)求并返回結(jié)果
}
}
主要通過循環(huán)和網(wǎng)絡(luò)工具類來實(shí)現(xiàn)重試邏輯,延時(shí)控制也可以用Random來實(shí)現(xiàn)指數(shù)退避。這種 utilities + 循環(huán) 的組合可以實(shí)現(xiàn)靈活可復(fù)用的重試機(jī)制。
2.注意事項(xiàng)

接口請(qǐng)求重試時(shí)需要注意以下幾點(diǎn):
2.1 冪等性
接口需要是冪等的,多次調(diào)用結(jié)果相同,避免重復(fù)執(zhí)行帶來副作用。
2.2 資源競爭
重試可能對(duì)服務(wù)端造成更大壓力,需要考慮限流等措施。
2.3 超時(shí)設(shè)置
合理設(shè)置重試最大次數(shù)和總超時(shí)時(shí)間,避免長時(shí)間等待。
2.4 重試條件
明確哪些異常情況下需要重試,不能無腦重試所有錯(cuò)誤。
2.5 數(shù)據(jù)一致性
請(qǐng)求成功后要冪等更新狀態(tài),避免重復(fù)數(shù)據(jù)。
2.6 異步機(jī)制
重試過程不要阻塞主業(yè)務(wù)線程。
2.7 退避策略
失敗后延遲一段時(shí)間再重試,可選避免集群重試。
2.8 日志記錄
記錄重試的次數(shù)、錯(cuò)誤原因等信息,方便排查問題。
2.9 容錯(cuò)機(jī)制
重試失敗后的降級(jí)處理,避免級(jí)聯(lián)失敗。
總結(jié)
接口請(qǐng)求重試機(jī)制對(duì)保證系統(tǒng)高可用非常關(guān)鍵,需要根據(jù)業(yè)務(wù)需求選擇合適的重試策略。常用的組合策略包括帶最大次數(shù)的定時(shí)/指數(shù)退避重試、故障轉(zhuǎn)移重試等。重試機(jī)制需要綜合設(shè)置以達(dá)到容錯(cuò)效果 yet又避免產(chǎn)生過大的系統(tǒng)負(fù)載。
相關(guān)文章
Java如何取掉json數(shù)據(jù)中值為null的屬性字段
這篇文章主要介紹了Java如何取掉json數(shù)據(jù)中值為null的屬性字段,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring源碼如何修改Bean的屬性用到的相關(guān)類
這篇文章主要介紹了Spring源碼如何修改Bean的屬性用到的相關(guān)類,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
@AutoConfigurationPackage與@ComponentScan注解區(qū)別
Spring Boot Starter 自動(dòng)裝配原理全解析

