欢迎大家来到IT世界,在知识的湖畔探索吧!
SpringBatch从入门到放弃005- 健壮配置之重试-扩展阅读- Spring – Retry 详解
上一节我们通过一个简单的例子了解了一下 Spring Retry的使用方法,这一节我们来详细介绍一个 Spring Retry功能。
1. RetryOperations
有时候一些业务操作会受到间歇性的异常而导致失败,如网络抖动,数据库锁等,这些间歇性的异常在一段时候之后会自行恢复,程序为了更加健壮并且更不容易出现故障,需要重新触发业务操作,以防止间歇性的异常对程序照成的影响。为了自动重试此类操作,Spring Retry提供了RetryOperations接口。 RetryOperations接口定义如下:

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

如果发生了异常 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(重试策略)接口定义如下:

canRetry() 如何判断是否可以重试,open()在重试开始时执行,close()在重试结束是执行,每次重试调用一次registerThrowable()来初始化上下文。
系统默认提供了以下几个实现类:

具体实现类的作用,参见下表:
实现类作用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 的默认实现类:

8 .Listener
通常,能够在每一次的重试中触发额外的回调也是非常有用的。因此Spring Retry提供了RetryListener接口,允许用户在重试时RetryContext和Throwable提供回调。
以下代码显示了RetryListener的接口定义:

最简单的情况下,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可以设置的属性:

@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 支持的配置属性:

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