Java中HTTP接口請求重試的實(shí)現(xiàn)方式
1、前言
HTTP接口請求重試是指在請求失敗時(shí),再次發(fā)起請求的機(jī)制。在實(shí)際應(yīng)用中,由于網(wǎng)絡(luò)波動、服務(wù)器故障等原因,HTTP接口請求可能會失敗。為了保證系統(tǒng)的可用性和穩(wěn)定性,需要對HTTP接口請求進(jìn)行重試。
2、實(shí)現(xiàn)方式
今天給大家分享一些常見的接口請求重試的方式。本地模擬了一個(gè)請求接口,后面的代碼示例均模擬請求該接口:
@GetMapping("http_test")
public String getHttpTest(){
return "接口請求成功,返回:OK";
}2.1、循環(huán)重試
循環(huán)重試是最簡單最粗暴的方式,就是在請求接口代碼中加入循環(huán)機(jī)制,如果接口請求失敗,則循環(huán)繼續(xù)發(fā)起接口請求,直到請求成功或接口重試次數(shù)達(dá)到上限。如果請求成功,則不進(jìn)行重試。
簡單代碼示例如下:
@GetMapping("retry_demo_loop")
public String retry_demo_loop(){
// 重試上限次數(shù)為3次
int maxRetryTime = 3;
String result = null;
// 接口循環(huán)請求
for (int i = 1; i <= maxRetryTime; i++) {
try {
// 模擬請求接口
result = HttpUtil.get("http://localhost:8080/http_test");
// 模擬一次請求失敗
if(i == 1){
int co = i / 0;
}
// 請求成功,跳出循環(huán)
break;
} catch (Exception e) {
log.error("接口請求異常,進(jìn)行第{}次重試", i);
result = "接口請求失敗,請聯(lián)系管理員";
}
}
return result;
}請求結(jié)果:

重試日志打?。?/p>

2.2、遞歸重試
除了循環(huán),還可以使用遞歸來實(shí)現(xiàn)接口的請求重試。遞歸是我們都比較熟悉的編程技巧,在請求接口的方法中調(diào)用自身,如果請求失敗則繼續(xù)調(diào)用,直到請求成功或達(dá)到最大重試次數(shù)。
@GetMapping("retry_demo_rec")
public String retry_demo_rec(){
// 重試上限次數(shù)為3次
int maxRetryTime = 3;
return retryRequest(maxRetryTime);
}
/**
* 遞歸方法
* @param maxRetryTime
* @return
*/
private String retryRequest(int maxRetryTime){
if (maxRetryTime <= 0) {
return "接口請求失敗,請聯(lián)系管理員";
}
int retryTime = 0;
try {
// 模擬請求接口
String result = HttpUtil.get("http://localhost:8080/http_test");
// 模擬一次請求失敗
if(maxRetryTime == 3){
int co = 1 / 0;
}
return result;
} catch (Exception e) {
// 處理異常
log.error("接口請求異常,進(jìn)行第{}次重試", ++retryTime);
return retryRequest(maxRetryTime - 1);
}
}請求結(jié)果:

重試日志打?。?/p>

2.3、Spring Retry
第三種便是使用Spring Retry依賴實(shí)現(xiàn)。首先我們需要集成相關(guān)依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- 由于retry使用到了aop,所以還需要加入aop依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>加入@EnableRetry啟動:
@EnableRetry
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}添加retry方法注解:
public interface MyRetryService {
/**
* retryable注解表示該方法需要重試
* value:出現(xiàn)該指定異常后,進(jìn)行重試
* maxAttempts:重試次數(shù)上限,這里指定為3次
* backoff:重試策略,這里指定200ms間隔一次
* @param code
* @return
* @throws Exception
*/
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(200))
String retry(int code) throws Exception;
/**
* 當(dāng)重試達(dá)到上限后還是失敗,則作為異?;卣{(diào)方法
* @param th
* @param code
* @return
*/
@Recover
String recover(Throwable th, int code);
}MyReretService實(shí)現(xiàn)類:
@Slf4j
@Service
public class MyRetryServiceImpl implements MyRetryService {
@Override
public String retry(int code) throws Exception {
log.info("請求retry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請求異常");
}
return result;
}
@Override
public String recover(Throwable th, int code) {
log.error("回調(diào)方法執(zhí)行?。。。?);
return "異常碼為:" + code + ",異常信息:" + th.getMessage();
}
}Controller:
@Autowired
private MyRetryService myRetryService;
/**
* 當(dāng)請求code參數(shù)為200時(shí),直接成功
* 當(dāng)code參數(shù)!=200時(shí),會出發(fā)重試
* @param code
* @return
* @throws Exception
*/
@GetMapping("retry_demo_spring_retry")
public String retry_demo_spring_retry(Integer code) throws Exception {
return myRetryService.retry(code);
}訪問地址:http://localhost:8080/retry_demo_spring_retry?code=123
查看結(jié)果:可以看到接口重試了3次,最后執(zhí)行了@Recover方法最后的回調(diào)。


2.4、Resilience4j
Resilience4j是一個(gè)輕量級、易于使用的輕量級“容錯(cuò)”包。它受Neflix Hystrix啟發(fā)但只有一個(gè)依賴(Vavr),而不像Hystrix很多很多的依賴。同時(shí)它是一個(gè) Java 庫,可以幫助我們構(gòu)建彈性和容錯(cuò)的應(yīng)用程序。Resilience4j在“容錯(cuò)”方面提供了各種模式:斷路器(Circuit Breaker)、重試(Retry)、限時(shí)器(Time Limiter)、限流器(Rate Limiter)、隔板(BulkHead)。我們今天討論的話題是重試,那么今天就來演示下Retry。
首先,添加相應(yīng)依賴:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>2.1.0</version>
</dependency>application.yml配置相關(guān)策略,配置官方文檔:https://resilience4j.readme.io/docs/retry
resilience4j:
retry:
instances:
retry_demo:
max-attempts: 3 # 重試的上限次數(shù)
wait-duration: 1s # 重試的間隔時(shí)間,配置為1s我們改造一下上面spring-retry的demo。
controller:
@GetMapping("retry_demo_spring_retry")
@Retry(name = "retry_demo", fallbackMethod = "recover")
public String retry_demo_spring_retry(Integer code) throws Exception {
return myRetryService.retry(code);
}
public String recover(Throwable th) {
log.error("回調(diào)方法執(zhí)行?。。?!");
return "異常信息:" + th.getMessage();
}myRetryService:
@Override
public String retry(int code) throws Exception {
log.info("請求retry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請求異常");
}
return result;
}程序執(zhí)行,打印結(jié)果:


同樣接口請求了3次,均失敗后執(zhí)行了fallback回調(diào)方法。
2.5、http請求網(wǎng)絡(luò)工具內(nèi)置重試方式
通常一些外部的http網(wǎng)絡(luò)工具,都會內(nèi)置一些重試的策略。如Apache HttpClient。這里以httpclient5為例。
首先添加依賴:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1.4</version>
</dependency>定義HttpClient相關(guān)類,指定重試策略??梢允褂媚J(rèn)的DefaultHttpRequestRetryStrategy,也可以自定義重試策略CustomRetryStrategy。
private static volatile CloseableHttpClient HTTP_CLIENT = null;
static {
if(HTTP_CLIENT == null){
synchronized (HelloWorldController.class) {
if(HTTP_CLIENT == null){
HTTP_CLIENT = HttpClients.custom()
// 設(shè)置重試策略
// .setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.NEG_ONE_SECOND))
// 自定義重試策略
.setRetryStrategy(new CustomRetryStrategy())
.build();
}
}
}
}CustomRetryStrategy:
public static class CustomRetryStrategy implements HttpRequestRetryStrategy {
@Override
public boolean retryRequest(HttpRequest httpRequest, IOException e, int executeCount, HttpContext httpContext) {
return false;
}
@Override
public boolean retryRequest(HttpResponse httpResponse, int executeCount, HttpContext httpContext) {
System.out.println("進(jìn)入重試策略");
if(executeCount > 3){
System.out.println("重試超過3次,終止重試");
return false;
}
if(httpResponse.getCode() != 200){
System.out.println("http狀態(tài)碼不等于200,進(jìn)行重試");
return true;
}
// 其他情況,不重試
return false;
}
@Override
public TimeValue getRetryInterval(HttpResponse httpResponse, int executeCount, HttpContext httpContext) {
return null;
}
}Controller代碼:
@GetMapping("retry_demo_httpclient")
public String retry_demo_httpclient(Integer code) throws Exception {
return httpclientRetry(code);
}
private String httpclientRetry(int code) throws Exception {
log.info("請求retry接口");
// 這里模擬了一個(gè)不存在的地址
HttpGet request = new HttpGet("http://localhost:8080/http_test1");
CloseableHttpResponse httpResponse = HTTP_CLIENT.execute(request);
String result = IoUtil.read(httpResponse.getEntity().getContent()).toString();
if(code != 200){
throw new Exception("接口請求異常");
}
return result;
}訪問接口地址:http://localhost:8080/retry_demo_httpclient?code=200。查看控制臺日志打?。?/p>

2.6、自定義重試工具
裝X的話,我們還可以自定義我們的重試工具。其實(shí)無非以下幾個(gè)步驟:
- 自定義重試的工具類
- 接收一個(gè)方法調(diào)用,并對該方法進(jìn)行異常捕獲
- 如果捕獲了該異常,則進(jìn)行一定間隔,然后重新請求
- 記錄請求次數(shù),如果超過上限,則提示異常信息
直接定義一個(gè)重試的工具類RetryUtil.java:
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
@Slf4j
public class RetryUtil {
/**
* 重試方法
* @param invokeFunc 原方法調(diào)用
* @param maxAttempts 重試次數(shù)上限
* @param deplay 重試的間隔時(shí)間
* @param timeUnit 重試的間隔時(shí)間單位
* @param faultFunc 如果超過重試上限次數(shù),那么會執(zhí)行該錯(cuò)誤回調(diào)方法
* @return
* @param <T>
*/
public static <T> T retry(Supplier<T> invokeFunc, int maxAttempts, long deplay, TimeUnit timeUnit, Function<Throwable, T> faultFunc) {
AtomicInteger retryTimes = new AtomicInteger(0);
for(;;) {
try{
return invokeFunc.get();
} catch (Throwable th) {
if(retryTimes.get() > maxAttempts){
log.error("重試次數(shù)超過{}次,進(jìn)入失敗回調(diào)", retryTimes.get());
return faultFunc.apply(th);
}
ThreadUtil.sleep(deplay, timeUnit);
retryTimes.getAndAdd(1);
}
}
}
}工具類使用:
@GetMapping("retry_demo_custom")
public String retry_demo_custom(Integer code) {
return RetryUtil.retry(() -> {
String result = null;
try {
result = customRetry(code);
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}, 3, 1000, TimeUnit.MILLISECONDS, Throwable::getMessage);
}
private String customRetry(int code) throws Exception {
log.info("請求customRetry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請求異常");
}
return result;
}執(zhí)行完后,訪問地址:http://localhost:8080/retry_demo_custom?code=2001

這里只是簡單的進(jìn)行了定義,如果項(xiàng)目中使用肯定需要考慮更復(fù)雜的因素。如進(jìn)入重試時(shí)不一定只有異常的時(shí)候需要重試,可以指定重試策略,然后制定進(jìn)入重試策略的規(guī)則。
2.7、并發(fā)框架異步重試
在 Java 并發(fā)框架中,異步重試通常涉及到使用線程池和定時(shí)器,以便在異步任務(wù)失敗后進(jìn)行重試。以下是一個(gè)簡單的示例,演示了如何使用 CompletableFuture、ScheduledExecutorService 和 CompletableFuture.supplyAsync 來實(shí)現(xiàn)異步任務(wù)的重試。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AsyncRetryExample {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public static void main(String[] args) {
// 示例異步任務(wù),這里使用 supplyAsync,你可以根據(jù)實(shí)際情況選擇其他異步任務(wù)
CompletableFuture<String> asyncTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Task"));
// 異步任務(wù)失敗后的重試邏輯
retryAsyncTask(asyncTask, 3, 1, TimeUnit.SECONDS);
}
private static CompletableFuture<String> performAsyncTask(String taskName) {
// 模擬異步任務(wù),這里可以是任何異步操作
System.out.println("Performing async task: " + taskName);
// 這里模擬任務(wù)失敗的情況
throw new RuntimeException("Task failed");
}
private static <T> void retryAsyncTask(CompletableFuture<T> asyncTask, int maxRetries, long delay, TimeUnit timeUnit) {
asyncTask.exceptionally(throwable -> {
// 異步任務(wù)失敗后的處理邏輯
System.out.println("Task failed: " + throwable.getMessage());
// 重試邏輯
if (maxRetries > 0) {
System.out.println("Retrying...");
CompletableFuture<T> retryTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Retry Task"));
// 遞歸調(diào)用,進(jìn)行重試
retryAsyncTask(retryTask, maxRetries - 1, delay, timeUnit);
} else {
System.out.println("Max retries reached. Task failed.");
}
return null; // 必須返回 null,否則會影響鏈?zhǔn)秸{(diào)用
});
}
}示例中,performAsyncTask 模擬了一個(gè)異步任務(wù),如果任務(wù)失敗,它會拋出一個(gè)運(yùn)行時(shí)異常。retryAsyncTask 方法用于處理異步任務(wù)的失敗情況,并進(jìn)行重試。在重試時(shí),它使用 CompletableFuture.supplyAsync 創(chuàng)建一個(gè)新的異步任務(wù),模擬了重試的過程。請注意,這只是一個(gè)簡單的示例,實(shí)際應(yīng)用中可能需要更復(fù)雜的重試策略和錯(cuò)誤處理邏輯。
2.8、消息隊(duì)列
網(wǎng)上還有一種消息隊(duì)列的方式來實(shí)現(xiàn),這里沒過多的去研究過,目前以上幾種方式應(yīng)該也是夠用的了。這里直接貼出網(wǎng)上的部分代碼,使用 RabbitMQ 作為消息隊(duì)列,演示了請求重試的實(shí)現(xiàn):
首先添加依賴:
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.13.1</version>
</dependency>
</dependencies>然后,創(chuàng)建一個(gè)發(fā)送者和接收者類:
消息發(fā)送者(Producer)
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MessageProducer {
private static final String QUEUE_NAME = "retry_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 模擬發(fā)送請求
String request = "Your request data";
// 將請求發(fā)送到隊(duì)列
channel.basicPublish("", QUEUE_NAME, null, request.getBytes());
System.out.println(" [x] Sent '" + request + "'");
}
}
}消息接收者(Consumer)
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MessageConsumer {
private static final String QUEUE_NAME = "retry_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 設(shè)置消息監(jiān)聽器
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String request = new String(delivery.getBody(), "UTF-8");
// 模擬處理請求,這里可能會出現(xiàn)處理失敗的情況
boolean processingSucceeded = processRequest(request);
if (processingSucceeded) {
System.out.println(" [x] Received and processed: '" + request + "'");
} else {
// 處理失敗,將請求重新放入隊(duì)列,進(jìn)行重試
channel.basicPublish("", QUEUE_NAME, null, delivery.getBody());
System.out.println(" [x] Processing failed. Retrying: '" + request + "'");
}
};
// 消費(fèi)消息
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
});
}
}
private static boolean processRequest(String request) {
// 模擬處理請求的方法
// 在實(shí)際應(yīng)用中,這里應(yīng)該是對請求的處理邏輯
// 返回 true 表示處理成功,返回 false 表示處理失敗,需要進(jìn)行重試
// 這里簡單地模擬了一個(gè)失敗的情況
return !request.equals("Your request data");
}
}示例中,消息發(fā)送者(MessageProducer)將請求發(fā)送到名為 "retry_queue" 的隊(duì)列中。消息接收者(MessageConsumer)監(jiān)聽隊(duì)列,當(dāng)接收到消息時(shí),模擬處理請求的邏輯。如果處理失敗,將請求重新放入隊(duì)列進(jìn)行重試。
3、小結(jié)
接口請求重試機(jī)制對保證系統(tǒng)高可用非常關(guān)鍵,需要根據(jù)業(yè)務(wù)需求選擇合適的重試策略。常用的組合策略包括帶最大次數(shù)的定時(shí)/指數(shù)退避重試、故障轉(zhuǎn)移重試等。重試機(jī)制需要綜合設(shè)置以達(dá)到容錯(cuò)效果 又避免產(chǎn)生過大的系統(tǒng)負(fù)載。
以上就是Java中HTTP接口請求重試的實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于Java HTTP接口請求重試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺析SpringBoot微服務(wù)中異步調(diào)用數(shù)據(jù)提交數(shù)據(jù)庫的問題
這篇文章主要介紹了SpringBoot微服務(wù)中異步調(diào)用數(shù)據(jù)提交數(shù)據(jù)庫的問題,今天本文涉及到的知識點(diǎn)不難,都是很簡單的crud操作,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
springboot jdbctemplate如何實(shí)現(xiàn)多數(shù)據(jù)源
這篇文章主要介紹了springboot jdbctemplate如何實(shí)現(xiàn)多數(shù)據(jù)源問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
Java?數(shù)據(jù)結(jié)構(gòu)與算法系列精講之貪心算法
我們可能在好多地方都會聽到貪心算法這一概念,并且它的算法思想也比較簡單就是說算法只保證局部最優(yōu),進(jìn)而達(dá)到全局最優(yōu)。但我們實(shí)際編程的過程中用的并不是很多,究其原因可能是貪心算法使用的條件比較苛刻,所要解決的問題必須滿足貪心選擇性質(zhì)2022-02-02
java無鎖hashmap原理與實(shí)現(xiàn)詳解
本文主要介紹了java無鎖hashmap原理與實(shí)現(xiàn),大家參考使用吧2014-01-01
java中continue和break區(qū)別詳細(xì)解析
break和continue都是跳轉(zhuǎn)語句,它們將程序的控制權(quán)轉(zhuǎn)移到程序的另一部分,下面這篇文章主要給大家介紹了關(guān)于java中continue和break區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11
SpringSecurity導(dǎo)致Redis壓力大問題的解決方案
在我們現(xiàn)有的系統(tǒng)中,用于登錄的?Redis?服務(wù)器?CPU?占用率長期處于?90%?的高位,這一狀況帶來了極大的風(fēng)險(xiǎn)隱患,因此,本文旨在解決這個(gè)問題,下面小編給大家詳細(xì)介紹SpringSecurity導(dǎo)致Redis壓力大問題的解決方案,需要的朋友可以參考下2025-06-06
SpringBoot實(shí)現(xiàn)簡單的日志鏈路追蹤
隨著分布式應(yīng)用的普及,現(xiàn)在的一些應(yīng)用系統(tǒng)不再像以前,所有的文件(前后端程序)都打包在一個(gè)包中,本文通過一個(gè)簡單的SpringBoot應(yīng)用來總結(jié),我們?nèi)绾螌⑷罩敬?lián)起來,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-10-10

