spring-retry是从spring batch独立出来的一个功能,主要实现了重试和熔断。
2013-3:【1.0.0】~2017-12:【1.2.2】
依赖:spring-aop、spring-beans、spring-core等spring核心包
@EnableRetry
在需要重试的类上增加,加载重试相关配置信息。其proxyTargetClass属性为true时,使用CGLIB代理。默认使用JDK代理。
@Retryable注解,被注解的方法发生异常时会重试
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有
@Backoff重试补偿策略
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
不设置参数时,默认使用FixedBackOffPolicy,重试等待1000ms。只设置delay()属性时,使用FixedBackOffPolicy,重试等待指定的毫秒数。当设置delay()和maxDealy()属性时,重试等待在这两个值之间均态分布。使用delay(),maxDealy()和multiplier()属性时,使用ExponentialBackOffPolicy。当设置multiplier()属性不等于0时,同时也设置了random()属性时,使用ExponentialRandomBackOffPolicy @Retryable(value = {SignServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 5000l, multiplier = 2)) public void createContract(String serviceName, SignRecord signRecord, ContractParamData contractParamData) { this.signRecord = signRecord; SpringUtil.getBean(serviceName, CreateContractService.class).create(signRecord, contractParamData); }@Recover注解,方法的参数为@Retryable异常类。当所有重试操作完成时(依然没有获取正确的结果)可以通过 RecoveryCallback实现一个兜底的操作(如发送邮件或短信提醒。需要注意的是发生的异常和入参类型一致时才会回调)。返回值应与重试方法返回相同。
@Recover public void sendEmail(SignServiceException e) { log.error("生成合同重试 = {} ,error = {}", JSON.toJSONString(signRecord), e); emailService.sendWarnEmailContractError(signRecord.getOrderId(), "生成合同", e.getMessage()); }
RetryOperations:定义重试的API,RetryTemplate是API的模板模式实现,实现了重试和熔断,线程安全的。内部通过while循环,RetryContext存储重试上下文参数信息,根据RetryPolicy重试策略判断是否可以重试,通过RetryCallback的doWithRetry执行重试的动作,RetryListener监听并记录重试信息,最后在重试完成依然失败的情况下,执行RecoveryCallback定义的方法逻辑。
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E; <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E; <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException; <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)throws E;
RetryContext:重试上下文。存储重试相关的参数。重试次数、异常信息、超时时间等
RetryPolicy:重试策略。根据不同的策略判断是否执行重试操作。
NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试; AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环; SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略; TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试; CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate,稍后详细介绍该策略; CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行。
BackOffPolicy:补偿策略。根据不同策略判断下次执行重试的时间。
NoBackOffPolicy:无退避算法策略,即当重试时是立即重试; FixedBackOffPolicy:固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒; UniformRandomBackOffPolicy:随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒; ExponentialBackOffPolicy:指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier; ExponentialRandomBackOffPolicy:随机指数退避策略,引入随机乘数,之前说过固定乘数可能会引起很多服务同时重试导致DDos,使用随机休眠时间来避免这种情况。
RetryCallback:定义了需要执行重试的操作。
方法:T doWithRetry(RetryContext context) throws E;
RecoveryCallback:“兜底”回调。
方法:T recover(RetryContext context) throws Exception;
RetryListener:监听,统计记录重试的数据信息。异常信息
spring-retry通过AOP实现对目的方法的封装,执行在当前线程下,所以重试过程中当前线程会堵塞。如果BackOff时间设置比较长,最好起异步线程重试(也可以加@Async注解)。
@Service @EnableRetry @Slf4j @EnableAsync @Async(value = "taskExecutor") public class RetryContractService { @Autowired EmailService emailService; private SignRecord signRecord; @Retryable(value = {SignServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 5000l, multiplier = 2)) public void createContract(String serviceName, SignRecord signRecord, ContractParamData contractParamData) { this.signRecord = signRecord; SpringUtil.getBean(serviceName, CreateContractService.class).create(signRecord, contractParamData); } @Recover public void sendEmail(SignServiceException e) { log.error("生成合同重试 = {} ,error = {}", JSON.toJSONString(signRecord), e); emailService.sendWarnEmailContractError(signRecord.getOrderId(), "生成合同", e.getMessage()); } }
有状态重试 OR 无状态重试 所谓无状态重试是指重试在一个线程上下文中完成的重试,反之不在一个线程上下文完成重试的就是有状态重试。之前的SimpleRetryPolicy就属于无状态重试,因为重试是在一个循环中完成的。那么什么会后会出现或者说需要有状态重试呢?通常有两种情况:事务回滚和熔断。 如数据库操作异常DataAccessException,则不能执行重试,而如果抛出其他异常可以重试。 熔断的意思不在当前循环中处理重试,而是全局重试模式(不是线程上下文)。熔断会跳出循环,那么必然会丢失线程上下文的堆栈信息。那么肯定需要一种“全局模式”保存这种信息,目前的实现放在一个cache(map实现的)中,下次从缓存中获取就能继续重试了。
参考:
https://blog.csdn.net/broadview2006/article/details/72841056
https://blog.csdn.net/songhaifengshuaige/article/details/79441326
熔断器设计模式:http://blog.jobbole.com/75283/