Guava-Retry实践

前言
在实际业务中,有非常多场景需要我们进行重试操作,编码中通过采用各种回调的方式来抽象重试的实现,但都不是那么理想。通过简单的调研,目前主要有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,符合策略约定。

等待策略——组合重试等待时长策略
场景:组合 ExponentialWaitStrategyFixedWaitStrategy策略。

示例代码:
@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会根据注册顺序执行。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值