重试利器之Guava-Retryer

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

前言

在日常开发中,我们经常会遇到需要调用外部服务和接口的场景。外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失败重试策略重新调用 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

        <dependency>
            <groupId>com.github.rholder</groupId>
            <artifactId>guava-retrying</artifactId>
            <version>2.0.0</version>
        </dependency>

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 <T> Retryer<T> getRetryer(long sleepTime, int attemptNumber,Class c) {
return RetryerBuilder
.<T>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号