Spring中的@Retryable | 8lovelife's life
0%

Spring中的@Retryable

WHAT

@Retryable由spring-retry模块提供,在方法或类上添加@Retryable注解可以实现方法调用失败的重试。可以指定失败重试的次数、fallback方法

@Retryable

设置重试的次数、指定需要重试的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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

设置重试的时间间隔,不同值的组合会确定不同的计算间隔方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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提供了更便捷的方式来实现错误重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <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>

事例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

  1. RetryConfiguration 创建pointcut(Retryable注解), 创建Advice(AnnotationAwareRetryOperationsInterceptor)
  2. AnnotationAwareRetryOperationsInterceptor 委托给默认的 RetryOperationsInterceptor

BackOffPolicy

@Backoff

  1. multiplier > 0 and random = false
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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);
    }
  2. multiplier > 0 and random = true
    1
    2
    3
    4
    5
    6
    public synchronized long getSleepAndIncrement() {
    // 1 中方法
    long next = super.getSleepAndIncrement();
    next = (long) (next * (1 + r.nextFloat() * (getMultiplier() - 1)));
    return next;
    }
  3. maxDelay > initialInterval
    1
    2
    long delta = maxDelay==initialInterval ? 0 : random.nextInt((int) (maxDelay - minBackOffPeriod));
    sleeper.sleep(minBackOffPeriod + delta );
  4. 其他
    1
    sleeper.sleep(delay);

注意点

  1. @Recover只对同类中的@Retryable生效
  2. 多个@Recover方法,同一种异常参数。相当于讲这些方法(Method)放在Set中,取出的第一个@Recover方法将会是fallback方法
  3. 多个@Recover方法,不同种异常参数。若@Recover方法参数距离@Retryable方法抛出的异常最近(根据重试的最后一次抛出的异常作为查找依据)则此@Recover方法将会是fallback方法。
    1
    RecoverAnnotationRecoveryHandler.findClosestMatch

If you find that one thing that keeps you going, you’ve got to hold onto it. - Red

The Shawshank Redemption