介绍
API 接口调用异常, 网络异常在我们日常开发中经常会遇到,这种情况下我们需要先重试几次调用才能将其标识为错误并在确认错误之后发送异常提醒。guava-retry可以灵活优雅的实现这一功能。
Guava retryer在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。Guava Retryer也是线程安全的,入口调用逻辑采用的是Java.util.concurrent.Callable的call方法。
详细说明
RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。
RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,通过retryIfException 和retryIfResult设置,同时支持多个且能兼容。
retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。
retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error
如:
.retryIfExceptionOfType(Error.class)// 只在抛出error重试
当然我们还可以在只有出现指定的异常的时候才重试,如:
.retryIfExceptionOfType(IllegalStateException.class) .retryIfExceptionOfType(NullPointerException.class)
或者通过Predicate实现
.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),Predicates.instanceOf(IllegalStateException.class)))
retryIfResult可以指定你的Callable方法在返回值的时候进行重试,如
// 返回false重试 .retryIfResult(Predicates.equalTo(false)) //以_error结尾才重试 .retryIfResult(Predicates.containsPattern("_error$"))
当发生重试之后,假如我们需要做一些额外的处理动作,比如log一下异常,那么可以使用RetryListener。每次重试之后,guava-retrying会自动回调我们注册的监听。可以注册多个RetryListener,会按照注册顺序依次调用。
.withRetryListener(new RetryListener {
@Override
public
logger.error("第【{}】次调用失败" , attempt.getAttemptNumber());
}
}
)
主要接口介绍
Attempt:一次执行任务;
AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务);
BlockStrategies:任务阻塞策略(通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么……),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY 也就是调用 Thread.sleep(sleepTime);
RetryException:重试异常;
RetryListener:自定义重试监听器,可以用于异步记录错误日志;
StopStrategy:停止重试策略,提供三种:
WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:
工具类源码:
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.AttemptTimeLimiters;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.RetryListener;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Created by pandechuan on 2018/2/24.
* 基于guava-retry的重试工具类
*/
public class RetryUtil {
private static final Logger logger = LoggerFactory.getLogger(RetryUtil.class);
/**
* @param task 要重试执行得任务
* @param fixedWaitTime 本次重试与上次重试之间的固定间隔时长
* @param maxEachExecuTime 一次重试的最大执行的时间
* @param timeUnit 时间单位
* @param attemptNumber 重试次数
*/
public static <T> T retry(Callable<T> task, long fixedWaitTime, long maxEachExecuTime, TimeUnit timeUnit, int attemptNumber) {
Retryer<T> retryer = RetryerBuilder
.<T>newBuilder()
//抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
.retryIfException()
//重试策略
.withWaitStrategy(WaitStrategies.fixedWait(fixedWaitTime, timeUnit))
//尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))
//每次重试执行的最大时间限制
.withAttemptTimeLimiter(AttemptTimeLimiters.<T>fixedTimeLimit(maxEachExecuTime, timeUnit))
//重试监听器
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
if (attempt.hasException()) {
logger.error("第【{}】次重试失败", attempt.getAttemptNumber(), attempt.getExceptionCause());
}
}
}
).build();
T t = null;
try {
t = retryer.call(task);
} catch (ExecutionException e) {
logger.error("", e);
} catch (RetryException e) {
logger.error("", e);
}
return t;
}
/**
* @param task 要重试执行得任务
* @param predicate 符合预期结果需要重试
* @param fixedWaitTime 本次重试与上次重试之间的固定间隔时长
* @param maxEachExecuTime 一次重试的最大执行的时间
* @param attemptNumber 重试次数
*/
public static <T> T retry(Callable<T> task, Predicate<T> predicate, long fixedWaitTime, long maxEachExecuTime, TimeUnit timeUnit, int attemptNumber) {
Retryer<T> retryer = RetryerBuilder
.<T>newBuilder()
//抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
.retryIfException()
//对执行结果的预期。符合预期就重试
.retryIfResult(predicate)
//每次重试固定等待fixedWaitTime时间
.withWaitStrategy(WaitStrategies.fixedWait(fixedWaitTime, timeUnit))
//尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))
//每次重试执行的最大时间限制(在规定的时间内没有返回结果会TimeoutException)
.withAttemptTimeLimiter(AttemptTimeLimiters.<T>fixedTimeLimit(maxEachExecuTime, timeUnit))
//重试监听器
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
if (attempt.hasException()) {
logger.error("第【{}】次重试失败", attempt.getAttemptNumber(), attempt.getExceptionCause());
}
}
}
).build();
T t = null;
try {
t = retryer.call(task);
} catch (ExecutionException e) {
logger.error("", e);
} catch (RetryException e) {
logger.error("", e);
}
return t;
}
public static void main(String[] args) {
// here shows thress kinds of test case
Callable
int a = 1 / 0;
return 2;
};
Callable
Thread.sleep(2000L);
return 2;
};
Callable
return false;
};
//异常重试
Integer result = OptionalUtil.get(() -> retry(task, 30L, 1000L, TimeUnit.MILLISECONDS, 3)).orElseGet((() -> 0));
logger.info("result: {}", result);
//超时重试
Integer result2 = OptionalUtil.get(() -> retry(task2, 30L, 1000L, TimeUnit.MILLISECONDS, 3)).orElseGet(() -> 0);
logger.info("result: {}", result2);
//预期值重试
boolean result3 = OptionalUtil.get(() -> retry(task3, Predicates.equalTo(false), 30L, 1000L, TimeUnit.MILLISECONDS, 3)).orElseGet(() -> true);
logger.info("result: {}", result3);
}
}