spring-retry是spring社区的一个成员,它提供了一种对失败操作进行自动重试的能力,可以作为某些瞬时错误(例如短暂的网络抖动)的解决方案。
作为spring生态的一部分,spring-retry自然地支持声明式(Declarative)方式使用。此外,它也支持命令式(Impertive)方式在代码里直接调用。
1. 项目集成
引入依赖:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
它的版本交给spring boot管理,以获得与spring良好的兼容性。
项目当前使用的是spring-boot版本为1.5.13.RELEASE,它管理的spring-retry版本是1.2.2.RELEASE。
2. 声明式使用方式
-
在spring boot启动类上增加
@EnableRetry
注解:@EnableRetry(proxyTargetClass = true) @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
该注解的proxyTargetClass属性默认为false,表示使用JDK的动态代理。如果设置为true,则表示使用CGLIB作为动态代理。
-
在需要重试的方法上,增加
@Retryable
注解:@Service @Slf4j public class RetryAnnotationTest { @Retryable(value = {RemoteAccessException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000L), recover = "recoverCall") public boolean call(String param){ System.out.println(new Date()); return RetryTask.retryTask(param); } @Recover public boolean recoverCall(Exception e,String param) { log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:", e); return false; } }
如上示例中,当call方法抛出
RemoteAccessException
异常时,spring retry会重新调用call方法,重试次数为5次,两次重试之间间隔为1s。如果超过最大重试次数仍未成功,或者抛出非
RemoteAccessException
异常,则调用recoverCall方法。注:@Retryable注解也可以作用在类上,作用在类上之后,spring retry会对类的全部方法进行重试。
3. 命令式使用方式
-
配置RetryTemplate:
@Configuration public class SpringRetryConfig { @Bean("retryTemplateFixed") public RetryTemplate retryTemplateFixed() { // 1.重试策略 // 触发条件 Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>(); exceptionMap.put(RemoteAccessException.class, true); // 重试次数设置为3次 int maxAttempts = 3; SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxAttempts, exceptionMap); // 2.重试间隔设置为1秒 FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); backOffPolicy.setBackOffPeriod(1000); // 3.构造RetryTemplate RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(retryPolicy); retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } @Bean("retryTemplate") public RetryTemplate retryTemplate() { // 定义简易重试策略,最大重试次数为3次,重试间隔为3s return RetryTemplate.builder() .maxAttempts(3) .fixedBackoff(3000) .retryOn(RuntimeException.class) .build(); } }
注:可以配置多个RetryTemplate,用以适应不同重试场景。
spring retry支持的重试策略和退避策略如下:
@Bean("retryTemplateDemo") public RetryTemplate retryTemplateDemo() { // 1.重试策略 // 不重试 NeverRetryPolicy neverRetryPolicy = new NeverRetryPolicy(); // 无限重试 AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy(); // 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试 ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy(); final Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>(3); policyMap.put(IOException.class, alwaysRetryPolicy); policyMap.put(InterruptedIOException.class, neverRetryPolicy); policyMap.put(UnknownHostException.class, neverRetryPolicy); exceptionClassifierRetryPolicy.setPolicyMap(policyMap); // 固定次数重试,默认最大重试次数为5次,RetryTemplate默认重试策略 SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(); simpleRetryPolicy.setMaxAttempts(5); // 超时时间重试,默认超时时间为1秒,在指定的超时时间内重试 TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy(); timeoutRetryPolicy.setTimeout(3000); /* * 组合重试策略,有两种组合方式: * 1.悲观默认重试,有不重试的策略则不重试。 * 2.乐观默认不重试,有需要重试的策略则重试。 */ CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy(); compositeRetryPolicy.setOptimistic(true); compositeRetryPolicy.setPolicies(new RetryPolicy[]{simpleRetryPolicy, timeoutRetryPolicy}); // 有熔断功能的重试 CircuitBreakerRetryPolicy circuitBreakerRetryPolicy = new CircuitBreakerRetryPolicy(compositeRetryPolicy); // 5s内失败10次,则开启熔断 circuitBreakerRetryPolicy.setOpenTimeout(5000); // 10s之后熔断恢复 circuitBreakerRetryPolicy.setResetTimeout(10000); // 2.退避策略(上一次执行失败之后,间隔多久进行下一次重试) // 立即重试 NoBackOffPolicy noBackOffPolicy = new NoBackOffPolicy(); // 固定时间后重试,默认1s FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); fixedBackOffPolicy.setBackOffPeriod(1000); // 随机时间后重试(如下:从500ms到1500ms内取一个随机时间后进行重试) UniformRandomBackOffPolicy uniformRandomBackOffPolicy = new UniformRandomBackOffPolicy(); uniformRandomBackOffPolicy.setMinBackOffPeriod(500); uniformRandomBackOffPolicy.setMaxBackOffPeriod(1500); // 指数退避策略(如下:初始休眠时间100ms,最大休眠时间30s,下一次休眠时间为当前休眠时间*2) ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy(); exponentialBackOffPolicy.setInitialInterval(100); exponentialBackOffPolicy.setMaxInterval(30000); exponentialBackOffPolicy.setMultiplier(2); // 随机指数退避策略 ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy(); exponentialRandomBackOffPolicy.setInitialInterval(100); exponentialRandomBackOffPolicy.setMaxInterval(30000); exponentialRandomBackOffPolicy.setMultiplier(2); // 3.return RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(circuitBreakerRetryPolicy); return retryTemplate; }
-
在代码中调用
@Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class RetryTest { // 注入RetryTemplate @Resource private RetryTemplate retryTemplateFixed; @Test public void test() { // 执行 Boolean execute = retryTemplateFixed.execute( // 重试回调 retryContext -> { System.out.println(new Date()); boolean b = RetryTask.retryTask("abc"); log.info("调用的结果:{}", b); return b; }, // 恢复回调(达到最大重试次数,或者抛出不满足重试条件的异常) retryContext -> { log.info("已达到最大重试次数或抛出了不重试的异常~~~"); return false; } ); log.info("执行结果:{}",execute); } }
-
监听重试过程
通过实现RetryListener接口,重写open、close、onError这三个方法,既可以完成对重试过程的追踪,也可以添加额外的处理逻辑。
@Slf4j @Component public class RetryListenerTemplate implements RetryListener { // 进入重试前调用 @Override public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) { log.info("--------------------------进入重试方法--------------------------"); return true; } // 重试结束后调用 @Override public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) { log.info("--------------------------重试方法结束--------------------------"); } // 捕获到异常时调用 @Override public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) { log.info("--------------------------第" + retryContext.getRetryCount() + "次重试--------------------------"); log.error(throwable.getMessage(), throwable); } }
或者,通过继承RetryListenerSupport,也可以从open、close、onError这三个方法中,选择性的重写。
public class RetryListener4Open extends RetryListenerSupport { @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { return super.open(context, callback); } }
在实例化RetryTemplate时,配置上该RetryListener实例即可。
retryTemplate.setListeners(new RetryListener[] {retryListenerTemplate});
注:
- V2.0版本以后,新增一个doOnSuccess方法,可以在调用成功之后,根据返回的结果值,来决定是否要进行重试。
- 每个RetryTemplate可以注册多个监听器,其中onOpen、onClose方法按照注册顺序执行,onError按照注册顺序的相反顺序执行。
参考资料: