前言
spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断。对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。在spring retry中可以指定需要重试的异常类型,并设置每次重试的间隔以及如果重试失败是继续重试还是熔断(停止重试)。目前在spring cloud中已经渗透到诸多组件,如ribbon等,故本章对原生spring-retry的应用做个简单的梳理和案例分析。
本章概要
1、设计实现原理
2、案例实践:
  • 重试策略:SimpleRetryPolicy固定重试次数
  • 重试策略:SimpleRetryPolicy固定重试次数
  • 重试策略:AlwaysRetryPolicy无限重试
  • 重试策略:TimeoutRetryPolicy超时重试
  • 重试策略:根据返回结果值实现重试
  • 重试策略:启用熔断器重试策略
  • 通过RetryListener实现拦截器模式
  • 采用注解方式实现
设计实现原理
重试的定义:RetryOperations定义重试的API,RetryTemplate是API的模板模式实现,实现了重试和熔断。提供的API如下:
RetryCallback定义了需要执行重试的操作,定义好操作后,就是如何重试的问题了。RetryTemplate通过制定不同的重试策略来执行如何重试的逻辑。默认的重试策略是SimpleRetryPlicy,也就是会重试3次。重试第1次如果成功后面就不会继续重试了。那么如果3尺都重试失败了呢?流程结束或者返回兜底结果。要返回兜底结果需要配置RecoveyCallBack,从名字可以看出这是一个兜底回调接口,也就是重试失败后执行的逻辑。
重试策略,除了SimpleRetryPolicy还有其他重试策略,先来看下RetryPolicy接口:
方法说明:
  • canRetry:在每次重试的时候调用,是否可以继续重试的判断条件
  • open:重试开始前调用,会创建一个重试上下文到RetryContext,保存重试的堆栈等信息
  • registerThrowable:每次重试异常时调用(有异常会继续重试)
以SimpleRetryPolicy为例,当重试次数达到3(默认3次)停止重试,重试次数保存在重试上下文中。
所有重试策略实现如下:
  • NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试
  • AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环
  • SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
  • TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
  • ExceptionClassifierRetryPolicy:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
  • CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
  • CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行
重试回退(等待)策略,指的是每次重试是立即重试还是等待一段时间后重试。默认情况下是立即重试,如果需要配置等待一段时间后重试则需要指定回退策略BackOffPolicy。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:随机指数退避策略,引入随机乘数可以实现随机乘数回退
有状态重试 OR 无状态重试
所谓无状态重试是指重试在一个线程上下文中完成的重试,反之不在一个线程上下文完成重试的就是有状态重试。之前的SimpleRetryPolicy就属于无状态重试,因为重试是在一个循环中完成的。那么什么会后会出现或者说需要有状态重试呢?通常有两种情况:事务回滚和熔断。
如数据库操作异常DataAccessException,则不能执行重试,而如果抛出其他异常可以重试。
熔断的意思不在当前循环中处理重试,而是全局重试模式(不是线程上下文)。熔断会跳出循环,那么必然会丢失线程上下文的堆栈信息。那么肯定需要一种“全局模式”保存这种信息,目前的实现放在一个cache(map实现的)中,下次从缓存中获取就能继续重试了。
案例实践
场景描述:i作为计数器,如果i小于5则抛出异常,i会进行自增一操作,直到等于5方正常返回,否则根据重试策略进行重试操作,如果直到最后一直未重试成功,则返回Integer最大值。在重试上下文中添加一个value变量,后续通过其值实现根据返回值判断重试应用。最后打印最终的返回值。
示例代码:
private void run(RetryTemplate retryTemplate) throws Exception {
Integer result = retryTemplate.execute(new RetryCallback<Integer, Exception>() {
int i = 0;
// 重试操作
@Override
public Integer doWithRetry(RetryContext retryContext) throws Exception {
retryContext.setAttribute("value", i);
LOGGER.info("retry {} times.", retryContext.getRetryCount());
return len(i++);
}
}, new RecoveryCallback<Integer>() { //兜底回调
@Override
public Integer recover(RetryContext retryContext) throws Exception {
LOGGER.info("after retry {} times, recovery method called!", retryContext.getRetryCount());
return Integer.MAX_VALUE;
}
});
LOGGER.info("final result: {}", result);
}
private int len(int i) throws Exception {
if (i < 5) throw new Exception(i + " le 5");
return i;
}
重试策略:SimpleRetryPolicy固定重试次数
示例代码:
@Test
public void retryFixTimes() throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
run(retryTemplate);
}
打印:
超过3次最大重试次数,触发了recoveryCall,并返回Integer最大值。
重试策略:AlwaysRetryPolicy无限重试
示例代码:
@Test
public void retryAlwaysTimes() throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new AlwaysRetryPolicy());
run(retryTemplate);
}
打印:
直达i等于5则正常返回,之前将实现无限重试。
重试策略:TimeoutRetryPolicy超时重试
TimeoutRetryPolicy策略定义:重试累计运行时长在设定的timeout范围内则重试,一旦超出则不再重试直接执行RecoveryCallback。
@Test
public void retryTimeout() throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
timeoutRetryPolicy.setTimeout(1000);
retryTemplate.setRetryPolicy(timeoutRetryPolicy);
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(400);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
run(retryTemplate);
}
打印:
设定1000ms后则认定为超时,每次重试等待时长400ms,故重试3次后即会超出超时阈值,触发RecoveryCallback回调,并返回Integer最大值。
重试策略:根据返回结果值实现重试
@Test
public void retryWithResult() throws Exception {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new AlwaysRetryPolicy() {
private static final long serialVersionUID = 1213824522266301314L;
@Override
public boolean canRetry(RetryContext context) {
//小于1则重试
return context.getAttribute("value") == null || Integer.parseInt(context.getAttribute("value").toString()) < 1;
}
});
run(retryTemplate);
}
打印:
如果value值小于1或者为null则进行重试,反之不在进行重试,触发RecoveryCallback回调,并返回Integer最大值。
重试策略:启用熔断器重试策略
首先来看CircuitBreakerRetryPolicy定义
需要设置如下三个参数:
  • delegate:传入RetryPolicy(每个RetryPolicy实现都有自己的重试策略实现),是真正判断是否重试的策略,当重试失败时,则执行熔断策略;
  • openTimeout:openWindow,配置熔断器电路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(只要有一次重试成功,则闭合电路);
  • resetTimeout:timeout,配置重置熔断器重新闭合的超时时间;
断路器判断实现:
可以理解为如下:
  • 当重试失败,且在熔断器打开时间窗口[0,openWindow) 内,立即熔断
  • 当重试失败,且超过timeout,熔断器电路重新闭合
  • 在熔断器半打开状态[openWindow, timeout] 时,只要重试成功则重置上下文,断路器闭合
示例代码:
@Test
public void retryCircuitBreakerTest() {
RetryTemplate retryTemplate = new RetryTemplate();
CircuitBreakerRetryPolicy retryPolicy =
new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(4));
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(300);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
retryPolicy.setOpenTimeout(1500);
retryPolicy.setResetTimeout(2000);
retryTemplate.setRetryPolicy(retryPolicy);
long startTime = System.currentTimeMillis();
IntStream.range(0, 10).forEach(index -> {
try {
Thread.sleep(100);
RetryState state = new DefaultRetryState("circuit", false);
String result = retryTemplate.execute(new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
LOGGER.info("retry {} times", context.getRetryCount());
if (System.currentTimeMillis() - startTime > 1300 && System.currentTimeMillis() - startTime < 1500) {
return "success";
}
throw new RuntimeException("timeout");
}
}, new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
return "default";
}
}, state);
LOGGER.info("result: {}", result);
} catch (Exception e) {
LOGGER.error("error: {}", e.getMessage());
}
});
}
打印:
设定重试次数为4次,在执行1300ms至1500ms期间连续两次调用成功,无需重试,其后继续抛出异常重试,第4次重试时(1405)仍然在1500ms内,故打开了断路器,后续请求异常均会直接返回RecoveryCallback中回调定义。
Note:
  • 如果采用无限重试策略则没有意义,将不会触发断路器;
  • 必须符合[0,openWindow)时间内重试失败方会发开断路器;
通过RetryListener实现拦截器模式
通过RetryListener实现拦截器模式,默认提供了StatisticsListener实现重试操作统计分析数据,如下示例:
@Test
public void retryListeners() {
RetryTemplate template = new RetryTemplate();
DefaultStatisticsRepository repository = new DefaultStatisticsRepository();
StatisticsListener listener = new StatisticsListener(repository);
template.setListeners(new RetryListener[]{listener});
for (int i = 0; i < 10; i++) {
String result = template.execute(new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
context.setAttribute(RetryContext.NAME, "method.key");
return "ok";
}
});
}
RetryStatistics statistics = repository.findOne("method.key");
System.out.println(statistics);
}
打印:
采用注解方式实现
以上采用硬编码方式定义实现,整体看起来代码非常的不优雅,Spring已经为我们提供了注解方式,本小节将基于springboot实现一个注解方式案例:
1、通过@EnableRetry注解启动类:
@SpringBootApplication
@EnableRetry(proxyTargetClass = true)
public class SpringRetryApp {
}
2、如下通过@Retryable注解一个待重试方法,并@Recover注解定义一个对应的Recover方法:
@Component
public class SpringRetryService {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringRetryService.class);
@Retryable(value = Exception.class, maxAttempts = 4, backoff = @Backoff(value = 0L))
public void run() throws Exception {
LOGGER.info("do sth");
throw new Exception("error");
}
@Recover
private void recover(Exception e) {
LOGGER.info("invoke recover");
}
}
Note:
  • Recover方法必须与源方法保持一样的参数列表定义,并添加一个Throwable作为异常信息传递;
3、编写一个测试方法:
@Autowired
private SpringRetryService springRetryService;
@Test
public void retryWithAnnotation() throws Exception {
springRetryService.run();
}
4、打印:
如上重试4次,并调用了Recover回调返回值。
5、更多的注解说明:
  • @EnableRetry
  • @Retryable
  • @Recover
  • @Backoff
  • @CircuitBreaker
  • @EnableRetry:能否重试,proxyTargetClass属性为true时(默认false),使用CGLIB代理
  • @Retryable:注解需要被重试的方法
include 指定处理的异常类。默认为空 
exclude指定不需要处理的异常。默认为空 
vaue指定要重试的异常。默认为空 
maxAttempts 最大重试次数。默认3次 
backoff 重试等待策略。默认使用@Backoff注解
  • @Backoff:重试回退策略(立即重试还是等待一会再重试)
不设置参数时,默认使用FixedBackOffPolicy,重试等待1000ms 
只设置delay()属性时,使用FixedBackOffPolicy,重试等待指定的毫秒数 
当设置delay()和maxDealy()属性时,重试等待在这两个值之间均态分布 
使用delay(),maxDealy()和multiplier()属性时,使用ExponentialBackOffPolicy 
当设置multiplier()属性不等于0时,同时也设置了random()属性时,使用ExponentialRandomBackOffPolicy
  • @Recover: 用于方法。用于@Retryable失败时的“兜底”处理方法。 @Recover注释的方法必须要与@Retryable注解的方法“签名”保持一致,第一入参为要重试的异常,其他参数与@Retryable保持一致,返回值也要一样,否则无法执行!
  • @CircuitBreaker:用于方法,实现熔断模式。
include 指定处理的异常类。默认为空 
exclude指定不需要处理的异常。默认为空 
vaue指定要重试的异常。默认为空 
maxAttempts 最大重试次数。默认3次 
openTimeout 配置熔断器打开的超时时间,默认5s,当超过openTimeout之后熔断器电路变成半打开状态(只要有一次重试成功,则闭合电路) 
resetTimeout 配置熔断器重新闭合的超时时间,默认20s,超过这个时间断路器关闭
6、源码浅析:
  • @Retryable注解实现源码主要参考AnnotationAwareRetryOperationsInterceptor实现,定义如下:
  • @Recover注解实现源码主要参考RecoverAnnotationRecoveryHandler实现,定义如下:
@Retryable和@Recover注解是如何关联起来的呢,正是上述findClosestMatch和calculateDistance。

Spring-Retry应用相关推荐

  1. foxmail邮件加载失败重试_java retry(重试) spring retry, guava retrying 详解

    系列说明 java retry 的一步步实现机制. java-retry 源码地址 情景导入 简单的需求 产品经理:实现一个按条件,查询用户信息的服务. 小明:好的.没问题. 代码 UserServi ...

  2. java retry(重试) spring retry, guava retrying 详解

    转载 自 http://blog.51cto.com/9250070/2156431 系列说明 java retry 的一步步实现机制. java-retry 源码地址 情景导入 简单的需求 产品经理 ...

  3. 针对Spring的Spring Retry 我发现了这样一个大家都不知道的技巧!

    外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失败重试策略重新调用 API 接口来获取.重试策略在服务治理方面也有很广泛 ...

  4. Spring Retry 重试机制实现及原理

    概要 Spring实现了一套重试机制,功能简单实用.Spring Retry是从Spring Batch独立出来的一个功能,已经广泛应用于Spring Batch,Spring Integration ...

  5. java retry 设置上限_java-如何设置Spring Retry模板重试最大尝试次数:无限

    我想用Spring Retry修改数据库连接的创建,以便在应用程序启动时数据库关闭时再试一次.我不想限制重试次数.我应该如何配置策略来做到这一点. 我当前的代码(我知道在这种状态下它限制为100): ...

  6. 高级JAVA - 手写简单的重试组件学习Spring Retry

    目录 一 . 定义注解 二 . 利用cglib代理扩展重试业务 三 . 编写代理类 , 使用自定义的XRetryInterceptor作为拦截器 四 . 编写相关业务方法 , 测试代码 五 . 测试结 ...

  7. Spring异常重试框架Spring Retry

    在调用第三方接口或者使用mq时,会出现网络抖动,连接超时等网络异常,所以需要重试.为了使处理更加健壮并且不太容易出现故障,后续的尝试操作,有时候会帮助失败的操作最后执行成功.例如,由于网络故障或数据库 ...

  8. Spring retry重试框架

    spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断.对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法.写操作等(要考虑写是否幂等)都不适合 ...

  9. Spring Retry # Stateful Retry

    Spring Retry中的重试,分为无状态的重试和有状态的重试: 简述 有状态重试通常是用在message-driven 的应用中,从消息中间件比如RabbitMQ等接收到的消息,如果应用处理失败, ...

  10. 重试框架Spring retry实践

    spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断.对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法.写操作等(要考虑写是否幂等)都不适合 ...

最新文章

  1. css中调整高度充满_CSS(十三).高度如何铺满全屏
  2. 2021年春季学期-信号与系统-第七次作业参考答案-第七小题
  3. java两个线程同时运行_Java实现的两个线程同时运行案例
  4. 第七周实践项目5 排队看病模拟(队列)
  5. 悟透JavaScript (强烈推荐)
  6. 关于计算机类课程实验教学的思考
  7. 两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
  8. 不要相信 errno 可靠
  9. (原文)基于甘特图的深度强化学习方法求解端到端在线重调度
  10. 网站盗取html文件工具,一键获取仿站精灵
  11. 实训(一)学生管理系统
  12. Mysql报错 Error querying database. Cause java.sql.SQLSyntaxErrorException
  13. HTTP协议详解+经典面试题
  14. 头牌知产介绍速冻水饺商标属于哪一类
  15. 财务自由人(Financial free man)
  16. Ubuntu系统从机械硬盘迁移到SSD
  17. 数据分析入门学习指南|零基础小白必看
  18. 汽车后市场有酒,元征科技有新故事吗?
  19. mysql 查询当月当天数据量
  20. 超详细 某代刷网站js逆向

热门文章

  1. 《基于Python的大数据分析基础及实战》第一章
  2. Python数据分析--统计注册用户
  3. oracle两个压缩包怎么解压,AIX下如何解压缩zip文件
  4. 计算机联锁系统故障处理流程图,铁路TYJL Ⅱ型计算机联锁操作流程图
  5. python运算符讲解
  6. Android中计步器的实现
  7. 如何用Python制作简单又好看的词云?来瞅瞅吧~
  8. 初涉VB.NET入门级代码积累
  9. U盘文件变成快捷方式的解决方案
  10. dopra linux 补全shell,HS8546V5光猫补全Shell 改华为原厂界面