Java中HTTP接口請求重試的實(shí)現(xiàn)方式
1、前言
HTTP接口請求重試是指在請求失敗時(shí),再次發(fā)起請求的機(jī)制。在實(shí)際應(yīng)用中,由于網(wǎng)絡(luò)波動(dòng)、服務(wù)器故障等原因,HTTP接口請求可能會(huì)失敗。為了保證系統(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啟動(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("請求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í),會(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); }
訪問地址: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 庫,可以幫助我們構(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ò)工具,都會(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("重試超過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。查看控制臺(tái)日志打?。?/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ù),那么會(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ù)超過{}次,進(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,否則會(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ù),模擬了重試的過程。請注意,這只是一個(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"); // 模擬處理請求,這里可能會(huì)出現(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基于MyBatis-Plus實(shí)現(xiàn)Lambda Query查詢的示例代碼
MyBatis-Plus 是 MyBatis 的增強(qiáng)工具,簡化了數(shù)據(jù)庫操作,并提高了開發(fā)效率,它提供了多種查詢方式,包括常規(guī)的 SQL 查詢、Lambda Query 查詢、分頁查詢、條件查詢等,在本篇博客中,我們將詳細(xì)講解如何使用 MyBatis-Plus 的各種查詢方式,需要的朋友可以參考下2025-01-01Security中的@PostAuthorize、@PreFilter和@PostFilter詳解
這篇文章主要介紹了Security中的@PostAuthorize、@PreFilter和@PostFilter詳解,@PostAuthorize是在方法調(diào)用完成后進(jìn)行權(quán)限檢查,它不能控制方法是否能被調(diào)用,只能在方法調(diào)用完成后檢查權(quán)限決定是否要拋出AccessDeniedException,需要的朋友可以參考下2023-11-11Java Hibernate中的持久化類和實(shí)體類關(guān)系
Hibernate是一種Java對象關(guān)系映射框架,通過持久化類將Java對象映射到數(shù)據(jù)庫表中。持久化類需要實(shí)現(xiàn)無參構(gòu)造器、具有標(biāo)識(shí)屬性和使用注解或XML進(jìn)行映射。Hibernate通過Session來管理對象的狀態(tài),包括臨時(shí)狀態(tài)、持久化狀態(tài)和游離狀態(tài)2023-04-04springboot實(shí)現(xiàn)極驗(yàn)校驗(yàn)的項(xiàng)目實(shí)踐
在系統(tǒng)業(yè)務(wù)中,需要想客戶發(fā)送手機(jī)驗(yàn)證碼,進(jìn)行驗(yàn)證后,才能提交,本文主要介紹了springboot實(shí)現(xiàn)極驗(yàn)校驗(yàn)的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法
這篇文章主要介紹了java實(shí)現(xiàn)在線預(yù)覽--poi實(shí)現(xiàn)word、excel、ppt轉(zhuǎn)html的方法,本文需要引入poi的jar包給大家介紹的非常詳細(xì),需要的朋友可以參考下2019-09-09SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析
這篇文章主要介紹了SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析,springboot根據(jù)訪問者的request中的Accept屬性來判斷要返回什么樣的數(shù)據(jù),SpringBoot存在一個(gè)錯(cuò)誤處理機(jī)制,會(huì)根據(jù)不同請求返回不同的結(jié)果,需要的朋友可以參考下2023-12-12java日期時(shí)間格式化@JsonFormat與@DateTimeFormat的使用
本文主要介紹了java日期時(shí)間格式化@JsonFormat與@DateTimeFormat的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Java虛擬機(jī)內(nèi)存結(jié)構(gòu)及編碼實(shí)戰(zhàn)分享
這篇文章主要介紹了Java虛擬機(jī)內(nèi)存結(jié)構(gòu)及編碼實(shí)戰(zhàn)分享,文章圍繞詳細(xì)主題展開相關(guān)資料具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-04-04