spring-retry是spring社区的一个成员,它提供了一种对失败操作进行自动重试的能力,可以作为某些瞬时错误(例如短暂的网络抖动)的解决方案。
作为spring生态的一部分,spring-retry自然地支持声明式(Declarative)方式使用。此外,它也支持命令式(Impertive)方式在代码里直接调用。
引入依赖:
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
<version>1.2.2.RELEASEversion>
dependency>
它的版本交给spring boot管理,以获得与spring良好的兼容性。
项目当前使用的是spring-boot版本为1.5.13.RELEASE,它管理的spring-retry版本是1.2.2.RELEASE。
在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会对类的全部方法进行重试。
配置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});
注:
参考资料: