07. HTTP接口请求重试怎么处理?

 目录

1、前言

2、实现方式

2.1、循环重试

2.2、递归重试

2.3、Spring Retry

2.4、Resilience4j

2.5、http请求网络工具内置重试方式

2.6、自定义重试工具

2.7、并发框架异步重试

2.8、消息队列

3、小结


1、前言

HTTP接口请求重试是指在请求失败时,再次发起请求的机制。在实际应用中,由于网络波动、服务器故障等原因,HTTP接口请求可能会失败。为了保证系统的可用性和稳定性,需要对HTTP接口请求进行重试。

2、实现方式

今天给大家分享一些常见的接口请求重试的方式。本地模拟了一个请求接口,后面的代码示例均模拟请求该接口:

@GetMapping("http_test")
public String getHttpTest(){return "接口请求成功,返回:OK";
}

2.1、循环重试

循环重试是最简单最粗暴的方式,就是在请求接口代码中加入循环机制,如果接口请求失败,则循环继续发起接口请求,直到请求成功或接口重试次数达到上限。如果请求成功,则不进行重试。

简单代码示例如下:

@GetMapping("retry_demo_loop")
public String retry_demo_loop(){// 重试上限次数为3次int maxRetryTime = 3;String result = null;// 接口循环请求for (int i = 1; i <= maxRetryTime; i++) {try {// 模拟请求接口result = HttpUtil.get("http://localhost:8080/http_test");// 模拟一次请求失败if(i == 1){int co = i / 0;}// 请求成功,跳出循环break;} catch (Exception e) {log.error("接口请求异常,进行第{}次重试", i);result = "接口请求失败,请联系管理员";}}return result;
}

请求结果:

重试日志打印:

2.2、递归重试

除了循环,还可以使用递归来实现接口的请求重试。递归是我们都比较熟悉的编程技巧,在请求接口的方法中调用自身,如果请求失败则继续调用,直到请求成功或达到最大重试次数。

@GetMapping("retry_demo_rec")
public String retry_demo_rec(){// 重试上限次数为3次int maxRetryTime = 3;return retryRequest(maxRetryTime);
}/*** 递归方法* @param maxRetryTime* @return*/
private String retryRequest(int maxRetryTime){if (maxRetryTime <= 0) {return "接口请求失败,请联系管理员";}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("接口请求异常,进行第{}次重试", ++retryTime);return retryRequest(maxRetryTime - 1);}
}

请求结果:

重试日志打印:

2.3、Spring Retry

第三种便是使用Spring Retry依赖实现。首先我们需要集成相关依赖:

<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:出现该指定异常后,进行重试* maxAttempts:重试次数上限,这里指定为3次* backoff:重试策略,这里指定200ms间隔一次* @param code* @return* @throws Exception*/@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(200))String retry(int code) throws Exception;/*** 当重试达到上限后还是失败,则作为异常回调方法* @param th* @param code* @return*/@RecoverString recover(Throwable th, int code);}

MyReretService实现类:

@Slf4j
@Service
public class MyRetryServiceImpl implements MyRetryService {@Overridepublic 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;}@Overridepublic String recover(Throwable th, int code) {log.error("回调方法执行!!!!");return "异常码为:" + code + ",异常信息:" + th.getMessage();}
}

Controller:

@Autowired
private MyRetryService myRetryService;/*** 当请求code参数为200时,直接成功* 当code参数!=200时,会出发重试* @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

查看结果:可以看到接口重试了3次,最后执行了@Recover方法最后的回调。

2.4、Resilience4j

Resilience4j是一个轻量级、易于使用的轻量级“容错”包。它受Neflix Hystrix启发但只有一个依赖(Vavr),而不像Hystrix很多很多的依赖。同时它是一个 Java 库,可以帮助我们构建弹性和容错的应用程序。Resilience4j在“容错”方面提供了各种模式:断路器(Circuit Breaker)、重试(Retry)、限时器(Time Limiter)、限流器(Rate Limiter)、隔板(BulkHead)。我们今天讨论的话题是重试,那么今天就来演示下Retry。

Github地址:GitHub - resilience4j/resilience4j: Resilience4j is a fault tolerance library designed for Java8 and functional programming

首先,添加相应依赖:

<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot2</artifactId><version>2.1.0</version>
</dependency>

application.yml配置相关策略,配置官方文档:https://resilience4j.readme.io/docs/retry

resilience4j:retry:instances:retry_demo:max-attempts: 3     # 重试的上限次数wait-duration: 1s   # 重试的间隔时间,配置为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("回调方法执行!!!!");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;
}

程序执行,打印结果:

同样接口请求了3次,均失败后执行了fallback回调方法。

2.5、http请求网络工具内置重试方式

通常一些外部的http网络工具,都会内置一些重试的策略。如Apache HttpClient。这里以httpclient5为例。

首先添加依赖:

<dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.1.4</version>
</dependency>

定义HttpClient相关类,指定重试策略。可以使用默认的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()// 设置重试策略
//                            .setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.NEG_ONE_SECOND))// 自定义重试策略.setRetryStrategy(new CustomRetryStrategy()).build();}}}}

CustomRetryStrategy:

public static class CustomRetryStrategy implements HttpRequestRetryStrategy {@Overridepublic boolean retryRequest(HttpRequest httpRequest, IOException e, int executeCount, HttpContext httpContext) {return false;}@Overridepublic boolean retryRequest(HttpResponse httpResponse, int executeCount, HttpContext httpContext) {System.out.println("进入重试策略");if(executeCount > 3){System.out.println("重试超过3次,终止重试");return false;}if(httpResponse.getCode() != 200){System.out.println("http状态码不等于200,进行重试");return true;}// 其他情况,不重试return false;}@Overridepublic 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接口");// 这里模拟了一个不存在的地址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。查看控制台日志打印:

2.6、自定义重试工具

装X的话,我们还可以自定义我们的重试工具。其实无非以下几个步骤:

  1. 自定义重试的工具类
  2. 接收一个方法调用,并对该方法进行异常捕获
  3. 如果捕获了该异常,则进行一定间隔,然后重新请求
  4. 记录请求次数,如果超过上限,则提示异常信息

直接定义一个重试的工具类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   原方法调用* @param maxAttempts  重试次数上限* @param deplay   重试的间隔时间* @param timeUnit   重试的间隔时间单位* @param faultFunc   如果超过重试上限次数,那么会执行该错误回调方法* @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("重试次数超过{}次,进入失败回调", 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;
}

执行完后,访问地址:http://localhost:8080/retry_demo_custom?code=2001

这里只是简单的进行了定义,如果项目中使用肯定需要考虑更复杂的因素。如进入重试时不一定只有异常的时候需要重试,可以指定重试策略,然后制定进入重试策略的规则。

2.7、并发框架异步重试

在 Java 并发框架中,异步重试通常涉及到使用线程池和定时器,以便在异步任务失败后进行重试。以下是一个简单的示例,演示了如何使用 CompletableFuture、ScheduledExecutorService 和 CompletableFuture.supplyAsync 来实现异步任务的重试。

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) {// 示例异步任务,这里使用 supplyAsync,你可以根据实际情况选择其他异步任务CompletableFuture<String> asyncTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Task"));// 异步任务失败后的重试逻辑retryAsyncTask(asyncTask, 3, 1, TimeUnit.SECONDS);}private static CompletableFuture<String> performAsyncTask(String taskName) {// 模拟异步任务,这里可以是任何异步操作System.out.println("Performing async task: " + taskName);// 这里模拟任务失败的情况throw new RuntimeException("Task failed");}private static <T> void retryAsyncTask(CompletableFuture<T> asyncTask, int maxRetries, long delay, TimeUnit timeUnit) {asyncTask.exceptionally(throwable -> {// 异步任务失败后的处理逻辑System.out.println("Task failed: " + throwable.getMessage());// 重试逻辑if (maxRetries > 0) {System.out.println("Retrying...");CompletableFuture<T> retryTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Retry Task"));// 递归调用,进行重试retryAsyncTask(retryTask, maxRetries - 1, delay, timeUnit);} else {System.out.println("Max retries reached. Task failed.");}return null; // 必须返回 null,否则会影响链式调用});}
}

示例中,performAsyncTask 模拟了一个异步任务,如果任务失败,它会抛出一个运行时异常。retryAsyncTask 方法用于处理异步任务的失败情况,并进行重试。在重试时,它使用 CompletableFuture.supplyAsync 创建一个新的异步任务,模拟了重试的过程。请注意,这只是一个简单的示例,实际应用中可能需要更复杂的重试策略和错误处理逻辑。

2.8、消息队列

网上还有一种消息队列的方式来实现,这里没过多的去研究过,目前以上几种方式应该也是够用的了。这里直接贴出网上的部分代码,使用 RabbitMQ 作为消息队列,演示了请求重试的实现:

首先添加依赖:

<dependencies><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.13.1</version></dependency>
</dependencies>

然后,创建一个发送者和接收者类:

消息发送者(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);// 模拟发送请求String request = "Your request data";// 将请求发送到队列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);// 设置消息监听器DeliverCallback deliverCallback = (consumerTag, delivery) -> {String request = new String(delivery.getBody(), "UTF-8");// 模拟处理请求,这里可能会出现处理失败的情况boolean processingSucceeded = processRequest(request);if (processingSucceeded) {System.out.println(" [x] Received and processed: '" + request + "'");} else {// 处理失败,将请求重新放入队列,进行重试channel.basicPublish("", QUEUE_NAME, null, delivery.getBody());System.out.println(" [x] Processing failed. Retrying: '" + request + "'");}};// 消费消息channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});}}private static boolean processRequest(String request) {// 模拟处理请求的方法// 在实际应用中,这里应该是对请求的处理逻辑// 返回 true 表示处理成功,返回 false 表示处理失败,需要进行重试// 这里简单地模拟了一个失败的情况return !request.equals("Your request data");}
}

示例中,消息发送者(MessageProducer)将请求发送到名为 "retry_queue" 的队列中。消息接收者(MessageConsumer)监听队列,当接收到消息时,模拟处理请求的逻辑。如果处理失败,将请求重新放入队列进行重试。

3、小结

接口请求重试机制对保证系统高可用非常关键,需要根据业务需求选择合适的重试策略。常用的组合策略包括带最大次数的定时/指数退避重试、故障转移重试等。重试机制需要综合设置以达到容错效果 又避免产生过大的系统负载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/592721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 反编译处理Dex

前言 当我们将Android项目打包上架的时候&#xff0c;为了提高被人反编译代码的可能性可以提取 dex 文件对代码进一步做混淆处理。 本文不对相关工具做过多的解释&#xff0c;不了解的可以先熟悉相关工具的使用。 相关工具&#xff08;点击直接下载&#xff09; jadx-gui&a…

池化层解析:新手也能理解 PyTorch 中的核心组件

目录 torch.nn详解 nn.MaxPool1d nn.MaxPool2d nn.MaxPool3d nn.MaxUnpool1d nn.MaxUnpool2d nn.MaxUnpool3d nn.AvgPool1d nn.AvgPool2d nn.AvgPool3d nn.FractionalMaxPool2d nn.FractionalMaxPool3d nn.LPPool1d nn.LPPool2d nn.AdaptiveMaxPool1d nn.Adapt…

计算机基础面试题 |05.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

基本类型和包装类、基本类型和字符串之间的转换

基本类型和包装类之间的转换 基本类型和包装类之间经常需要互相转换&#xff0c;以 Integer 为例&#xff08;其他几个包装类的操作雷同哦&#xff09;&#xff1a; 在 JDK1.5 引入自动装箱和拆箱的机制后&#xff0c;包装类和基本类型之间的转换就更加轻松便利了。 那什么是…

CMake入门教程【基础篇】CMake+Visual Studio2022构建C++项目

文章目录 1.概述2.Visual Studio 2022简介3.安装Visual Studio 20224.安装CMake5.创建CMake项目6. 构建项目 1.概述 CMake和Visual Studio 2022结合 在现代软件开发中&#xff0c;CMake和Visual Studio 2022的结合提供了一个强大的环境&#xff0c;用于构建和管理各种规模的C项…

外汇天眼:不懂、不熟悉的交易,避而远之!

在市场中很多人都想表达一句&#xff1a;真的是感觉自己学得越多&#xff0c;就越迷茫。 不管是做交易还是做别的行业好像都是在刚入行的时候兴致勃勃&#xff0c;兴趣满满&#xff0c;什么都不懂但却很容易就能尝到一些甜头&#xff0c;这样就就很大程度上激励了自己&#xf…

Maven 整理(含面试题)

Maven是Java 项目必备&#xff0c;Maven 主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。项目构建工具 、更好的管理依赖 目录 Maven安装与运行maven 重要知识点面试题 Maven安装与运行 Maven 是一个项目管理工具&#xff0c;可以对 Java 项目进行构建、依赖管理…

Axure医疗-住院板块,住院患者原型预览,新增医护人员原型预览,新增病房原型预览,选择床位原型预览,主治医生原型预览,主治医生医嘱原型预览

目录 一.医疗项目原型图-----住院板块 1.1 住院板块原型预览 1.2 新增住院患者原型预览 1.3 新增医护人员原型预览 1.4 新增病房原型预览 1.5 选择床位原型预览 1.6 主治医生原型预览 1.7 主治医生医嘱原型预览 1.8 主治医生查看患者报告原型预览 1.9 护士原型预…

【项目问题解决】参数过滤器实现代码及相关问题

目录 【项目问题解决】参数过滤器实现代码及相关问题 1.前言2.代码实现2.1常量类2.2过滤器方法2.3 注意 3.总结4.参考 文章所属专区 项目问题解决 1.前言 参数过滤器实现代码及相关问题 2.代码实现 2.1常量类 Component public class ProjectConstants {public static St…

山海鲸可视化软件的优势:数据整合、可视化与个性化定制

随着科技的快速发展&#xff0c;企业数字化转型已成为必然趋势。而对于一些本身没有开发优势或非技术型企业&#xff0c;数字化产品的选择就成为重中之重。作为山海鲸可视化软件的开发者&#xff0c;我们深知这一点&#xff0c;对于企业来说&#xff0c;能选择一个产品一定要有…

C# Onnx yolov8 竹签计数、一次性筷子计数

目录 效果 模型信息 项目 代码 数据集 下载 C# Onnx yolov8 竹签计数、一次性筷子计数 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-01-03T08:55:22.768617 author&#xff1a;Ultralytics task&#xff1a;detect license&#x…

并发编程下的集合:数组寻址、LinkedList、HashMap、ConcurrentHashMap

数组如何寻址 a[n]起始地址(n*字节数) 译&#xff1a;a[2]100(2*4) 2为下标、4为int类型字段占四个字节 LinkedList LinkedList为双向链表结构&#xff0c;链表结构又分为单向、双向、以及循环。 // 双向链表 private static class Node<E> {E item;Node<E> nex…

vscode中增加参数的一个方法

1 在settings.json 文件中增加参数 2. 在参数中配置 这里也是ok的

【Mybatis】深度解析MyBatis中#和$的差异

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; Mybatis ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 #&#xff08;预编译&#xff09;&#xff1a; $&#xff08;直接拼接&#xff09;&#xff1a; 结语 我的其他博客 前言…

【unity插件】推荐一款开源的Unity网格破碎插件,实现在Unity中展示可破坏的墙壁的——unity-fracture

最终效果 地址 https://github.com/ElasticSea/unity-fracture 介绍 FractureThis.cs 脚本获取其游戏对象中的所有网格并合并它们。这个全局网格被发送到这里 nvblast &#xff0c;以分解成块。块的内部部分为要应用的材料生成了新的 UV。原始游戏对象被隐藏&#xff0c;每个…

提升三维模型数据的几何坐标纠正速度效率具体技术方法

提升三维模型数据的几何坐标纠正速度效率具体技术方法 根据搜索结果&#xff0c;以下是提升倾斜摄影三维模型数据的几何坐标纠正和三维重建速度的具体技术方法&#xff1a; 1、增加控制点&#xff1a;通过增加控制点数量可以提高几何坐标精度。控制点是已知地面坐标的点&#…

微信公众号内网穿透本地调试微信授权

微信公众号内网穿透本地调试一直以来都比较麻烦 怕自己忘记&#xff0c; 记录一下 准备 natapp获取一个域名隧道下载nginx配置微信公众号web网页授权域名 1.natapp获取一个域名隧道 在natapp官网&#xff08;https://natapp.cn/&#xff09;进行注册登录后&#xff0c;进入…

ES6 class详解

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

关于“Python”的核心知识点整理大全58

目录 19.2.3 注销 1. 注销URL urls.py 2. 视图函数logout_view() views.py 3. 链接到注销视图 base.html 19.2.4 注册页面 1. 注册页面的URL模式 urls.py 2. 视图函数register() views.py 3. 注册模板 register.html 4. 链接到注册页面 base.html 注意 19.3 …

1.2作业

温湿度数据通过中断处理显示到数码管中 main.c #include "spi.h"#include"si7006.h"int main(){int i0,j0,m0,n0;int num[10] {0xFC,0x60,0xDA,0xF2,0x66,0xB6,0x3E,0xE0,0xFE,0xF6};SPI_init();unsigned short hum;short tem;//进行si7006的初始化si700…