本文介绍Spring中的重试机制@Retryable注解
配置
添加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(); } }
}
|
@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(); } }
|
@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"; } }
|