0%

Spring重试机制之@Retryable注解

本文介绍Spring中的重试机制@Retryable注解

abstract.png

配置

添加spring retry依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.4</version>
</dependency>

Spring Boot启动类上添加 @EnableRetry 注解,使能重试机制

1
2
3
4
5
6
7
// 使能重试机制
@EnableRetry(proxyTargetClass = true)
public class SpringBoot1Application {
public static void main(String[] args) {
SpringApplication.run(SpringBoot1Application.class, args);
}
}

@Retryable注解之重试

@Retryable 注解的基本属性:

  • include属性:指定需要重试的异常
  • maxAttempts属性:最大重试次数(包括第一次调用失败)
  • backoff属性:重试退避策略。指定两次重试之间的延迟时间

需要注意的是:在配置的重试次数内,如果方法最终执行成功(没有抛出异常),则调用者将得到方法的返回结果,就像正常调用一样。在这种情况下,调用者无需捕获异常;如果在所有的重试尝试之后方法仍然失败(即继续抛出异常),则会抛出异常。这时,调用者可以选择是否捕获这个异常进行处理

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;

@RestController
@RequestMapping("testRetry")
public class RetryTestController {

private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

private static Integer count = 0;

@Autowired
private RetryTestController retryTestController;

@RequestMapping("/test1")
public void test1() {
try {
System.out.println("\nTest A:对指定异常重试,且方法最终执行成功");
count = 0;
retryTestController.callRpc1(111);
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}

try {
System.out.println("\nTest B:对指定异常重试,且方法最终未执行成功");
count = 0;
retryTestController.callRpc1(222);
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}

try {
System.out.println("\nTest C:抛出未指定异常");
count = 0;
callRpc1(333);
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}
}


@Retryable(include = {IOException.class, ClassNotFoundException.class}, maxAttempts = 5,
backoff = @Backoff(delay = 2000) //每次重试时,固定延迟2秒
)
public void callRpc1(int param) throws IOException, ClassNotFoundException {
count++;
String nowTime = formatter.format(new Date());
System.out.println(nowTime + " : call rpc1 method, param: " + param + " .第" + count + "次");
if (param == 111) {
if (count <= 2) {
throw new IOException();
}
} else if (param == 222) {
throw new ClassNotFoundException();
} else if (param == 333) {
throw new EOFException();
}
}

}

figure 1.png

@Backoff注解之指数退避

该注解用于指定重试的退避策略。除了上文的固定延迟外。其还可以实现指数退避,具体地:

  • multiplier属性:重试延迟时间的倍增因子,实现指数退避。默认为0,意为非指数退避
  • maxDelay属性:重试最大延时时间。当使用指数退避时使用,防止延时时间增长过快,默认为无限大
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
40
41
42
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;


@RestController
@RequestMapping("testRetry")
public class RetryTestController {

private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

private static Integer count = 0;

@Autowired
private RetryTestController retryTestController;

@RequestMapping("/test2")
public void test2() {
try {
System.out.println("重试策略:指数退避");
count = 0;
retryTestController.callRpc2();
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}
}

@Retryable(include = Exception.class, maxAttempts = 10,
// 重试时初始延迟时间为2秒,每次重试延迟时间为上次延迟时间的3倍。最大延迟时间为30秒
// 第1次延迟时间:2秒
// 第2次延迟时间:6秒
// 第3次延迟时间:18秒
// 第4次延迟时间:30秒
// 第5次延迟时间:30秒
backoff = @Backoff(delay = 2 * 1000, multiplier = 3, maxDelay = 30 * 1000))
public void callRpc2() throws IOException {
count++;
String nowTime = formatter.format(new Date());
System.out.println(nowTime + " : call rpc2 method. 第" + count + "次");
throw new IOException();
}

}

figure 2.png

@Recover注解之降级方法

@Retryable注解的recover属性可用于指定重试方法失败后,所调用的降级方法名称。而对于降级方法而言,则需要添加@Recover注解。同时该降级方法需要满足以下条件:

  • 必须与 相应的@Retryable方法 在同一个类中
  • 第一个参数必须是 Throwable 或 相应的@Retryable方法 可能抛出的异常类型
  • 剩余参数(如果有)必须与 相应的@Retryable方法签名 保持一致
  • 返回类型 必须与 相应的@Retryable方法的返回类型 保持一致
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;

@RestController
@RequestMapping("testRetry")
public class RetryTestController {

private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

private static Integer count = 0;

@Autowired
private RetryTestController retryTestController;

@RequestMapping("/test3")
public void test3() {
try {
System.out.println("\nTest A:经过重试后仍然失败,走降级方法");
count = 0;
String res = retryTestController.callRpc3(8);
System.out.println("res : " + res);
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}

try {
System.out.println("\nTest B:经过重试后成功,不会走降级方法");
count = 0;
String res = retryTestController.callRpc3(3);
System.out.println("res : " + res);
} catch (Exception e) {
System.out.println("Happen Exception: " + e.getClass().getSimpleName());
}
}

@Retryable( include = Exception.class, maxAttempts = 5,
// recover属性:指定重试后依然失败时,所调用的降级方法名称
recover = "callRpc3ByDefault",
backoff = @Backoff(delay = 3000)
)
public String callRpc3(Integer index) throws IOException {
count++;
String nowTime = formatter.format(new Date());
System.out.println(nowTime + " : call rpc3 method. 第" + count + "次");
if (count < index) {
throw new IOException();
}
return "OK";
}

@Recover
public String callRpc3ByDefault(Throwable throwable, Integer index) {
System.out.println("callRpc3ByDefault Method, param msg:");
System.out.println("throwable : " + throwable.getClass().getSimpleName());
System.out.println("index : " + index);
return "NA";
}
}

figure 3.png

请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝