前言
在实际业务中,有非常多场景需要我们进行重试操作,编码中通过采用各种回调的方式来抽象重试的实现,但都不是那么理想。通过简单的调研,目前主要有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 callableWithResult() {
return new
Callable() {
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 retryer = RetryerBuilder.
newBuilder
()
.retryIfResult(result -> !result.contains(
"good"
))
.withStopStrategy(StopStrategies.
neverStop
())
.build();
run(retryer, callableWithResult());
}
打印:
根据异常判断是否重试
场景:如果counter值小于5则抛出异常,等于5则正常返回停止重试;
示例代码:
private
Callable callableWithException() {
return new
Callable() {
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 retryer = RetryerBuilder.
newBuilder
()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.
neverStop
())
.build();
LOGGER
.info(
"result : "
+ run(retryer, callableWithException()));
}
打印:
重试策略——设定无限重试
场景:在有异常情况下,无限重试,直到返回正常有效结果;
示例代码:
@Test
public void
retryNeverStop() {
Retryer retryer = RetryerBuilder.
newBuilder
()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.
neverStop
())
.build();
LOGGER
.info(
"result : "
+ run(retryer, callableWithException()));
}
打印:
重试策略——设定最大的重试次数
场景:在有异常情况下,最多重试3次,如果超过3次则会抛出异常;
示例代码:
@Test
public void
retryStopAfterAttempt() {
Retryer retryer = RetryerBuilder.
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 retryer = RetryerBuilder.
newBuilder
()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.
neverStop
())
.withWaitStrategy(WaitStrategies.
fixedWait
(
100
, TimeUnit.
MILLISECONDS
))
.build();
LOGGER
.info(
"result : "
+ run(retryer, callableWithException()));
}
打印:
基本每次的间隔维持在100ms。
等待策略——设定重试等待时长固定增长策略
场景:设定初始等待时长值,并设定固定增长步长,但不设定最大等待时长;
示例代码:
@Test
public void
retryWaitIncreaseStrategy() {
Retryer retryer = RetryerBuilder.
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 retryer = RetryerBuilder.
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 retryer = RetryerBuilder.
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 retryer = RetryerBuilder.
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会根据注册顺序执行。