接口请求重试八种方法
请求三方接口需要加入重试机制
一、循环重试
在请求接口的代码块中加入循环,如果请求失败则继续请求,直到请求成功或达到最大重试次数。
int retryTimes = 3;
for(int i = 0;i < retryTimes;i++){try{//请求接口的代码break;}catch(Exception e){//处理异常Thread.sleep(1000);//为了避免频繁请求,延迟1秒后重试}
}
二、使用递归结构
在请求接口的方法中调用自身,如果请求失败则继续调用,直到请求成功或达到最大重试次数。
public void requestWithRetry(int retryTimes){if(retryTimes <= 0){return;}try{//请求接口的代码}catch(Exception e){//处理异常Thread.sleep(1000);//延迟1秒后重试requestWithRetry(retryTimes - 1);}
}
三、使用网络工具内置重试机制
HTTP 客户端通常内置了一些重试机制,只需要在创建对应的客户端实例的时候进行配置即可,以 Apache HTTPClient 为例:
- 4.5+ 版本:使用 HttpClients.custom().setRetryHandler() 方法来设置重试机制
- 5.x 版本:使用 HttpClients.custom().setRetryStrategy() 方法来设置重试机制
CloseableHttpClient httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler(3,true)).build();CloseableHttpClient httpClient = HttpClients.custom().setRetryStrategy(new DefaultHttpRequestRetryStrategy(3,NEG_ONE_SECOND)).build();
Apache HTTPClient 还支持自定义重试策略,可以可以实现 HTTPRequestRetryHandler 接口(4.5 + 版本)或者 RetryStrategy 接口(5.x 版本)
CloseableHttpClient httpClient = HttpClients.custom().setRetryStrategy((response,executionCount,context) -> {if(executionCount > 3){//如果重试次数超过3次,则放弃重试return false;}if(status >= 500 && statusCode < 600){//如果遇到服务器错误状态码,则进行重试return true;}//其他情况不进行重试return false;}).build();
四、使用Spring Retry库
Spring Retry 提供了一组注解和工具类,可以方便地为方法添加重试功能。
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.3.1</version>
</dependency>
Spring Retry 的使用有两种方式,一种是使用 RetryTemplate 来显式调用需要重试的方法,一种实用注解来自动触发重试。
显式调用
RetryTemplate retryTemplate = new RetryTemplate();//配置重试策略
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
retryTemplate.setRetryPolicy(retryPolicy);//最大重试次数为 3 次//配置重试间隔策略
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000);//重试间隔为 1 秒
retryTemplate.setBackOffPolicy(backOffPolicy);//使用RetryTemplate调用方法,执行需要重试的代码块
retryTemplate.execute((RetryCallback<Void,Exception>) context - >{//请求接口的代码return null;
});
Spring Retry 是一个提供重试机制的库,可以方便地在 Spring 项目中使用。使用 @Retryable 注解标记需要重试的方法,如果方法抛出异常则会自动重试。
@Retryable(value=Exception.class,maxAttempts=3)
public void request(){//请求接口的代码
}
注解调用:
①、配置重试切面
@Configuration
@EnableRetry //启用重试功能
public class RetryConfig{//配置其他bean
}
②、注解标记需要重试的方法
@Retryable(maxAttempts=3)//指定了最大重试次数为 3 次
public void request(){//请求接口代码
}
③、调用被标记的方法
@Autowired
private HttpService httpService;httpService.request();
springboot中使用:
①、启动类开启 Spring Retry 功能
@SpringBootApplication
@EnableRetry // 启用Spring Retry功能
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
②、进行重试的方法上添加 @Retryable指定了重试的异常类型、最大重试次数和重试间隔
@Backoff 注解用于指定重试间隔策略,delay 属性表示每次重试之间的间隔时间。在这个例子中,每次重试之间的间隔时间为 1 秒
@Retryable 注解只能标记在 public 方法上。如果需要在非 public 方法上使用重试功能,可以使用代理模式实现
@Service
public class MyService {@Retryable(value = {MyException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))public void doSomething() {// 需要进行重试的方法逻辑}
}
如果需要在重试过程中进行一些特定的操作,比如记录日志、发送消息等,可以在重试方法中使用 RetryContext 参数,它提供了一些有用的方法来获取重试的上下文信息。
@Service
public class MyService {@Retryable(value = {MyException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))public void doSomething(RetryContext context) {// 获取重试次数int retryCount = context.getRetryCount();// 获取上一次异常Throwable lastThrowable = context.getLastThrowable();// 记录日志、发送消息等操作// ...// 需要进行重试的方法逻辑}
}
五、使用Resilience4j库
提供了重试、熔断、限流等多种机制
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot2</artifactId><version>1.7.0</version>
</dependency>
1、显式调用
//创建一个 RetryRegistry 对象
RetryRegistry retryRegistry = RetryRegistry.ofDefalults();//配置RetryRegistry实例,使用 RetryConfig 类来自定义 Retry 的配置,包括最大重试次数、重试间隔等
RetryConfig config = RetryConfig.custom().maxAttempts(3)//最大重试次数为 3 次.waitDuration(Duration.ofMillis(1000))//重试间隔为 1 秒.retryOnResult(response -> response.getStatus() == 500)//返回结果的状态码为 500 时进行重试.retryOnException(e -> e instanceof WebServiceException)//抛出 WebServiceException 异常时进行重试.retryExceptions(IOException.class, TimeoutException.class).ignoreExceptions(BusinessException.class, OtherBusinessException.class)//忽略 BusinessException 和 OtherBusinessException 异常.failAfterMaxAttempts(true).build();//使用 Retry 来装饰和执行需要进行重试的代码块
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retry, () -> {// 需要进行重试的代码return "result";
});
2、注解调用
//SpringBoot项目中使用@Retryable注解来标记需要重试的方法
@Service
public class MyService{//指定了重试的异常类型为MyException,最大重试次数3次,重试间隔1秒@Retryable(value={MyException.class},maxAttempts=3,backoff=@Backoff(delay = 1000))public void doSomething(){//需要进行重试的方法逻辑}
}
六、自定义重试工具类
①、自定义一个是实现了Callback抽象类的具体回调类
public abstract class Callback{//执行重试逻辑public abstract RetryResult doProcess();//RetryResult封装重试结果
}
②、封装重试结果
public class RetryResult{private Boolean isRetry;//是否需要进行重试private Object obj;//重试的结果对象//构造方法和getter方法省略public static RetryResult ofResult(Boolean isRetry, Object obj){return new RetryResult(isRetry, obj);}public static RetryResult ofResult(Boolean isRetry){return new RetryResult(isRetry, null);}
}
③、执行
public class RetryExecutor{//接收一个重试次数和一个回调对象public static Object execute(int retryCount,Callback callback){for(int curRetryCount = 0;curRetryCount < retryCount;curRetryCount++){RetryResult retryResult = callback.doProcess();if(retryResult.isRetry()){continue;}return retryResult.getObj();}return null;}
}
使用这个自定义的重试工具类时,只需要实现一个继承自 Callback 的回调类,并在其中实现具体的重试逻辑。然后,通过调用 RetryExecutor.execute() 方法来执行重试操作。这里直接用了一个匿名的实现:
//最大重试次数
int maxRetryCount = 3;
Object result = RetryExecutor.execute(maxRetryCount, new Callback() {@Overridepublic RetryResult doProcess() {// 执行需要重试的逻辑// 如果需要重试,返回 RetryResult.ofResult(true)// 如果不需要重试,返回 RetryResult.ofResult(false, result)}
});
七、并发框架异步重试
使用ThreadPoolExecutor把请求接口转换成一个异步任务,将任务放入线程池中异步执行
并发地重试请求接口。
可以在任务执行完成后,判断任务执行结果,如果失败则继续重试
int maxRetryTimes = 3;
int currentRetryTimes = 0;ThreadPoolExecutor executor = new ThreadPoolExecutor(10,//核心线程数10,//最大线程数0L,//空闲线程存活时间TimeUnit.MILLISECONDS,//时间的单位new LinkedBlockingQueue<>()//任务队列
);//任务
Callable<String> task = () -> {//请求接口的代码return "result";
}Future<String> future;
while(currentRetryTimes < maxRetryTimes){try{future = executor.submit(task);String result = future.get();//获取任务结果//判断任务执行结果,如果任务执行成功,则跳出循环;如果任务执行失败,则继续重试,直到达到最大重试次数break;}catch(Exception e){currentRetryTimes++;try{Thread.sleep(1000);}catch(InterruptedException ex){Thread.currentThread().interrupt();}}
}
八、消息队列重试
保证重试的可靠性,不会因为服务中断,而导致重试任务的丢失,可以引入消息队列
直接把消息投递到消息队列里,通过对消息的消费,来实现重试机制。
//指定了消费者的相关配置,包括消费者组和订阅的主题
@Component
@RocketMQMessageListener(topic="myTopic",consumerGroup="myConsumerGroup")
public class MyConsumer implements RocketMQListener<String>{@Overridepublic void onMessage(String message){try{//请求接口的代码}catch(Exception e){//处理异常//如果请求失败,创建一个 RocketMQ 的生产者,并将请求重新发送到消息队列中,等待下一次处理DefaultMQProducer producer = new DefaultMQProducer("myProducerGroup");producer.setNamesrvAddr("127.0.0.1:9876");try{producer.start();}catch(Exception ex){//处理异常}finally{producer.shutdown();}}}
}
【注意】
- 合理设置重试次数和重试间隔时间,避免频繁地发送请求,同时也不要设置过大的重试次数,以免影响系统的性能和响应时间。
- 考虑接口幂等性:如果请求是写操作,而且下游的服务不保证请求的幂等性,那么在重试时需要谨慎处理,可以通过查询等幂等的方式进行重试
- 在重试过程中,需要考虑并发的问题。如果多个线程同时进行重试,可能会导致请求重复发送或请求顺序混乱等问题。可以使用锁或者分布式锁来解决并发问题。
- 在处理异常时,需要根据具体的异常类型来进行处理。有些异常是可以通过重试来解决的,例如网络超时、连接异常等;而有些异常则需要进行特殊的处理,例如数据库异常、文件读写异常等。
- 在使用重试机制时,需要注意不要陷入死循环。如果请求一直失败,重试次数一直增加,可能会导致系统崩溃或者资源耗尽等问题。