000, multiplier = 2))
public void addOrder() {
System.out.println("重试...");
int i = 3 / 0;
// addOrder
}
@Recover
public void recover(RuntimeException e) {
log.error("达到最大重试次数", e);
}
}
该方法调用后会进行重试,最大重试次数为 3,第一次重试间隔为 2s,之后以 2 倍大小进行递增,第二次重试间隔为 4 s,第三次为 8s
Spring 的重试机制还支持很多很有用的特性,由三个注解完成:
- @Retryable
- @Backoff
- @Recover
查看 @Retryable 注解源码:指定异常重试、次数
public @interface Retryable {
// 设置重试拦截器的 bean 名称
String interceptor() default "";
// 只对特定类型的异常进行重试。默认:所有异常
Class<? extends Throwable>[] value() default {};
// 包含或者排除哪些异常进行重试
Class<? extends Throwable>[] include() default {};
Class<? extends Throwable>[] exclude() default {};
// l设置该重试的唯一标志,用于统计输出
String label() default "";
boolean stateful() default false;
// 最大重试次数,默认为 3 次
int maxAttempts() default 3;
String maxAttemptsExpression() default "";
// 设置重试补偿机制,可以设置重试间隔,并且支持设置重试延迟倍数
Backoff backoff() default @Backoff;
// 异常表达式,在抛出异常后执行,以判断后续是否进行重试
String exceptionExpression() default "";
String[] listeners() default {};
}
@Backoff 注解: 指定重试回退策略(如果因为网络波动导致调用失败,立即重试可能还是会失败,最优选择是等待一小会儿再重试。决定等待多久之后再重试的方法。通俗的说,就是每次重试是立即重试还是等待一段时间后重试)
@Recover 注解: 进行善后工作:当重试达到指定次数之后,会调用指定的方法来进行日志记录等操作
注意:
@Recover
注解标记的方法必须和被 @Retryable
标记的方法在同一个类中
- 重试方法抛出的异常类型需要与
recover()
方法参数类型保持一致
recover()
方法返回值需要与重试方法返回值保证一致
recover()
方法中不能再抛出 Exception,否则会报无法识别该异常的错误
这里还需要再提醒的一点是,由于 Spring Retry 用到了 Aspect 增强,所以就会有使用 Aspect 不可避免的坑——方法内部调用,如果被 @Retryable
注解的方法的调用方和被调用方处于同一个类中,那么重试将会失效
通过以上几个简单的配置,可以看到 Spring Retry 重试机制考虑的比较完善,比自己写AOP实现要强大很多
弊端:
但也还是存在一定的不足,Spring的重试机制只支持对 异常 进行捕获,而无法对返回值进行校验
@Retryable
public String hello() {
long current = count.incrementAndGet();
System.out.println("第" + current +"次被调用");
if (current % 3 != 0) {
log.warn("调用失败");
return "error";
}
return "success";
}
因此就算在方法上添加 @Retryable
,也无法实现失败重试
除了使用注解外,Spring Retry 也支持直接在调用时使用代码进行重试:
@Test
public void normalSpringRetry() {
// 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试
Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
exceptionMap.put(HelloRetryException.class, true);
// 构建重试模板实例
RetryTemplate retryTemplate = new RetryTemplate();
// 设置重试回退操作策略,主要设置重试间隔时间
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
long fixedPeriodTime = 1000L;
backOffPolicy.setBackOffPeriod(fixedPeriodTime);
// 设置重试策略,主要设置重试次数
int maxRetryTimes = 3;
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);
Boolean execute = retryTemplate.execute(
//RetryCallback
retryContext -> {
String hello = helloService.hello();
log.info("调用的结果:{}", hello);
return true;
},
// RecoverCallBack
retryContext -> {
//RecoveryCallback
log.info("已达到最大重试次数");
return false;
}
);
}
此时唯一的好处是可以设置多种重试策略:
NeverRetryPolicy
:只允许调用RetryCallback一次,不允许重试
AlwaysRetryPolicy
:允许无限重试,直到成功,此方式逻辑不当会导致死循环
SimpleRetryPolicy
:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
TimeoutRetryPolicy
:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
ExceptionClassifierRetryPolicy
:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区