google的guava-retrying
特点
支持设置重试次数和间隔时间,支持多种复杂场景的重试策略,延迟策略
而且支持多个异常或者自定义实体对象的重试源,让重试功能有更多的灵活性
线程安全,我们只需要关注我们的业务逻辑实现即可
内部使用线程池管理线程
基于命令模式使用链式调用,使用方便
pom
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
用法
// 重试条件
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介绍
AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务)
BlockStrategies:任务阻塞策略,默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY,也就是调用Thread.sleep ()
StopStrategy:停止重试策略,提供三种:
StopAfterDelayStrategy:设定一个最长允许的执行时间,比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException
NeverStopStrategy:不停止,用于需要一直轮询直到返回期望结果的情况
StopAfterAttemptStrategy:设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常
WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:
FixedWaitStrategy:固定等待时长策略
RandomWaitStrategy:随机等待时长策略
IncrementingWaitStrategy:递增等待时长策略
ExponentialWaitStrategy:指数等待时长策略
FibonacciWaitStrategy:斐波那契等待时长策略
ExceptionWaitStrategy:异常时长等待策略
CompositeWaitStrategy:复合时长等待策略
spring的spring-retry
pom
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
用法
package hello;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@Service
public class RemoteService {
@Retryable(value= {RemoteAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 5000l,multiplier = 1))
public void call() throws Exception {
System.out.println("do something...");
throw new RemoteAccessException("RPC调用异常");
}
@Recover
public void recover(RemoteAccessException e) {
System.out.println(e.getMessage());
}
}
@Retryable注解
被注解的方法发生异常时会重试
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有
@Backoff注解
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调
添加@EnableRetry注解,启用重试功能
package hello;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@EnableRetry
public class Application {
public static void main(String[] args) throws Exception {
ApplicationContext annotationContext = new AnnotationConfigApplicationContext("hello");
RemoteService remoteService = annotationContext.getBean("remoteService", RemoteService.class);
remoteService.call();
}
}