Spring – Retry 详解

Spring – Retry 详解1. RetryOperations有时候一些业务操作会受到间歇性的异常而导致失败,如网络抖动,数据库锁等,这些间歇性的异常在一段时候之后会自行

欢迎大家来到IT世界,在知识的湖畔探索吧!

SpringBatch从入门到放弃005- 健壮配置之重试-扩展阅读- Spring – Retry 详解

上一节我们通过一个简单的例子了解了一下 Spring Retry的使用方法,这一节我们来详细介绍一个 Spring Retry功能。

1. RetryOperations

有时候一些业务操作会受到间歇性的异常而导致失败,如网络抖动,数据库锁等,这些间歇性的异常在一段时候之后会自行恢复,程序为了更加健壮并且更不容易出现故障,需要重新触发业务操作,以防止间歇性的异常对程序照成的影响。为了自动重试此类操作,Spring Retry提供了RetryOperations接口。 RetryOperations接口定义如下:

Spring - Retry 详解

需要重试的业务逻辑放到 RetryCallback.doWithRetry() 里面。RetryCallback 接口定义如下:

Spring - Retry 详解

如果发生了异常 Callback方法会被执行,如果设置了重试次数,这个方法会被执行到这个次数,如果没有设置,会一直执行到程序结束。RetryOperations提供了多个方法的重载,如提供失败后执行的可以执行业务回滚的 Callback,记录重试状态的实体类等。

RetryTemplate 是RetryOperations接口最常见的实现,通过RetryTemplate我们可以这样设置重试:

 RetryTemplate template = new RetryTemplate();
 //使用超时重试策略
 TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
 //设置超时时间
 policy.setTimeout(200L);
 template.setRetryPolicy(policy);
 //设置重试执行代码
 String result = template.execute(new RetryCallback<String, IOException>() {
 public String doWithRetry(RetryContext context) {
 log.info("Execute callback .");
 int i = 1/0;
 return "Successfully";
 }
 });

欢迎大家来到IT世界,在知识的湖畔探索吧!

这个是模拟有一个操作失败了,会一直重复这个业务,直到执行时间操过了我们设置的超时时间。

官方文档提示V1.3版本之后可以支持 Build 的配置,但是最新的 Spring Boot 依赖的版本是1.2.4-release。所以鹏哥就不演示 Build 配置了,感兴趣的朋友可以指定 Spring Retry 的版本到1.3以上,按照下边的配置尝试一下:

欢迎大家来到IT世界,在知识的湖畔探索吧!RetryTemplate.builder()
 .maxAttempts(10)
 .exponentialBackoff(100, 2, 10000)
 .retryOn(IOException.class)
 .traversingCauses()
 .build();
 
RetryTemplate.builder()
 .fixedBackoff(10)
 .withinMillis(3000)
 .build();
 
RetryTemplate.builder()
 .infiniteRetry()
 .retryOn(IOException.class)
 .uniformRandomBackoff(1000, 3000)
 .build();

2. RetryContext

RetryCallback的方法参数是RetryContext。很多回调是忽略上下文的,但如果需要,他可以作为一个属性来存储执行期间的上下文信息。

如果在同一线程使用了嵌套重试,RetryContext具有父上下文。父上下文偶尔用于存储需要在执行的调用之间共享的数据。

3. RecoveryCallback

在重试结束时, RetryOperations 可以将控制权传递给另一个名为 RecoveryCallback 的回调。要使用此功能,需要我们使用RetryOperations.execute()的使用传递这个 callback ,例子如下:

Foo foo = template.execute(new RetryCallback<Foo>() {
 public Foo doWithRetry(RetryContext context) {
 // business logic here
 },
 new RecoveryCallback<Foo>() {
 Foo recover(RetryContext context) throws Exception {
 // recover logic here
 }
});

这个 callback 允许我们在前边重试之后依然失败的时候,可以有机会回滚我们的业务操作。

4. 无状态重试

在最简单的情况下,重试只是一个while循环。 RetryTemplate 可以继续尝试,直到它成功或失败或者程序推出。 RetryContext 包含一些状态来确定是重试还是中止,但是这个状态在堆栈上并且不需要将它存储在全局任何地方,所以我们称之为无状态重试。无状态和有状态重试之间的区别包含在 RetryPolicy 的实现中( RetryTemplate 可以处理两者)。在无状态重试中,重试回调始终在失败时在其所在的同一线程中执行。

5. 有状态重试

有很多重可能都会导致事务回滚的失败。这不适用于简单的远程调用,因为这一般是没有事务的,但它适用于数据库更新,尤其是在使用Hibernate时。在这种情况下,重新抛出立即调用失败的异常才有意义,这样事务就可以回滚,我们可以启动一个新的有效事务。

在这些情况下,无状态重试不够好,因为重新抛出和回滚必然涉及离开RetryOperations.execute()方法并可能丢失堆栈上的上下文。为了避免丢失它,我们必须引入一个存储策略,将其从栈中取出并将其(至少)放入堆存储中。为此,Spring retry提供了一个名为 RetryContextCache 的存储策略,可以将其注入 RetryTemplate 。 RetryContextCache 的默认实现是在内存中,使用简单的 Map 。在 集群 环境中使用多个进程的高级用法也可以考虑使用某种类型的 集群 缓存实现 RetryContextCache (但是,即使在 集群 环境中,这可能也是过度杀伤)

RetryOperations 的部分责任是识别失败的操作,当它们返回新的执行时(通常包含在新的事务中)。为了实现这一点,Spring Retry 提供了 RetryState 抽象。这与 RetryOperations 接口中的特殊 execute 方法结合使用。

识别失败操作的方式是通过多次重试调用来识别状态。为了识别状态,用户可以提供 RetryState 对象,该对象负责返回标识该项目的唯一键。标识符用作 RetryContextCache 接口中的键。

在 RetryState 返回的密钥中执行 Object.equals() 和 Object.hashCode() 时要非常小心。最好的建议是使用业务密钥来识别项目。对于JMS消息,可以使用消息ID。

当重试次数用完时,还可以选择以不同的方式处理失败的项目,而不是调用 RetryCallback (现在假定它可能失败)。就像无状态情况一样,此选项由 RecoveryCallback 提供,可以通过将其传递给 RetryOperations 的 execute 方法来提供。

重试与否的决定实际上是委托给固定的 RetryPolicy ,因此可以在那里注入关于限制和超时的常见问题。

6. 重试策略

在 RetryTemplate 内, execute 方法中重试或失败的决定由 RetryPolicy 确定, RetryPolicy 也是 RetryContext 的工厂。 RetryTemplate 有责任使用当前策略创建 RetryContext 并在每次尝试时将其传递给 RetryCallback 。回调失败后, RetryTemplate 必须调用 RetryPolicy 以要求它更新其状态(存储在 RetryContext 中),然后询问策略是否可以进行另一次尝试。如果无法进行另一次尝试(例如达到限制或检测到超时),则策略还负责处理耗尽状态。简单实现抛出 RetryExhaustedException ,这会导致回滚任何封闭事务。更复杂的实现可能会尝试采取一些恢复操作,在这种情况下,事务可以保持不变。

Spring Retry提供了一些简单的无状态 RetryPolicy 通用实现,例如 SimpleRetryPolicy 和 TimeoutRetryPolicy (在前面的例子中使用)。

SimpleRetryPolicy 允许在任何命名的异常类型列表上重试,最多固定次数。它还有一个永远不会重试的 “fatal” 异常列表,这个列表会覆盖可重试列表,以便它可以用来更好地控制重试行为,如下例所示:

还有一个名为 ExceptionClassifierRetryPolicy 的更灵活的实现,它允许用户通过 ExceptionClassifier 抽象为任意一组异常类型配置不同的重试行为。该策略通过调用分类器将异常转换为委托 RetryPolicy 来工作。例如,通过将一个异常类型映射到不同的策略,可以在失败之前多次重试一个异常类型。

用户可能需要实施自己的重试策略以进行更多自定义决策。例如,当有一个众所周知的,特定于解决方案的异常分类为可重试且不可重试时,自定义重试策略是有意义的。

RetryPolicy(重试策略)接口定义如下:

Spring - Retry 详解

canRetry() 如何判断是否可以重试,open()在重试开始时执行,close()在重试结束是执行,每次重试调用一次registerThrowable()来初始化上下文。

系统默认提供了以下几个实现类:

Spring - Retry 详解

具体实现类的作用,参见下表:

实现类作用NeverRetryPolicy只允许调用RetryCallback一次,不允许重试AlwaysRetryPolicy允许无限重试,直到成功,此方式逻辑不当会导致死循环SimpleRetryPolicy固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略TimeoutRetryPolicy超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试CircuitBreakerRetryPolicy有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate,CompositeRetryPolicy组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行ExceptionClassifierRetryPolicy为不同的异常指定不同的重试策略

7. 重试延迟时间

在暂时故障后重试时,通常会在再次尝试之前等待一段时间,因为通常故障是由某些问题引起的,只能通过等待来解决。如果 RetryCallback 失败, RetryTemplate 可以根据 BackoffPolicy 暂停执行。

以下代码显示 BackOffPolicy 接口的接口定义:

欢迎大家来到IT世界,在知识的湖畔探索吧!public interface BackoffPolicy {
 BackOffContext start(RetryContext context);
 void backOff(BackOffContext backOffContext)
 throws BackOffInterruptedException;
}

BackoffPolicy 可以以任何方式自由实现backOff。 Spring retry开箱即用的策略都使用 Object.wait() 。一个常见的用例是以指数级增加的等待时间进行退避,以避免两次重试进入锁定步骤并且都失败(这是从以太网中吸取的教训)。为此,Spring Retry提供 ExponentialBackoffPolicy 。

BackoffPolicy 的默认实现类:

Spring - Retry 详解

8 .Listener

通常,能够在每一次的重试中触发额外的回调也是非常有用的。因此Spring Retry提供了RetryListener接口,允许用户在重试时RetryContext和Throwable提供回调。

以下代码显示了RetryListener的接口定义:

Spring - Retry 详解

最简单的情况下,open()和close() 在每一次的重试前后执行,onError适用于个别 RetryCallback调用。 close方法也可能会收到Throwable。如果出现错误,则它是RetryCallback抛出的最后一个错误。

需要注意的是如果定义了多个 RetryListener,那么open/before 是按照定义的顺序执行,after/close 则是按照的定义的反序执行

9 如何使用 Retry

有时会有一些业务处理,你知道每次发生时都要重试。典型的例子是远程服务调用。 Spring Retry提供了一个AOP拦截器,它在RetryOperations中为这个重试包装了一个方法调用。 RetryOperationsInterceptor根据提供的RepeatTemplate中的RetryPolicy执行截获的方法并在失败时重试。

我们可以在任何一个带有@Configuration的配置类上加上@EnableRetry来启动重试功能。如果想要在某个方法上重试,需要在这个方法上添加@Retryable 注解

@SpringBootApplication
@EnableRetry
public class SpringRetryDemoApplication {

在方法上边加上@Retryable,以实现方法体的重试。

 private int j = 0 ;
 @Retryable
 public void retry(){
 log.info("retry method -> " + j);;
 if(j<2){
 j++;
 int i = 1/0;
 }
 }

默认情况下对于100到500毫秒之间的随机延迟和最多12次尝试。还有一个有状态属性(默认为false)来控制重试是否为有状态。要使用有状态重试,截获的方法必须有参数,因为它们用于构造状态的缓存键。

如果我们没有额外的关于 retry 的配置,@EnableRetry 会帮我们初始化一个默认的RetryTemplate,并使用默认的 RetryPolicy 。同时他还会为带有@Retryable的 Bean 创建 一个代理,也就是如果我们在这个 Bean 内部是调用这个类的方法,是不是会被 Retry 的,只能在可以使用代理的地方调用才能被 Retry。如通过@Autowired引入等。

如果要在重试耗尽时采用备用代码路径,可以提供恢复方法。方法应该在与@Retryable相同的类中声明并标记为@Recover。返回类型必须与@Retryable方法匹配。恢复方法的参数可以选择性地包括抛出的异常,也可以包括传递给原始可重试方法的参数(或者只要没有省略,就可以包含它们的部分列表)。

@Service
class Service {
 @Retryable(RemoteAccessException.class)
 public void service(String str1, String str2) {
 // ... do something
 }
 @Recover
 public void recover(RemoteAccessException e, String str1, String str2) {
 // ... error handling making use of original args if required
 }
}

1.2 版本之后还引入了其他表达式的属性:

@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service1() {
 ...
}
@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service2() {
 ...
}
@Retryable(exceptionExpression="@exceptionChecker.shouldRetry(#root)",
 maxAttemptsExpression = "#{@integerFiveBean}",
 backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
public void service3() {
 ...
}

Spring Retry 1.2.5之后, 不推荐使用模板配置 (#{…})exceptionExpression的方式来支持简单的字符串 (@Retryable(exceptionExpression=”message.contains(‘this can be retried’)”))

表达式也可以使用属性占位符,如: #{{max.delay}} or #{@exceptionChecker.

{max.delay}} or #{@exceptionChecker.

{retry.method}(#root)}

exceptionExpression作为#root对象针对抛出的异常进行评估。

初始化期间,maxAttemptsExpression和@BackOff表达式属性计算一次;评估没有根对象,但它们可以在上下文中引用其他bean。

@Retryable可以设置的属性:

Spring - Retry 详解

@Retryable的参数说明:

  • value:抛出指定异常才会重试
  • include:和value一样,默认为空,当exclude也为空时,默认所以异常
  • exclude:指定不处理的异常
  • maxAttempts:最大重试次数,默认3次
  • backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒

Backoff 支持的配置属性:

Spring - Retry 详解

10 添加依赖

对于 Spring boot 的工程需要引入:

 <dependency>
 <groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

对于非 Spring Boot 的工程,需要引入:

 <dependency>
 <groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
 </dependency>
 <dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 </dependency>

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/36918.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信