文章目录
背景
项目中使用阿里云进行文件上传等操作,有时候上传会因为网络问题导致超时;或者在调用一些三方的操作上会导致超时,这时候应该在程序中进行重试.平常重试我们会使用try-catch然后一些判断去进行重试,写起来极其不优雅,不方便.guava-retrying就是为了解决以上痛点的.
使用
可以先考到测试项目,执行一下基本就了解了
首先引入:
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
简单的使用案例:
import com.github.rholder.retry.*;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* @ClassName RetryerTest
* @Author yida
* @Date 2021/8/18 5:08 下午
* @Description RetryerTest
*/
@Slf4j
public class RetryerTest {
private static int i = 0;
public static void main(String[] args) throws Exception {
System.out.println("请求结束返回:"+test());
}
public static Integer test() throws Exception {
Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
// 抛出runtime异常、checked异常时都会重试,但是抛出error不会重试
.retryIfException()
//3秒后重试
.withWaitStrategy(WaitStrategies.fixedWait(3000, TimeUnit.MILLISECONDS))
//最多重试次数,后面可接入apollo
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
// 执行时间不超过5秒
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(5, TimeUnit.SECONDS))
// 回调监听,不管成功失败都会执行listener
.withRetryListener(new RetryListener() {
@Override
public <Integer> void onRetry(Attempt<Integer> attempt) {
System.out.println("listener开始....");
// 第几次重试,(注意:第一次重试其实是第一次调用)
System.out.print("[重试的次数]time=" + attempt.getAttemptNumber());
// 距离第一次重试的延迟
System.out.print(",重试的延迟delay=" + attempt.getDelaySinceFirstAttempt());
// 重试结果: 是异常终止, 还是正常返回
System.out.print(",hasException=" + attempt.hasException());
System.out.print(",hasResult=" + attempt.hasResult());
// 是什么原因导致异常
if (attempt.hasException()) {
System.out.print(",causeBy=" + attempt.getExceptionCause().toString());
} else {
// 正常返回时的结果
System.out.print(",result=" + attempt.getResult());
}
// bad practice: 增加了额外的异常处理代码
try {
Integer result = attempt.get();
System.out.println("在listener里面处理结果,result get=" + result);
} catch (Exception e) {
System.out.println("在listener里面处理异常exception." + e.getCause().toString());
}
System.out.println("listener结束....\n\r\n\r");
}
})
.build();
Integer call = retryer.call(() -> {
System.out.println("同一规则,可以执行多份代码");
return 0;
});
return retryer.call(() -> {
// 前两次模拟异常,第三次成功
if (i >= 2) {
return 0;
} else {
i++;
}
try {
// 模拟请求异常
return 1 / 0;
} catch (Exception e) {
// 打印异常
System.out.println("执行代码,请求失败" + e);
throw e;
}
});
}
}
返回的结果
同一规则,可以执行多份代码
listener开始....
[重试的次数]time=1,重试的延迟delay=2,hasException=false,hasResult=true,result=0在listener里面处理结果,result get=0
listener结束....
执行代码,请求失败java.lang.ArithmeticException: / by zero
listener开始....
[重试的次数]time=1,重试的延迟delay=0,hasException=true,hasResult=false,causeBy=java.lang.ArithmeticException: / by zero在listener里面处理异常exception.java.lang.ArithmeticException: / by zero
listener结束....
执行代码,请求失败java.lang.ArithmeticException: / by zero
listener开始....
[重试的次数]time=2,重试的延迟delay=3006,hasException=true,hasResult=false,causeBy=java.lang.ArithmeticException: / by zero在listener里面处理异常exception.java.lang.ArithmeticException: / by zero
listener结束....
listener开始....
[重试的次数]time=3,重试的延迟delay=6011,hasException=false,hasResult=true,result=0在listener里面处理结果,result get=0
listener结束....
请求结束返回:0
Process finished with exit code 0
Api详解
通过RetryerBuilder创建Retryer对象,使用Retryer对象执行call方法.Retryer对象可以执行多个call方法,不过是按照顺序执行的.所以一般使用都是通过RetryerBuilder新建Retryer对象去执行.
RetryerBuilder是一个factory建立者,能够定制设置重试源且能够支持多个重试源,能够配置重试次数或重试超时时间,以及能够配置等待时间间隔,建立重试者Retryer实例。
RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,经过retryIfException 和retryIfResult设置,同时支持多个且能兼容。
RetryerBuilder的部分代码:
public class RetryerBuilder<V> {
private AttemptTimeLimiter<V> attemptTimeLimiter;
private StopStrategy stopStrategy;
private WaitStrategy waitStrategy;
private BlockStrategy blockStrategy;
private Predicate<Attempt<V>> rejectionPredicate = Predicates.alwaysFalse();
private List<RetryListener> listeners = new ArrayList<RetryListener>();
private RetryerBuilder() {
}
public static <V> RetryerBuilder<V> newBuilder() {
return new RetryerBuilder<V>();
}
// 省略
retryIfException,抛出runtime异常、checked异常时都会重试,可是抛出error不会重试。
retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。
.retryIfExceptionOfType(NullPointerException.class)
.retryIfExceptionOfType(IllegalStateException.class)
retryIfExceptionOfType容许咱们只在发生特定异常的时候才重试,好比NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error;
当发生重试以后,可使用RetryListener进行监听.能够注册多个RetryListener,会按照注册顺序依次调用。测试代码写的很清楚了.
项目中使用
可以通过aop对指定方法进行重试.
新建自定义重试注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RetryAnnotation {
}
然后写个切面对retryAnnotation进行切面处理.
@Component
@Aspect
@Slf4j
public class RetryAspect {
@Around("@annotation(com.study.springbootplus.config.RetryAnnotation)")
public Object process(ProceedingJoinPoint joinPoint) throws Exception {
// 参数可以改到配置文件中
Retryer<Object> retryer = RetryerBuilder.newBuilder()
// 抛出runtime异常、checked异常时都会重试,但是抛出error不会重试
.retryIfException()
//3秒后重试
.withWaitStrategy(WaitStrategies.fixedWait(3000, TimeUnit.MILLISECONDS))
//最多重试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();
Object[] parameters = joinPoint.getArgs();
return retryer.call(() -> {
try {
return joinPoint.proceed();
} catch (Throwable ex) {
log.error("RetryAspect processed error,parameters={},ex={}", JSON.toJSON(parameters), ex);
if (ex instanceof Exception) {
throw (Exception) ex;
} else {
throw new Exception(ex);
}
}
});
}
}