WHAT
@Retryable由spring-retry模块提供,在方法或类上添加@Retryable注解可以实现方法调用失败的重试。可以指定失败重试的次数、fallback方法
@Retryable
设置重试的次数、指定需要重试的异常
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
// 指定拦截方法
String interceptor() default "";
// 指定需要重试异常,若未指定异常则重试异常为 Exception.class
Class<? extends Throwable>[] value() default {};
// 最大尝试次数(包含第一次
int maxAttempts() default 3;
Class<? extends Throwable>[] include() default {};
Class<? extends Throwable>[] exclude() default {};
String label() default "";
boolean stateful() default false;
String maxAttemptsExpression() default "";
Backoff backoff() default @Backoff();
String exceptionExpression() default "";
}
@Backoff
设置重试的时间间隔,不同值的组合会确定不同的计算间隔方式
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {
long value() default 1000;
// 若delay=0未配置,则delay=value
long delay() default 0;
long maxDelay() default 0;
double multiplier() default 0;
String delayExpression() default "";
String maxDelayExpression() default "";
String multiplierExpression() default "";
boolean random() default false;
}
@Recover
重试失败后会进入@Recover注解的方法
WHY
在项目中假设调用的外部服务发生网络异常、服务器故障、死机状况,在这些情况下通常会重试几次调用,假如最终还是失败则会返回特定的内容。如果可能在后续的尝试中会成功,则有重试的必要。你可能会写一段循环代码然后计数来实现重试功能,@Retryable提供了更便捷的方式来实现错误重试
BEFORE:
int retries = 0;
long wait = 1;
while (retries < maxRetries) {
TimeUnit.SECONDS.sleep(wait);
// 处理任务
wait *= 2;
++retries;
}
AFTER:
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000,multiplier = 1.5,maxDelay = 3000))
String retryForError();
@Recover
String recoverResponse(Exception e);
HOW
@Retryable如何使用?怎么实现的?
- Requirements
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${version}</version>
</dependency>
事例
public interface RetryService {
@Retryable(maxAttempts = 3,
backoff = @Backoff(delay = 1000,multiplier = 1.5,maxDelay = 3000),
value = ArithmeticException.class)
String retryForError();
@Recover
String recoverResponse(ArithmeticException e);
}
public class RetryServiceImpl implements RetryService {
@Override
public String retryForError() {
System.out.println("retryForError");
int a = 10 / 0;
return "Success";
}
@Override
public String recoverResponse(ArithmeticException e) {
return "RecoverResponse Success";
}
}
@RestController
public class RetryController {
@Resource
private RetryService retryService;
@GetMapping("/retry")
public String retryService() {
return retryService.retryForError();
}
}
原理
AOP
- RetryConfiguration 创建pointcut(Retryable注解), 创建Advice(AnnotationAwareRetryOperationsInterceptor)
- AnnotationAwareRetryOperationsInterceptor 委托给默认的 RetryOperationsInterceptor
BackOffPolicy
@Backoff
- multiplier > 0 and random = false
initialInterval = delay
maxInterval = maxDelay > initialInterval ? maxDelay : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL(30000毫秒)
public synchronized long getSleepAndIncrement() {
long sleep = this.interval;
if (sleep > maxInterval) {
sleep = maxInterval;
}
else {
this.interval = getNextInterval();
}
return sleep;
}
protected long getNextInterval() {
return (long) (this.interval * this.multiplier);
}
- multiplier > 0 and random = true
public synchronized long getSleepAndIncrement() {
// 1 中方法
long next = super.getSleepAndIncrement();
next = (long) (next * (1 + r.nextFloat() * (getMultiplier() - 1)));
return next;
}
- maxDelay > initialInterval
long delta = maxDelay==initialInterval ? 0 : random.nextInt((int) (maxDelay - minBackOffPeriod));
sleeper.sleep(minBackOffPeriod + delta );
- 其他
sleeper.sleep(delay);
注意点
- @Recover只对同类中的@Retryable生效
- 多个@Recover方法,同一种异常参数。相当于讲这些方法(Method)放在Set中,取出的第一个@Recover方法将会是fallback方法
- 多个@Recover方法,不同种异常参数。若@Recover方法参数距离@Retryable方法抛出的异常最近(根据重试的最后一次抛出的异常作为查找依据)则此@Recover方法将会是fallback方法。
RecoverAnnotationRecoveryHandler.findClosestMatch