业务场景:
在平时开发中经常会遇到需要调用接口和外部服务的场景,但是有些接口服务方不能立即返回数据,而是需要处理一段时间才能返回真实的业务数据,如果没有处理完则直接返回一个中间状态的结果。
类似于{"errorCode":"-1", "errorMsg":"处理中", "result":""}这样的结果,然后调用方需要过段时间(一般间隔几秒种后,需要根据具体的业务确认)再次调用才能获取真实的结果。
传统的写法如下(伪代码):
int time = 0;
do {
time++;
result = testRedo(); // 调用接口
} while (null == result && time < 5);
对于这种场景大家或多或少都遇到过,但上面这样的写法有几个很明显的弊端:
当然这些问题自己实现完全可以解决,但已经有现成的轮子我们可以直接拿来用
上述场景可以考虑使用google的guava-retry工具,guava-retryer的特点如下:
pom依赖:
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
git 地址:https://github.com/rholder/guava-retrying
下面的代码模拟接口调用,设置重试次数为5次,每次调用间隔为2秒,如果调用过程中出现异常或结果满足重试条件的则再次调用直到最大次数(抛出异 常):
// 重试条件
Predicate<String> condition = response -> Objects.nonNull(response)
&& "处理中".equals(response.getReturnCode());
Optional<String> response = RetryUtil.retry(
condition, // 重试条件
() -> invoke(request), // 重试任务(比如调用接口)
500, // 500ms重试一次, 可以做成配置化
3); // 一共重试3次, 可以做成配置化
return response.orElse(null);
RetryUtil是我对guava-retrying的封装实现,下面的代码大家可以直接拿去使用,只需要按照业务改下重试条件和重试任务以及重试间隔和次数即可:
/**
* 根据输入的condition重复做task,在规定的次数内达到condition则返回,
* 如果超过retryTimes则返回null, 重试次数,整个重试时间以及retry exception都会记录log
*
* @param condition 重试条件,比如接口返回errorCode为处理中,或不是最终需要的结果
* @param task 重试做的任务
* @param sleepTime 重试间隔时间,单位毫秒
* @param retryTimes 重试次数
* @return targetBean
*/
public static <V> Optional<V> retry(Predicate<V> condition, Callable<V> task, int sleepTime, int retryTimes) {
Optional<V> result = Optional.empty();
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
Retryer<V> retry = RetryerBuilder.<V>newBuilder()
// 默认任务执行过程中发生异常自动重试
.retryIfException()
// 重试条件(按照业务场景)
.retryIfResult(condition)
// 等待策略
.withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))
// 重试策略
.withStopStrategy(StopStrategies.stopAfterAttempt(retryTimes))
// 重试监听器
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
// 记录重试次数和异常信息
log.info(MessageFormat.format("{0}th retry", attempt.getAttemptNumber()));
if (attempt.hasException()) {
log.error(MessageFormat.format("retry exception:{0}", attempt.getExceptionCause()));
}
}
}).build();
// 开始执行重试任务
result = Optional.ofNullable(retry.call(task));
} catch (Exception e) {
log.error("retry fail:", e.getMessage());
} finally {
stopWatch.stop();
log.info("retry execute time", stopWatch.getTime());
}
return result;
}
重试间隔时间和重试次数可以做成可配置的,方便后续根据日志记录观察调整
相关重试策略和api介绍:
StopAfterAttemptStrategy:设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常
FixedWaitStrategy:固定等待时长策略
RandomWaitStrategy:随机等待时长策略
IncrementingWaitStrategy:递增等待时长策略
ExponentialWaitStrategy:指数等待时长策略
FibonacciWaitStrategy:斐波那契等待时长策略
ExceptionWaitStrategy:异常时长等待策略
CompositeWaitStrategy:复合时长等待策略
除了google的retry外,spring框架也提供了一个重试工具:spring-retry,该工具把重试操作模板定制化。还有RxJava里有个retry的api也能实现类似的用法,感兴趣的同学可以研究下。
点个在看支持我吧,转发就更好了