前言
在实际业务中,有非常多场景需要我们进行重试操作,编码中通过采用各种回调的方式来抽象重试的实现,但都不是那么理想。通过简单的调研,目前主要有Guava-Retry和Spring-Retry作为三方库比较流行,本章节将介绍Guava-Retry的实际应用。
Guave在github地址(https://github.com/rholder/guava-retrying),可以看到其已经有很长一段时间没有更新维护,但这并不影响其正常使用,他已经足够的稳定。其相比较于Spring-Retry在是否重试的判断条件上有更多的选择性,如类似的retryIf方法。
其主要接口及策略介绍:
Attempt:一次执行任务;
AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务);
BlockStrategies:任务阻塞策略(通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么……),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY 也就是调用 Thread.sleep(sleepTime);
RetryException:重试异常;
RetryListener:自定义重试监听器,可以用于异步记录错误日志;
StopStrategy:停止重试策略,提供三种:
- StopAfterDelayStrategy :设定一个最长允许的执行时间;比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException;
- NeverStopStrategy :不停止,用于需要一直轮训直到返回期望结果的情况;
- StopAfterAttemptStrategy :设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常;
WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:
- FixedWaitStrategy:固定等待时长策略;
- RandomWaitStrategy:随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)
- IncrementingWaitStrategy:递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)
- ExponentialWaitStrategy:指数等待时长策略;
- FibonacciWaitStrategy :Fibonacci 等待时长策略;
- ExceptionWaitStrategy :异常时长等待策略;
- CompositeWaitStrategy :复合时长等待策略;
本章概要
1、根据结果判断是否重试
2、根据异常判断是否重试
3、重试策略——设定无限重试
4、重试策略——设定最大的重试次数
5、等待策略——设定重试等待固定时长策略
6、等待策略——设定重试等待时长固定增长策略
7、等待策略——设定重试等待时长按指数增长策略
8、等待策略——设定重试等待时长按斐波那契数列增长策略
9、等待策略——组合重试等待时长策略
10、重试监听器——RetryListener实现重试过程细节处理
根据结果判断是否重试
场景:如果返回值不是'good'则需要重试,返回值通过counter控制,直到等于5方会返回’good‘。
示例代码:
private <T> T run(Retryer<T> retryer, Callable<T> callable) {
try {
return retryer.call(callable);
} catch (RetryException | ExecutionException e) {
LOGGER.trace(ExceptionUtils.getFullStackTrace(e));
LOGGER.warn(e.getMessage());
}
return null;
}
private Callable<String> callableWithResult() {
return new Callable<String>() {
int counter = 0;
public String call() throws Exception {
counter++;
LOGGER.info("do sth : {}", counter);
if (counter < 5) {
return "sorry";
}
return "good";
}
};
}
@Test
public void retryWithResult() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfResult(result -> !result.contains("good"))
.withStopStrategy(StopStrategies.neverStop())
.build();
run(retryer, callableWithResult());
}
打印:
根据异常判断是否重试
场景:如果counter值小于5则抛出异常,等于5则正常返回停止重试;
示例代码:
private Callable<String> callableWithException() {
return new Callable<String>() {
int counter = 0;
public String call() throws Exception {
counter++;
LOGGER.info("do sth : {}", counter);
if (counter < 5) {
throw new RuntimeException("sorry");
}
return "good";
}
};
}
@Test
public void retryWithException() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
重试策略——设定无限重试
场景:在有异常情况下,无限重试,直到返回正常有效结果;
示例代码:
@Test
public void retryNeverStop() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
重试策略——设定最大的重试次数
场景:在有异常情况下,最多重试3次,如果超过3次则会抛出异常;
示例代码:
@Test
public void retryStopAfterAttempt() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
等待策略——设定重试等待固定时长策略
场景:设定每次重试等待间隔固定为100ms;
示例代码:
@Test
public void retryWaitFixStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
基本每次的间隔维持在100ms。
等待策略——设定重试等待时长固定增长策略
场景:设定初始等待时长值,并设定固定增长步长,但不设定最大等待时长;
示例代码:
@Test
public void retryWaitIncreaseStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.incrementingWait(200, TimeUnit.MILLISECONDS, 100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
可以看到,等待时长依次为200ms、300ms、400ms、500ms,符合策略约定。
等待策略——设定重试等待时长按指数增长策略
场景:根据multiplier值按照指数级增长等待时长,并设定最大等待时长;
示例代码:
@Test
public void retryWaitExponentialStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.exponentialWait(100, 1000, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
可以看到,等待时长依次为200ms、400ms、800ms、1000ms,符合策略约定。
等待策略——设定重试等待时长按斐波那契数列增长策略
场景:根据multiplier值按照斐波那契数列增长等待时长,并设定最大等待时长,斐波那契数列:1、1、2、3、5、8、13、21、34、……
示例代码:
@Test
public void retryWaitFibonacciStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.fibonacciWait(100, 1000, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
可以看到,等待时长依次为100ms、100ms、200ms、300ms,符合策略约定。
等待策略——组合重试等待时长策略
场景:组合ExponentialWaitStrategy和FixedWaitStrategy策略。
示例代码:
@Test
public void retryWaitJoinStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.join(WaitStrategies.exponentialWait(25, 500, TimeUnit.MILLISECONDS)
, WaitStrategies.fixedWait(50, TimeUnit.MILLISECONDS)))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:
可以看到,等待时长依次为100(50+50)ms、150(100+50)ms、250(200+50)ms、450(400+50)ms,符合策略约定。
监听器——RetryListener实现重试过程细节处理
场景:定义两个监听器,分别打印重试过程中的细节,未来可更多的用于异步日志记录,亦或是特殊处理。
示例代码:
private RetryListener myRetryListener() {
return new RetryListener() {
@Override
public <T> void onRetry(Attempt<T> attempt) {
// 第几次重试,(注意:第一次重试其实是第一次调用)
LOGGER.info("[retry]time=" + attempt.getAttemptNumber());
// 距离第一次重试的延迟
LOGGER.info(",delay=" + attempt.getDelaySinceFirstAttempt());
// 重试结果: 是异常终止, 还是正常返回
LOGGER.info(",hasException=" + attempt.hasException());
LOGGER.info(",hasResult=" + attempt.hasResult());
// 是什么原因导致异常
if (attempt.hasException()) {
LOGGER.info(",causeBy=" + attempt.getExceptionCause().toString());
} else {
// 正常返回时的结果
LOGGER.info(",result=" + attempt.getResult());
}
// 增加了额外的异常处理代码
try {
T result = attempt.get();
LOGGER.info(",rude get=" + result);
} catch (ExecutionException e) {
LOGGER.error("this attempt produce exception." + e.getCause().toString());
}
}
};
}
private RetryListener myRetryListener2() {
return new RetryListener() {
@Override
public <T> void onRetry(Attempt<T> attempt) {
LOGGER.info("myRetryListener2 : [retry]time=" + attempt.getAttemptNumber());
}
};
}
private <T> T runWithFixRetryAndListener(Callable<T> callable) {
Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withRetryListener(myRetryListener())
.withRetryListener(myRetryListener2())
.build();
return run(retryer, callable);
}
@Test
public void retryWithRetryListener() {
LOGGER.info("result : " + runWithFixRetryAndListener(callableWithException()));
}
打印:
RetryListener会根据注册顺序执行。