什么是重试
重试是指,当在一个程序运行过程中,突然遇到了例如网络延迟,中断等情况时,为了保证程序容错性,可用性,一致性等的一个措施,目前主流的框架大多都有一套自己的重试机制,例如 dubbo,mq,Spring 等
概要
Spring 也自己实现了一套重试机制,Spring Retry 是从 Spring batch 中独立出来的一个功能,主要功能点在于重试和熔断,目前已经广泛应用于 Spring Batch,Spring Integration, Spring for Apache Hadoop 等 Spring 项目。spring retry 提供了注解和编程 两种支持,提供了 RetryTemplate 支持,类似 RestTemplate。 整个流程如下:

使用介绍
Maven 依赖
java
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- also need to add Spring AOP into our project-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency`>`
注解使用
开启 Retry 功能,需在启动类中使用 @EnableRetry
注解
java
@SpringBootApplication
@EnableRetry
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注解 @Retryable
需要在重试的代码中加入重试注解 @Retryable
java
@Retryable(value = RuntimeException.class)
public void testRetry01() throws MyException {
System.out.println("测试-value属性");
throw new RuntimeException("出现了异常");
}
默认情况下,会重试 3 次,间隔 1 秒
重试配置
通过 @Retryable
注解的属性 可以实现重试配置
-
Value()
-
include
value 与 include 含义相同,表示可重试的异常类型。默认为空,如果同时 exclude 也为空则会重试所有异常。但在使用时需要注意
js
@Retryable(value = RuntimeException.class)
public void testRetry01() throws MyException {
System.out.println("测试-value属性");
throw new RuntimeException("出现了异常");
}
例:testRetry01 只会在程序抛出 RuntimeException 时,开启重试
- exclude
不可重试的异常类型。默认为空(如果 include 也为为空,将重试所有异常)。如果 include 为空但 exclude 不为空,则重试非 exclude 中的异常
csharp
@Retryable(exclude = RuntimeException.class)
public void testRetry02() throws MyException {
System.out.println("测试-value属性");
throw new MyException("出现了异常");
}
例:testRetry02 在程序抛出 MyException 时,不会开启重试
- maxAttempts
最大重试次数,默认为 3
- maxAttemptsExpression
最大尝试次数的表达式,表达式一旦设置了值,则会覆盖 maxAttempts 的值,maxAttemptsExpression 可以读取 application.yml 配置文件里的数据,也可以通过 SpEL 表达式计算对应的值
js
@Retryable(value = MyException.class, maxAttemptsExpression = "${maxAttempts}")
public void testRetry03() throws MyException {
System.out.println("测试-maxAttemptsExpression属性");
throw new MyException("出现了异常");
}
例:testRetry03 会去读 properties 配置文件获取属性名为 maxAttempts 的值
js
@Retryable(value = MyException.class, maxAttemptsExpression = "#{2+3}")
public void testRetry04() throws MyException {
System.out.println("测试-maxAttemptsExpression属性");
throw new MyException("出现了异常");
}
- exceptionExpression
异常处理表达式,ExpressionRetryPolicy 中使用,执行完父类的 canRetry 之后,需要校验 exceptionExpression 的值,为 true 则可以重试
@Retryable(value = MyException.class, exceptionExpression = "#{@retryService.isRetry()}")
public void testRetry05() throws MyException {
System.out.println("测试-exceptionExpression");
throw new MyException("出现了异常");
}
例:这个表达式的意思就是,如果 testRetry05 方法出现异常 会调用 retryService.isRetry() 方法,根据返回结果判断是否重试
-
@Recover
兜底方法 -
当 @Retryable 方法重试失败之后,最后就会调用 @Recover 方法。用于 @Retryable 失败时的"兜底"处理方法。 @Recover 的方法必须要与 @Retryable 注解的方法保持一致,第一入参为要重试的异常,其他参数与 @Retryable 保持一致,返回值也要一样,否则无法执行!
js
@Retryable(value = MyException.class)
public void testRetry06() throws MyException {
System.out.println("测试兜底方法");
throw new MyException("出现了异常");
}
@Recover
public void recover06(MyException e) {
System.out.println("兜底方法开启,异常信息:" + e.getMessage());
}
熔断模式@CircuitBreaker
指在具体的重试机制下失败后打开断路器,过了一段时间,断路器进入半开状态,允许一个进入重试,若失败再次进入断路器,成功则关闭断路器,注解为 @CircuitBreaker
,具体包括熔断打开时间、重置过期时间
js
@CircuitBreaker(openTimeout = 1000, resetTimeout = 3000, value = MyException.class)
public void testRetry07() throws MyException {
System.out.println("测试CircuitBreaker注解");
throw new MyException("出现了异常");
}
例:openTimeout 时间范围内失败 maxAttempts 次数后,熔断打开 resetTimeout 时长 这个方法的意思就是方法在一秒内失败三次时,触发熔断,下次在有请求过来时,直接进入
重试策略
-
SimpleRetryPolicy 默认最多重试 3 次
-
TimeoutRetryPolicy 默认在 1 秒内失败都会重试
-
ExpressionRetryPolicy 符合表达式就会重试
-
CircuitBreakerRetryPolicy 增加了熔断的机制,如果不在熔断状态,则允许重试
-
CompositeRetryPolicy 可以组合多个重试策略
-
NeverRetryPolicy 从不重试(也是一种重试策略哈)
-
AlwaysRetryPolicy 总是重试
退避策略
退避策略退避是指怎么去做下一次的重试,在这里其实就是等待多长时间。
通过 @Backoff 注解实现,那么我们首先看一下@Backoff 的参数
@Backoff 参数
- value
默认为 1000, 与 delay 作用相同,表示延迟的毫秒数。当 delay 非 0 时,此参数忽略。
- delay
默认为 0。在指数情况下用作初始值,在统一情况下用作*的最小值。当此元素的值为 0 时,将采用元素 value 的值,否则将采用此元素的值,并且将忽略 value。
- maxDelay
默认为 0。重试之间的最大等待时间(以毫秒为单位)。如果小于 delay,那么将应用默认值为 30000L
- multipler
默认为 0。如果为正,则用作乘法器以生成下一个退避延迟。返回一个乘法器,用于计算下一个退避延迟
- delayExpression
评估标准退避期的表达式。在指数情况下用作初始值*,在均匀情况下用作最小值。覆盖 delay。
- maxDelayExpression
该表达式计算重试之间的最大等待时间(以毫秒为单位)。 如果小于 delay,那么将应用 30000L 为默认值。覆盖 maxDelay。
- multiplierExpression
评估为用作乘数的值,以生成退避的下一个延迟。覆盖 multiplier。 返回一个乘数表达式,用于计算下一个退避延迟
- random
默认为 false,在指数情况下 multiplier> 0 将此值设置为 true 可以使后退延迟随机化,从而使最大延迟乘以前一延迟,并且两个值之间的分布是均匀的。
js
@Retryable(value = MyException.class, maxAttempts = 4,
backoff = @Backoff(delay = 2000, multiplier = 2, maxDelay = 5000))
public void testRetry08() throws MyException {
System.out.println("测试-backoff属性");
throw new MyException("出现了异常");
}
@Backoff 的参数会影响我们使用哪种退避策略
- FixedBackOffPolicy
默认退避策略,每 1 秒重试 1 次
- ExponentialBackOffPolicy
指数退避策略,当设置 multiplier 时使用,每次重试时间间隔为 当前延迟时间 * multiplier。
例如:默认初始 0.1 秒,系数是 2,那么下次延迟 0.2 秒,再下次就是延迟 0.4 秒,如此类推,最大 30 秒。
- ExponentialRandomBackOffPolicy
指数随机退避策略。在指数退避策略的基础上增加了随机性。
- UniformRandomBackOffPolicy
均匀随机策略,设置 maxDely 但没有设置 multiplier 时使用,重试间隔会在 maxDelay 和 delay 间随机