重试是在网络通讯中非常重要的概念,尤其是在微服务体系内重试显得格外重要。常见的场景是当遇到网络抖动造成的请求失败时,可以按照业务的补偿需求来制定重试策略。Spring框架提供了SpringRetry能让我们在项目工程中很方便的使用重试。这里我主要试着分析一下在Spring框架的各个核心模块里如何集成是使用SpringRetry。

1. SpringRetry的快速上手示例

首先我们引入springRetry的依赖: implementation group: 'org.springframework.retry', name: 'spring-retry',version:'1.2.5.RELEASE',同时已SpringBoot作为项目示例基础:

    @Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy(); //定义基于时间超时的重试策略
timeoutRetryPolicy.setTimeout(3000L);
retryTemplate.setRetryPolicy(timeoutRetryPolicy);
return retryTemplate; } public static void main(String[]args){ ConfigurableApplicationContext applicationContext = SpringApplication.run(RetryDemo.class, args);
RetryTemplate retryTemplate = applicationContext.getBean(RetryTemplate.class);
retryTemplate.execute(new RetryCallback<Object, Throwable>() { //2 重试执行的代码块
private int index = 1; @Override
public Object doWithRetry(RetryContext context) throws Throwable {
System.out.println("第" + index + "次执行");
index++;
System.out.println(1 / 0);//3 重试代码块里模拟异常的操作
return null;
}
}); }

上面的代码示例展示了SpringRetry的最基本使用方式,首先在代码1处先定义了RetryTemplate的Bean,同时定义了重试的策略配置,等会说一下这个RetryPolicy接口。在此处定义TimeoutRetryPolicy主要控制重试多长时间,此处设置3秒。3秒后没有成功的话结束重试。在代码2处,使用RetryTemplate的execute方法来执行重试的回调,如果在回调中出现异常则执行重试,因此我们可以把执行重试的代码块放在这里。在代码3处模拟了抛出异常的操作

这里运行的结果:

....
第110823次执行
第110824次执行
第110825次执行
第110826次执行
Exception in thread "main" java.lang.ArithmeticException: / by zero

2. SpringRetry的核心代码

RetryTemplate里核心方法时exectute方法,这里可以从这个入口方法来了解代码的核心。

  1. 初始化RetryContext
	// Allow the retry policy to initialise itself...
RetryContext context = open(retryPolicy, state);
if (this.logger.isTraceEnabled()) {
this.logger.trace("RetryContext retrieved: " + context);
}

在这里会调用给RetryPolicy接口的open方法,目的是为了初始化RetryContext(重试上下文),RetryContext可以扩展我们的业务字段,这里我举个例子TimeoutRetryContext中定义了timeout与start属性。LoadBalancedRetryContext中定义了request与serviceInstance

  1. 调用RetryListener的onOpen方法


// Give clients a chance to enhance the context...
boolean running = doOpenInterceptors(retryCallback, context); private <T, E extends Throwable> boolean doOpenInterceptors(
RetryCallback<T, E> callback, RetryContext context) { boolean result = true; for (RetryListener listener : this.listeners) {
result = result && listener.open(context, callback);
} return result; }

在这里单行注释已经明明白白的写出了,我们可以利用RetryListener的来给客户端一个机会增强RetryContext的机会,如果理解拦截器的思想的话,这点应该很容易理解。

  1. 获取和启动BackOffContext

    // Get or Start the backoff context...
    BackOffContext backOffContext = null;
    Object resource = context.getAttribute("backOffContext"); if (resource instanceof BackOffContext) {
    backOffContext = (BackOffContext) resource;
    } if (backOffContext == null) {
    backOffContext = backOffPolicy.start(context);
    if (backOffContext != null) {
    context.setAttribute("backOffContext", backOffContext);
    }
    }

    先从RetryContext中获取backOffContext,如果没有会调用backOffPolicy的start方法。该方法会返回BackOffContext对象不等于null的情况下把此对象放在RetryContext当中

  2. 执行RetryCallback的代码回调

    	/*
    * We allow the whole loop to be skipped if the policy or context already
    * forbid the first try. This is used in the case of external retry to allow a
    * recovery in handleRetryExhausted without the callback processing (which
    * would throw an exception).
    */
    while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) { try {
    if (this.logger.isDebugEnabled()) {
    this.logger.debug("Retry: count=" + context.getRetryCount());
    }
    // Reset the last exception, so if we are successful
    // the close interceptors will not think we failed...
    lastException = null;
    return retryCallback.doWithRetry(context);
    }
    catch (Throwable e) { lastException = e; try {
    registerThrowable(retryPolicy, state, context, e);
    }
    catch (Exception ex) {
    throw new TerminatedRetryException("Could not register throwable",
    ex);
    }
    finally {
    doOnErrorInterceptors(retryCallback, context, e);
    } if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
    try {
    backOffPolicy.backOff(backOffContext);
    }
    catch (BackOffInterruptedException ex) {
    lastException = e;
    // back off was prevented by another thread - fail the retry
    if (this.logger.isDebugEnabled()) {
    this.logger
    .debug("Abort retry because interrupted: count="
    + context.getRetryCount());
    }
    throw ex;
    }
    } if (this.logger.isDebugEnabled()) {
    this.logger.debug(
    "Checking for rethrow: count=" + context.getRetryCount());
    } if (shouldRethrow(retryPolicy, context, state)) {
    if (this.logger.isDebugEnabled()) {
    this.logger.debug("Rethrow in retry for policy: count="
    + context.getRetryCount());
    }
    throw RetryTemplate.<E>wrapIfNecessary(e);
    } } /*
    * A stateful attempt that can retry may rethrow the exception before now,
    * but if we get this far in a stateful retry there's a reason for it,
    * like a circuit breaker or a rollback classifier.
    */
    if (state != null && context.hasAttribute(GLOBAL_STATE)) {
    break;
    }
    }

    此处代码是重试代码的执行核心:

    1. 首先是用一个循环执行RetryCallback的回调内容,此处循环条件有两个:

      • 根据RetryPolicy接口里的canRetry方法的返回值,比如说TimeoutPolicy 里控制retry的条件是(System.currentTimeMillis() - start) <= timeout
      • 还有一个是RetryContext中的isExhaustedOnly()方法,这个方法是一个“筋疲力尽”的标志如果为true那么也会停止重试的循环
    2. 当出现异常时
      • 首先会调用RetryPolicy里的registerThrowable的方法。然后当RetryState不为null时会把RetryContext对象放入RetryContextCache中保存。
      • 依次循环调用RetryListener里的onError方法
      • 再次判断重试条件,如果满足的情况下,会调用BackoffPolicy的backoff方法。BackOffPolicy的接口主要用于定义在执行重试时出现异常情况的处理逻辑,举个例子说明:FixedBackOffPolicy重写的backoff方法里可以控制线程休眠让其间隔多长时间进行重试: sleeper.sleep(backOffPeriod)
      • 调用RetryState的rollbackFor方法的方法来判断当前的异常需不需要回滚,而在该接口的唯一实现类DefaultRetryState里会用Classifier<? super Throwable, Boolean> rollbackClassifier来将Throwable类型转换成Boolean类型,可以在这里定义处理逻辑。如果该方法的返回值为true,将会重新抛出异常

3. SpringRetry的注解式配置

使用注解的方式是SpringRetry中最常见的方式,其实它底层还是aop与前面所说的基本执行流程。对于注解的方式使用起来还是比较简单的。首先我们打上@EnableRetry的注解

@SpringBootApplication
@EnableRetry
public class SpringApplicationProvider { //....
} @Retryable(maxAttempts = 2, recover = "recover")
public void send() {
System.out.println("send....");
System.out.println(1 / 0);
} @Recover
public void recover(ArithmeticException exception) {
System.out.println(2 / 1);
}

可以在需要重试的方法上打上@Retryable ,这个注解有几个常见的属性:

  • maxAttepts 这个定义了重试的最大次数,默认值是3
  • recover : 用于定义@Recover修饰的方法名,旨在指定重试失败后用于执行的方法。@Recover修饰的方法参数必须是Throwable的子类,同时返回值必须和@Retryable修饰的返回值相同
  • stateful:表示重试是有状态的,默认值为false,如果为true则有如下影响: 1.会重新抛出异常 2.执行重试期间,注册当前的重试上下文至缓存
  • backoff : 这个属性用于收集Backoff的元数据,那么根据文档描述,可以得到以下信息:
    • 没有明确设置的情况下,默认值为 1000 毫秒的固定延迟

      只有delay()设置:设置固定的延迟时间进行重试

      当设置delay()和maxDelay() ,延时的值在两个值之间均匀分布

      使用delay() 、 maxDelay()和multiplier()后退指数增长到最大值

      如果设置了random()标志,则从 [1, multiplier-1] 中的均匀分布中为每个延迟选择乘数
    • 实际上这个几个属性是控制创建BackOff接口实现类的:
      • 如果multiplier大于0,则创建ExponentialBackOffPolicy对象
      • 如果max > min,则创建UniformRandomBackOffPolicy对象
      • 否则创建FixedBackOffPolicy对象

初探SpringRetry机制的更多相关文章

  1. [转]NHibernate之旅(12):初探延迟加载机制

    本节内容 引入 延迟加载 实例分析 1.一对多关系实例 2.多对多关系实例 结语 引入 通过前面文章的分析,我们知道了如何使用NHibernate,比如CRUD操作.事务.一对多.多对多映射等问题,这 ...

  2. Java SE之初探反射机制

    [Keywords]:Java,Hibernate,虚拟机,框架,SQL [Abstract]:   反射的概念:所谓的反射就是java语言在运行时拥有一项自观的能力,反射使您的程序代码能够得到装载到 ...

  3. 【NHibernate】应用层面需要掌握的知识汇总

    休息接待区 欢迎加入NHibernate中文社区!在讨论中寻找乐趣!在问题中寻找答案! 旅途站点路线 第一站:熟悉NHibernate NHibernate之旅(1):开篇有益 第二站:接触NHibe ...

  4. NHibernate之旅系列文章导航

    NHibernate之旅系列文章导航 宣传语 NHibernate.NHibernate教程.NHibernate入门.NHibernate下载.NHibernate教程中文版.NHibernate实 ...

  5. NHibernate 基础教程

    NHibernate之旅系列文章导航 宣传语 NHibernate.NHibernate教程.NHibernate入门.NHibernate下载.NHibernate教程中文版.NHibernate实 ...

  6. spring-retry 重试机制

    业务场景 应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果:第二步拿 ...

  7. WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探

    原文:WPF中自定义的DataTemplate中的控件,在Window_Loaded事件中加载机制初探         最近因为项目需要,开始学习如何使用WPF开发桌面程序.使用WPF一段时间之后,感 ...

  8. Java 反射机制 初探*

    反射机制初探 * 走进沼泽 在正常的程序中,先有类,然后再有对象. 取得Class对象(类对象) public final Class<?> getClass() ; 实例观察: publ ...

  9. 实例说明C++的virtual function的作用以及内部工作机制初探

    C++为何要引入virtual function? 来看一个基类的实现: 1 class CBase 2 { 3 public: 4 CBase(int id) : m_nId(id), m_pBas ...

随机推荐

  1. 全景分割:CVPR2019论文解析

    全景分割:CVPR2019论文解析 Panoptic Segmentation 论文链接: http://openaccess.thecvf.com/content_CVPR_2019/papers/ ...

  2. 2D池化IPoolingLayer

    2D池化IPoolingLayer IPooling层在通道内实现池化.支持的池类型为最大, 平均 和 最大平均混合. 层描述:二维池化 使用张量上的2D滤波器计算池化a tensor A, of d ...

  3. 全卷积网络Fully Convolutional Networks (FCN)实战

    全卷积网络Fully Convolutional Networks (FCN)实战 使用图像中的每个像素进行类别预测的语义分割.全卷积网络(FCN)使用卷积神经网络将图像像素转换为像素类别.与之前介绍 ...

  4. MySQL 面试必备:又一神器“锁”,不会的在面试都挂了

    1 什么是锁 1.1 锁的概述 在生活中锁的例子多的不能再多了,从古老的简单的门锁,到密码锁,再到现在的指纹解锁,人脸识别锁,这都是锁的鲜明的例子,所以,我们理解锁应该是非常简单的. 再到MySQL中 ...

  5. 如何提升springboot服务吞吐量

    生产环境偶尔会有一些慢请求导致系统性能下降,吞吐量下降,下面介绍几种优化建议. 方案 1.undertow替换tomcat 电子商务类型网站大多都是短请求,一般响应时间都在100ms,这时可以将web ...

  6. Android系统编程入门系列之清单文件

    在上一篇文章中已经提到,Android系统加载应用程序之后,首先会读取该应用程序的AndroidManifest.xml清单文件,之后根据该清单文件加载后边的东西.所以要开发应用程序,自然要先知道清单 ...

  7. 基于ABP落地领域驱动设计-00.目录和小结

    <实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...

  8. 试着给VuePress添加渐进式Web应用(PWA)支持,基于vuepress/plugin-pwa,点亮离线访问

    背景 有时候,我们也希望VuePress构建的文档中心能支持离线访问,这时候我们需要给他添加渐进式Web应用(PWA,Progressive Web App)的支持,根据官方文档指引,我们可以借助插件 ...

  9. Docker入门与进阶(上)

    Docker入门与进阶(上) 作者 刘畅 时间 2020-10-17 目录 1 Docker核心概述与安装 1 1.1 为什么要用容器 1 1.2 docker是什么 1 1.3 docker设计目标 ...

  10. 基于Redis的分布式锁设计

    前言 基于Redis的分布式锁实现,原理很简单嘛:检测一下Key是否存在,不存在则Set Key,加锁成功,存在则加锁失败.对吗?这么简单吗? 如果你真这么想,那么你真的需要好好听我讲一下了.接下来, ...