?目錄
1、前言
2、實(shí)現(xiàn)方式
2.1、循環(huán)重試
2.2、遞歸重試
2.3、Spring Retry
2.4、Resilience4j
2.5、http請(qǐng)求網(wǎng)絡(luò)工具內(nèi)置重試方式
2.6、自定義重試工具
2.7、并發(fā)框架異步重試
2.8、消息隊(duì)列
3、小結(jié)
1、前言
HTTP接口請(qǐng)求重試是指在請(qǐng)求失敗時(shí),再次發(fā)起請(qǐng)求的機(jī)制。在實(shí)際應(yīng)用中,由于網(wǎng)絡(luò)波動(dòng)、服務(wù)器故障等原因,HTTP接口請(qǐng)求可能會(huì)失敗。為了保證系統(tǒng)的可用性和穩(wěn)定性,需要對(duì)HTTP接口請(qǐng)求進(jìn)行重試。
2、實(shí)現(xiàn)方式
今天給大家分享一些常見(jiàn)的接口請(qǐng)求重試的方式。本地模擬了一個(gè)請(qǐng)求接口,后面的代碼示例均模擬請(qǐng)求該接口:
@GetMapping("http_test")
public String getHttpTest(){
return "接口請(qǐng)求成功,返回:OK";
}
2.1、循環(huán)重試
循環(huán)重試是最簡(jiǎn)單最粗暴的方式,就是在請(qǐng)求接口代碼中加入循環(huán)機(jī)制,如果接口請(qǐng)求失敗,則循環(huán)繼續(xù)發(fā)起接口請(qǐng)求,直到請(qǐng)求成功或接口重試次數(shù)達(dá)到上限。如果請(qǐng)求成功,則不進(jìn)行重試。
簡(jiǎn)單代碼示例如下:
@GetMapping("retry_demo_loop")
public String retry_demo_loop(){
// 重試上限次數(shù)為3次
int maxRetryTime = 3;
String result = null;
// 接口循環(huán)請(qǐng)求
for (int i = 1; i <= maxRetryTime; i++) {
try {
// 模擬請(qǐng)求接口
result = HttpUtil.get("http://localhost:8080/http_test");
// 模擬一次請(qǐng)求失敗
if(i == 1){
int co = i / 0;
}
// 請(qǐng)求成功,跳出循環(huán)
break;
} catch (Exception e) {
log.error("接口請(qǐng)求異常,進(jìn)行第{}次重試", i);
result = "接口請(qǐng)求失敗,請(qǐng)聯(lián)系管理員";
}
}
return result;
}
請(qǐng)求結(jié)果:
重試日志打?。?/p>
2.2、遞歸重試
除了循環(huán),還可以使用遞歸來(lái)實(shí)現(xiàn)接口的請(qǐng)求重試。遞歸是我們都比較熟悉的編程技巧,在請(qǐng)求接口的方法中調(diào)用自身,如果請(qǐng)求失敗則繼續(xù)調(diào)用,直到請(qǐng)求成功或達(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 "接口請(qǐng)求失敗,請(qǐng)聯(lián)系管理員";
}
int retryTime = 0;
try {
// 模擬請(qǐng)求接口
String result = HttpUtil.get("http://localhost:8080/http_test");
// 模擬一次請(qǐng)求失敗
if(maxRetryTime == 3){
int co = 1 / 0;
}
return result;
} catch (Exception e) {
// 處理異常
log.error("接口請(qǐng)求異常,進(jìn)行第{}次重試", ++retryTime);
return retryRequest(maxRetryTime - 1);
}
}
請(qǐng)求結(jié)果:
重試日志打印:
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啟動(dòng):
@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("請(qǐng)求retry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請(qǐng)求異常");
}
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)請(qǐng)求code參數(shù)為200時(shí),直接成功
* 當(dāng)code參數(shù)!=200時(shí),會(huì)出發(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);
}
訪問(wèn)地址:http://localhost:8080/retry_demo_spring_retry?code=123
查看結(jié)果:可以看到接口重試了3次,最后執(zhí)行了@Recover方法最后的回調(diào)。
2.4、Resilience4j
Resilience4j是一個(gè)輕量級(jí)、易于使用的輕量級(jí)“容錯(cuò)”包。它受Neflix Hystrix啟發(fā)但只有一個(gè)依賴(Vavr),而不像Hystrix很多很多的依賴。同時(shí)它是一個(gè) Java 庫(kù),可以幫助我們構(gòu)建彈性和容錯(cuò)的應(yīng)用程序。Resilience4j在“容錯(cuò)”方面提供了各種模式:斷路器(Circuit Breaker)、重試(Retry)、限時(shí)器(Time Limiter)、限流器(Rate Limiter)、隔板(BulkHead)。我們今天討論的話題是重試,那么今天就來(lái)演示下Retry。
Github地址:GitHub - resilience4j/resilience4j: Resilience4j is a fault tolerance library designed for Java8 and functional programming
首先,添加相應(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("請(qǐng)求retry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請(qǐng)求異常");
}
return result;
}
程序執(zhí)行,打印結(jié)果:
同樣接口請(qǐng)求了3次,均失敗后執(zhí)行了fallback回調(diào)方法。
2.5、http請(qǐng)求網(wǎng)絡(luò)工具內(nèi)置重試方式
通常一些外部的http網(wǎng)絡(luò)工具,都會(huì)內(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("重試超過(guò)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("請(qǐng)求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("接口請(qǐng)求異常");
}
return result;
}
訪問(wèn)接口地址:http://localhost:8080/retry_demo_httpclient?code=200。查看控制臺(tái)日志打?。?/p>
2.6、自定義重試工具
裝X的話,我們還可以自定義我們的重試工具。其實(shí)無(wú)非以下幾個(gè)步驟:
- 自定義重試的工具類
- 接收一個(gè)方法調(diào)用,并對(duì)該方法進(jìn)行異常捕獲
- 如果捕獲了該異常,則進(jìn)行一定間隔,然后重新請(qǐng)求
- 記錄請(qǐng)求次數(shù),如果超過(guò)上限,則提示異常信息
直接定義一個(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 如果超過(guò)重試上限次數(shù),那么會(huì)執(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ù)超過(guò){}次,進(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("請(qǐng)求customRetry接口");
String result = HttpUtil.get("http://localhost:8080/http_test");
if(code != 200){
throw new Exception("接口請(qǐng)求異常");
}
return result;
}
執(zhí)行完后,訪問(wèn)地址:http://localhost:8080/retry_demo_custom?code=2001
這里只是簡(jiǎn)單的進(jìn)行了定義,如果項(xiàng)目中使用肯定需要考慮更復(fù)雜的因素。如進(jìn)入重試時(shí)不一定只有異常的時(shí)候需要重試,可以指定重試策略,然后制定進(jìn)入重試策略的規(guī)則。
2.7、并發(fā)框架異步重試
在 Java 并發(fā)框架中,異步重試通常涉及到使用線程池和定時(shí)器,以便在異步任務(wù)失敗后進(jìn)行重試。以下是一個(gè)簡(jiǎn)單的示例,演示了如何使用 CompletableFuture、ScheduledExecutorService 和 CompletableFuture.supplyAsync 來(lái)實(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,否則會(huì)影響鏈?zhǔn)秸{(diào)用
});
}
}
示例中,performAsyncTask 模擬了一個(gè)異步任務(wù),如果任務(wù)失敗,它會(huì)拋出一個(gè)運(yùn)行時(shí)異常。retryAsyncTask 方法用于處理異步任務(wù)的失敗情況,并進(jìn)行重試。在重試時(shí),它使用 CompletableFuture.supplyAsync 創(chuàng)建一個(gè)新的異步任務(wù),模擬了重試的過(guò)程。請(qǐng)注意,這只是一個(gè)簡(jiǎn)單的示例,實(shí)際應(yīng)用中可能需要更復(fù)雜的重試策略和錯(cuò)誤處理邏輯。
2.8、消息隊(duì)列
網(wǎng)上還有一種消息隊(duì)列的方式來(lái)實(shí)現(xiàn),這里沒(méi)過(guò)多的去研究過(guò),目前以上幾種方式應(yīng)該也是夠用的了。這里直接貼出網(wǎng)上的部分代碼,使用 RabbitMQ 作為消息隊(duì)列,演示了請(qǐng)求重試的實(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ā)送請(qǐng)求
String request = "Your request data";
// 將請(qǐng)求發(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)聽(tīng)器
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String request = new String(delivery.getBody(), "UTF-8");
// 模擬處理請(qǐng)求,這里可能會(huì)出現(xiàn)處理失敗的情況
boolean processingSucceeded = processRequest(request);
if (processingSucceeded) {
System.out.println(" [x] Received and processed: '" + request + "'");
} else {
// 處理失敗,將請(qǐng)求重新放入隊(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) {
// 模擬處理請(qǐng)求的方法
// 在實(shí)際應(yīng)用中,這里應(yīng)該是對(duì)請(qǐng)求的處理邏輯
// 返回 true 表示處理成功,返回 false 表示處理失敗,需要進(jìn)行重試
// 這里簡(jiǎn)單地模擬了一個(gè)失敗的情況
return !request.equals("Your request data");
}
}
示例中,消息發(fā)送者(MessageProducer)將請(qǐng)求發(fā)送到名為 "retry_queue" 的隊(duì)列中。消息接收者(MessageConsumer)監(jiān)聽(tīng)隊(duì)列,當(dāng)接收到消息時(shí),模擬處理請(qǐng)求的邏輯。如果處理失敗,將請(qǐng)求重新放入隊(duì)列進(jìn)行重試。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-775597.html
3、小結(jié)
接口請(qǐng)求重試機(jī)制對(duì)保證系統(tǒng)高可用非常關(guān)鍵,需要根據(jù)業(yè)務(wù)需求選擇合適的重試策略。常用的組合策略包括帶最大次數(shù)的定時(shí)/指數(shù)退避重試、故障轉(zhuǎn)移重試等。重試機(jī)制需要綜合設(shè)置以達(dá)到容錯(cuò)效果 又避免產(chǎn)生過(guò)大的系統(tǒng)負(fù)載。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-775597.html
到了這里,關(guān)于07. HTTP接口請(qǐng)求重試怎么處理?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!