重试利器之Guava-Retryer

/ 技术 / 无站内评论 / 4266浏览

前言

在日常开发中,我们经常会遇到需要调用外部服务和接口的场景。外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失败重试策略重新调用 API 接口来获取。重试策略在服务治理方面也有很广泛的使用,通过定时检测,来查看服务是否存活(Active)。

Guava Retrying是一个灵活方便的重试组件,包含了多种的重试策略,而且扩展起来非常容易。

用作者的话来说:

This is a small extension to Google’s Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

使用Guava-retrying你可以自定义来执行重试,同时也可以监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。

代码

引入Guava-retry

 com.github.rholder guava-retrying 2.0.0         

Retryer配置


Retryer<Boolean> retryer = RetryerBuilder  
  .<Boolean>newBuilder()  
  //抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。  
  .retryIfException()  
  //抛出连接异常才会重试  
  .retryIfExceptionOfType(ConnectException.class)  
  //返回指定结果<泛型>也需要重试  
  .retryIfResult(Predicates.equalTo(null))  
  //重调策略  
  .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))  
  //尝试次数  
  .withStopStrategy(StopStrategies.stopAfterAttempt(3))  
  .build();

简单三步就能使用Guava Retryer优雅的实现重调方法。
接下来对其进行详细说明:

  • RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。
  • RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,通过retryIfExceptionretryIfResult设置,同时支持多个且能兼容。
  • retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
  • retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。
  • retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException`都属于runtime异常,也包括自定义的error

重试成功如何获取返回值呢?

网上很多文章都是各种抄,自己翻源码找到了。

首先再Retryer的核心执行方法call中,发现Callable是带泛型的,同时Callable又是带返回值的线程。

看了有get(),于是继续往下翻,发现实现了接口的ResultAttempt中,有个泛型保存执行结果。

因此,如果想要重试成功返回值,以字符串为例。


Retryer<String> retryer = RetryerBuilder  
  .<String>newBuilder()  
  //抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。  
  .retryIfException()  
  //抛出连接异常才会重试  
  .retryIfExceptionOfType(ConnectException.class)  
  //返回指定结果<泛型>也需要重试  
  .retryIfResult(Predicates.equalTo(null))  
  //重调策略  
  .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))  
  //尝试次数  
  .withStopStrategy(StopStrategies.stopAfterAttempt(3))  
  .build();

定义实现Callable接口的方法,以便Guava retryer能够调用

场景,我这里我访问一个http接口,因为是挂代理去访问的,所以存在着ip代理连接超时的异常,这时候就要进行重试。

访问接口成功就返回对应字符串,当然,自己可以根据场景定义所需的返回值。


private static Callable<String> updateReimAgentsCall = new Callable<String>() {  
  @Override  
  public String call() throws Exception {                CrawlerService service = new CrawlerService();  
  try {  
  List<PopularRoutesInfo> infoList = service.getPopularRoutes(IP.builder().host("xxxx").port(xxx).build());  
  if (CollectionUtils.isEmpty(infoList)) {  
  System.out.println("抓取结果为空,继续重试...");  
  return null;  
  }  
 } catch (ConnectException e) {  
  System.out.println("连接失败,继续重试...");  
  return null;  
  } catch (Exception e) {  
  System.out.println("连接失败,继续重试...");  
  return null;  
  }  
  return "成功抓取热门航线报价";  
  } };

执行任务


        try {  
  System.out.println(retryer.call(updateReimAgentsCall));  
  } catch (ExecutionException e) {             //  } catch (RetryException e) {             //   }

执行结果

成功时

失败时

我们主要关注下可能的异常:

RetryException和ExecutionException。

ExecutionException产生:传入的Callable执行过程中产生了异常,但是我们在构建Retryer对象的时候并没有考虑这种情况,就会抛出这个异常。抛出这异常,也就意味着重试终止。

RetryException就很简单了,当所有重试介绍后,依然不能成功,那么就会抛这异常。

封装工具类

我封装了一下,方便使用


/**  
 * @Author: shouliang.wang  
 * @Date: 2018/10/31 15:19  
 * @Description: 重试机制  
  */ public class RetryerUtils {      /**  
 * 获取连接异常重试机制  
  * @param sleepTime 重试间隔休眠时间  
  * @param attemptNumber 重试次数  
  * @param c 重试异常  
  * @return  
  */  
  public static  Retryer getRetryer(long sleepTime, int attemptNumber,Class c) {  
  return RetryerBuilder  
  .newBuilder()  
  //设置抛出指定异常重试  
  .retryIfExceptionOfType(c)  
  //返回null也需要重试  
  .retryIfResult(Predicates.equalTo(null))  
  //重调策略  
  .withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))  
  //尝试次数  
  .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))  
  .build();  
  } }

使用示范


//重试机制 Retryer<String> retryer = RetryerUtils.getRetryer(2000, 3, ConnectException.class);

资料参考 https://juejin.im/post/5b1779bbf265da6e410e0bbb https://blog.csdn.net/aitangyong/article/details/53894997

琼ICP备18000156号

鄂公网安备 42011502000211号