Java接口请求重试机制的几种常见方法

 更新时间:2023年11月06日 09:58:29   作者:fking86  
Java接口请求重试机制是保证系统稳定性和容错能力的重要手段之一,当接口请求发生失败或暂时性错误时,通过重试机制可以提高请求的成功率,本文将详细介绍Java接口请求重试机制的几种常见方法,需要的朋友可以参考下

1.几种方法

1.1循环重试

它的基本思路是:

  1. 定义重试次数,如最大重试5次
  2. 发送请求,如果失败则进入重试逻辑
  3. 在循环内部,记录当前已重试次数,如当前已重试2次
  4. 判断当前重试次数是否达到最大次数,如果达到则终止循环,否则进行重试
  5. 在循环内部,可以添加定时重试间隔,也可以使用指数退避算法
  6. 发送重试请求,重复判断是否成功,直到成功、达到最大次数或其他终止条件

示例

public class Retry {

  private static final int MAX_RETRIES = 5;

  public static Response request() throws Exception {
    int retries = 0;
    while (true) {
      try {
        // 发送请求,返回响应
        Response response = HttpClient.sendRequest();

        // 请求成功则返回响应
        if (response.isSuccess()) {
          return response; 
        }

      } catch (Exception e) {
        // 请求失败进行重试
      }

      // 判断是否超过最大重试次数
      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 库可以很方便地实现接口请求的重试机制。

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) {
  // 远程调用接口 
}

@Retryable 定义了重试规则:- value - 重试的异常类型

- maxAttempts - 最大重试次数

- backoff - 重试等待策略

1.2.4. 还可以自定义 RetryTemplate 进行更复杂的重试控制

RetryTemplate template = new RetryTemplate();

template.execute(context -> {
  // 可在此处自定义重试逻辑
  
  return remoteClient.invoke(); 
});

Spring Retry 为接口请求重试提供了完善和易用的解决方案,可以灵活控制各种重试参数,适用于复杂系统的容错要求。

1.3 并发框架异步重试

使用并发框架的异步请求方式可以较简单地实现接口请求的重试机制。以CompletableFuture为例:

1.3.1 发送请求使用CompletableFuture封装:

CompletableFuture<Response> future = CompletableFuture.supplyAsync(() -> {
  return service.call();
});

1.3.2 当请求失败时,使用retryAsync自动完成重试:

future = future.exceptionally(e -> {
  return service.retryAsync(); 
});

1.3.3 可以链式调用,自定义重试逻辑:

future
  .exceptionally(e -> {
     // 处理异常
  })
  .thenApplyAsync(resp -> {
     // 处理响应
  })
  .retryAsync(retryCount, delay); 

主要优点是:

  • 线程安全的异步请求
  • 自动重试失败任务
  • 简洁的链式编程方式
  • 避免阻塞主线程

使用并发框架可以便捷地实现异步重试机制,提高系统容错性。其他框架如RxJava也有类似的重试机制。

1.4 消息队列重试

使用消息队列可以实现接口请求的异步重试机制。

基本思路是:

  • 接口请求发送失败后,将请求信息封装为消息,发送到请求重试的队列中。

  • 消息消费者从队列中获取失败的请求,根据策略进行重试。

  • 重复重试直到成功、重试次数用尽或其他终止条件。

  • 成功后将消息移除队列,失败则保留消息供再次重试。

主要步骤:

  • 创建请求重试队列,如“request.retry.queue”

  • 接口请求失败后,生成重试消息,发送到队列

  • 消费者启动线程从队列中取消息重试

  • 根据重试策略进行定时重试或最大重试数

  • 成功则确认消息,失败则重新入队

使用消息队列进行重试有利于:

  • 异步重试,不阻塞主线程
  • 可靠地完成重试任务
  • 灵活控制重试策略

示例

// 1. 创建队列
Queue retryQueue = new Queue("request.retry.queue");

// 2. 请求失败,发送重试消息  
public void request() {
  try {
    // 调用接口
    httpClient.post(url, payload);
  } catch (Exception e) {
    // 发送重试消息
    Message msg = new Message(url, payload, maxRetries);
    retryQueue.send(msg);
  }
}

// 3. 消费者线程进行重试
class RetryConsumer implements Runnable {

  public void run() {
    while (true) {
      Message msg = retryQueue.take();
      
      for (int i = 0; i < msg.getMaxRetries(); i++) {
        try {
          // 重试请求
          httpClient.post(msg.getUrl(), msg.getPayload());
          // 请求成功,结束循环
          break;
        } catch (Exception e) {
          // 等待后继续重试
        }  
      }
      
      // 重试完成后,确认消息
      retryQueue.confirm(msg);
    }
  }
}

这就是使用消息队列实现接口重试的基本流程,可以根据需求扩展重试策略、异常处理等逻辑。

1.5 自定义重试工具类

使用自定义的重试工具类来实现接口请求的重试机制,提高代码的复用性和可维护性。

重试工具类的实现思路:

  • 提供重试方法,参数包括请求函数、重试策略等
  • 在重试方法内部执行循环请求
  • 每次请求失败时,根据策略等待一段时间
  • 记录当前重试次数,与最大次数比较
  • 请求成功或者达到最大重试次数则结束循环

示例:

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();
        // 重试次数加1
        retries++;
      }
    }
  }

}

// 执行请求的函数接口
interface RetryCallable<T> {
  T call(); 
}

// 重试策略 
class RetryPolicy {
  int maxRetries;
  int delay; 
}

// 使用示例
RetryUtil.retry(() -> {
  // 接口请求
  return httpClient.get(url); 
}, policy);

这样可以提高重试相关逻辑的复用性,避免写重复代码。

1.6 使用递归结构

使用递归结构也可以实现接口请求的重试机制。

基本思路是设计一个递归函数,在函数内部发送请求,如果失败则继续递归调用自身再次重试。

示例:

public class RetryRequest {

  private static final int MAX_RETRIES = 3;
  
  public static Response request(int retries) {
    
    try {
      // 发送请求
      Response response = HttpClient.get("http://example.com");
      return response;
      
    } catch (Exception e) {
      
      // 处理异常
      
      // 判断是否需要重试
      if (retries < MAX_RETRIES) {
        // 增加重试次数
        retries++; 
        // 延迟1秒钟
        Thread.sleep(1000); 
        // 递归调用自身进行重试
        return request(retries);
      }
      
      // 重试失败
      throw new RuntimeException("Request failed after " + MAX_RETRIES + " retries!");
      
    }
  }
  
  public static void main(String[] args) {  
    Response response = request(0);
    // 处理响应
  }

}

主要逻辑是通过递归不断调用自身来实现重试。优点是逻辑较简单清晰,缺点是递归层次过深时可能会导致堆栈溢出。需要合理设置最大递归深度,也可以通过循环改写递归来避免深层递归。

1.7 使用Resilience4j

Resilience4j是一个很好的Java重试库,可以用它来实现接口请求的重试机制。

主要步骤:

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 使用重试逻辑调用接口

String result = retry.executeSupplier(() -> {

  // 发送请求
  return backendService.callAPI(); 

});

1.7.4 自定义重试异常predicate

RetryConfig config = RetryConfig.custom()
  .retryOnException(e -> isRetryable(e))
  .build();

Resilience4j提供了线程安全的重试 decorator,可以通过配置灵活控制重试策略,很好地支持了接口请求重试。

1.8 使用网络工具重试

我们常用的一些网络工具来做重试

示例

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发送请求
        return HttpClientUtils.get(url);
        
      } catch (Exception e) {

        if (retries >= MAX_RETRIES) {
          throw e;
        }
        
        // 增加重试次数
        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);
  }

}

// 网络工具类 
class HttpClientUtils {

  public static String get(String url) throws IOException {
    // 发送GET请求并返回结果
  }

}

主要通过循环和网络工具类来实现重试逻辑,延时控制也可以用Random来实现指数退避。这种 utilities + 循环 的组合可以实现灵活可复用的重试机制。

2.注意事项

接口请求重试时需要注意以下几点:

2.1 幂等性
接口需要是幂等的,多次调用结果相同,避免重复执行带来副作用。

2.2 资源竞争
重试可能对服务端造成更大压力,需要考虑限流等措施。

2.3 超时设置
合理设置重试最大次数和总超时时间,避免长时间等待。

2.4 重试条件
明确哪些异常情况下需要重试,不能无脑重试所有错误。

2.5 数据一致性
请求成功后要幂等更新状态,避免重复数据。

2.6 异步机制
重试过程不要阻塞主业务线程。

2.7 退避策略
失败后延迟一段时间再重试,可选避免集群重试。

2.8 日志记录
记录重试的次数、错误原因等信息,方便排查问题。

2.9 容错机制
重试失败后的降级处理,避免级联失败。

总结

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

相关文章

  • IntelliJ IDEA maven 构建简单springmvc项目(图文教程)

    IntelliJ IDEA maven 构建简单springmvc项目(图文教程)

    在工作当中,我们有时需要创建一个全新的工程,而基于spring-mvc web的工程较为常见,这篇文章主要介绍了IntelliJ IDEA maven 构建简单springmvc项目(图文教程),感兴趣的小伙伴们可以参考一下
    2018-05-05
  • java 使用poi动态导出的操作

    java 使用poi动态导出的操作

    这篇文章主要介绍了java 使用poi动态导出的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • SpringBoot 单元测试JUnit的使用详解

    SpringBoot 单元测试JUnit的使用详解

    这篇文章主要介绍了SpringBoot 单元测试JUnit的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • Netty粘包拆包及使用原理详解

    Netty粘包拆包及使用原理详解

    Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序,这篇文章主要介绍了Netty粘包拆包及使用原理
    2022-08-08
  • mybatis foreach传两个参数批量删除

    mybatis foreach传两个参数批量删除

    这篇文章主要介绍了mybatis foreach 批量删除传两个参数,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • Java web入门指南之在Idea上创建Java web项目

    Java web入门指南之在Idea上创建Java web项目

    好多书上的JavaWeb教程都是Eclipse以及MyEclipse,当然这里不论IDE的好坏,下面这篇文章主要给大家介绍了关于Java web入门指南之在Idea上创建Java web项目的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • Java和MySQL数据库中关于小数的保存问题详析

    Java和MySQL数据库中关于小数的保存问题详析

    在Java和MySQL中小数的精度可能会受到限制,如float类型的小数只能精确到6-7位,double类型也只能精确到15-16位,这篇文章主要给大家介绍了关于Java和MySQL数据库中关于小数的保存问题,需要的朋友可以参考下
    2024-01-01
  • SpringBoot+JUnit5+MockMvc+Mockito单元测试的实现

    SpringBoot+JUnit5+MockMvc+Mockito单元测试的实现

    今天聊聊如何在 SpringBoot 中集成 Junit5、MockMvc、Mocktio。Junit5 是在 Java 栈中应用最广的测试框架,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 在JDK和Eclipse下如何编写和运行Java Applet

    在JDK和Eclipse下如何编写和运行Java Applet

    本文主要介绍了在JDK和Eclipse的环境下如何编写和运行Java Applet,图文方式,适合初学者学习。
    2015-09-09
  • Java中的异常测试框架JUnit使用上手指南

    Java中的异常测试框架JUnit使用上手指南

    这篇文章主要介绍了Java的异常测试框架JUnit使用上手指南,JUnit是Java代码进行单元测试中的常用工具,需要的朋友可以参考下
    2016-03-03

最新评论