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

配置
添加spring retry依赖
| 12
 3
 4
 5
 
 | <dependency><groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
 <version>1.3.4</version>
 </dependency>
 
 | 
Spring Boot启动类上添加 @EnableRetry 注解,使能重试机制
| 12
 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属性:重试退避策略。指定两次重试之间的延迟时间
需要注意的是:在配置的重试次数内,如果方法最终执行成功(没有抛出异常),则调用者将得到方法的返回结果,就像正常调用一样。在这种情况下,调用者无需捕获异常;如果在所有的重试尝试之后方法仍然失败(即继续抛出异常),则会抛出异常。这时,调用者可以选择是否捕获这个异常进行处理
| 12
 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();
 }
 }
 
 }
 
 | 

@Backoff注解之指数退避
该注解用于指定重试的退避策略。除了上文的固定延迟外。其还可以实现指数退避,具体地:
- multiplier属性:重试延迟时间的倍增因子,实现指数退避。默认为0,意为非指数退避
- maxDelay属性:重试最大延时时间。当使用指数退避时使用,防止延时时间增长过快,默认为无限大
| 12
 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();
 }
 
 }
 
 | 

@Recover注解之降级方法
@Retryable注解的recover属性可用于指定重试方法失败后,所调用的降级方法名称。而对于降级方法而言,则需要添加@Recover注解。同时该降级方法需要满足以下条件:
- 必须与 相应的@Retryable方法 在同一个类中
- 第一个参数必须是 Throwable 或 相应的@Retryable方法 可能抛出的异常类型
- 剩余参数(如果有)必须与 相应的@Retryable方法签名 保持一致
- 返回类型 必须与 相应的@Retryable方法的返回类型 保持一致
| 12
 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";
 }
 }
 
 | 
