目录
1)RetryPolicy : 点进源码中我们能看到这个属性是一个 RetryPolicy 重试策略
2)BackOffPolicy : 点进源码里发现是一个 BackOffPolicy 回退策略
Spring-Retry
Git:
GitHub – spring-projects/spring-retry
Spring Retry
是Spring框架的一个模块,它提供了一种简单且可配置的方式来在方法执行失败时进行重试。这对于处理网络通信、数据库连接、外部服务调用等不稳定操作非常有用。使用
Spring Retry
,您可以在失败的情况下自动重试方法,而无需手动编写复杂的重试逻辑。
这里以maven项目为例,展示两种使用方式
一、RetryTemplate
1、添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
2、编写配置
官方给的示例,你可以直接在psvm中测试
// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
}
});
这里我是单独配置了一遍,然后使用三层的方式调用,你可以选择直接使用默认的 RetryTemplate
首先自定义一个异常,用于重试异常
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends RuntimeException {
private int code;
private String msg;
}
编写RetryTemplate
@Bean("springRetryTemplate")
public RetryTemplate springRetryTemplate() {
Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
exceptionMap.put(ServiceException.class, true);
RetryTemplate retryTemplate = new RetryTemplate();
// 重试策略,参数依次代表:最大尝试次数、可重试的异常映射
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, exceptionMap);
retryTemplate.setRetryPolicy(retryPolicy);
// 重试间隔时间
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000L);
retryTemplate.setBackOffPolicy(backOffPolicy);
return retryTemplate;
}
这里解释下,RetryTemplate这里赋值了两个属性
1)RetryPolicy : 点进源码中我们能看到这个属性是一个 RetryPolicy 重试策略
private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
可以看到有这么多的重试策略,默认是 SimpleRetryPolicy
-
NeverRetryPolicy:
只允许调用
RetryCallback
一次,不允许重试 -
AlwaysRetryPolicy:
允许无限重试,直到成功,此方式逻辑不当会导致死循环 -
SimpleRetryPolicy:
固定次数重试策略,默认重试最大次数为3次,
RetryTemplate
默认使用的策略 -
TimeoutRetryPolicy:
超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试 -
ExceptionClassifierRetryPolicy:
设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试 -
CircuitBreakerRetryPolicy:
有熔断功能的重试策略,需设置3个参数
openTimeout
、
resetTimeout
和
delegate
-
CompositeRetryPolicy:
组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行
点进 SimpleRetryPolicy 类中看看
2)BackOffPolicy : 点进源码里发现是一个 BackOffPolicy 回退策略
private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();
也是给出了很多的回退策略,默认使用的是 NoBackOffPolicy
这里以 FixedBackOffPolicy 为例使用
-
NoBackOffPolicy:
无退避算法策略,每次重试时立即重试 -
FixedBackOffPolicy:
固定时间的退避策略,需设置参数
sleeper
和
backOffPeriod
,
sleeper
指定等待策略,默认是
Thread.sleep
,即线程休眠,
backOffPeriod
指定休眠时间,默认1秒 -
UniformRandomBackOffPolicy:
随机时间退避策略,需设置
sleeper
、
minBackOffPeriod
和
maxBackOffPeriod
,该策略在
minBackOffPeriod
,
maxBackOffPeriod
之间取一个随机休眠时间,
minBackOffPeriod
默认500毫秒,
maxBackOffPeriod
默认1500毫秒 -
ExponentialBackOffPolicy:
指数退避策略,需设置参数
sleeper
、
initialInterval
、
maxInterval
和
multiplie
r,
initialInterval
指定初始休眠时间,默认100毫秒,
maxInterval
指定最大休眠时间,默认30秒,
multiplier
指定乘数,即下一次休眠时间为
当前休眠时间*multiplier
-
ExponentialRandomBackOffPolicy:
随机指数退避策略,引入随机乘数可以实现随机乘数回退
3、编写方法
前提:我们需要考虑到一个方式的三种情况
- 方法正常返回成功或错误,这里指的是流程正常走下来没有任何异常或者在某一步失败了,但是我们捕捉到了并进行返回
- 方法抛出自定义异常,如比在调用远程方式的时候失败了,但是我们捕捉到了并进行了抛出相应的异常
- 方法抛出未捕捉到的异常,比如空指针、索引超出界限等。
思路:需要一个方法内随机出现以上各种情况
我们可以指定一个随机数,然后判断这个随机数的区间,针对不同区间值进行相应的返回
实现:思路我们有了就开始代码方面
① service层方法
@Slf4j
@Service
public class DemoTaskService {
@Resource
@Qualifier("springRetryTemplate")
private RetryTemplate retryTemplate;
public boolean testTask(String param) {
Boolean executeResult = retryTemplate.execute(retryCallback -> {
boolean result = randomResult(param);
log.info("调用的结果:{}", result);
return result;
}, recoveryCallback -> {
log.info("已达到最大重试次数或抛出了不在重试策略内的异常");
return false;
});
log.info("执行结果:{}", executeResult);
return executeResult;
}
/**
* 根据随机数模拟各种情况
*/
public boolean randomResult(String param) {
log.info("进入测试任务,参数:{}", param);
int i = new Random().nextInt(10);
log.info("随机生成的数:{}", i);
switch (i) {
case 0: {
log.info("数字为0,返回成功");
return true;
}
case 1: {
log.info("数字为1,返回失败");
return false;
}
case 2:
case 3:
case 4:
case 5: {
log.info("数字为2、3、4、5,抛出参数异常");
throw new IllegalArgumentException("参数异常");
}
default: {
log.info("数字大于5,抛出自定义异常");
throw new ServiceException(10010, "远程调用失败");
}
}
}
}
② Controller 层
@RestController
@RequestMapping("/retry")
@RequiredArgsConstructor
public class RetryController {
private final DemoTaskService demoTaskService;
@GetMapping("/spring")
public Boolean springRetry(@RequestParam("param") String param) {
return demoTaskService.testTask(param);
}
}
访问 http://ip:port/retry/spring?param=str 模拟测试如下:
1、遇到参数异常,因为参数异常不在重试异常中,所以不重试
2、 遇到正常返回true或false,不重试
3、 遇到重试的异常,达到重试次数或不重试的异常
二、注解方式
1、添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring-boot.version}</version>
</dependency>
2、启动类启动注解
@EnableRetry
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3、编写方法
① service层
@Slf4j
@Service
public class DemoTaskService {
/**
* 重试所调用方法
*/
@Retryable(value = {ServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
public boolean annotationTestTask(String param) {
return randomResult(param);
}
/**
* 根据相同参数和相同返回值匹配
*/
@Recover
public boolean annotationRecover(Exception e, String param) {
log.error("参数:{},已达到最大重试次数或抛出了不在重试策略内的异常:", param, e);
return false;
}
/**
* 根据随机数模拟各种情况
*/
public boolean randomResult(String param) {
log.info("进入测试任务,参数:{}", param);
int i = new Random().nextInt(10);
log.info("随机生成的数:{}", i);
switch (i) {
case 0: {
log.info("数字为0,返回成功");
return true;
}
case 1: {
log.info("数字为1,返回失败");
return false;
}
case 2:
case 3:
case 4:
case 5: {
log.info("数字为2、3、4、5,抛出参数异常");
throw new IllegalArgumentException("参数异常");
}
default: {
log.info("数字大于5,抛出自定义异常");
throw new ServiceException(10010, "远程调用失败");
}
}
}
}
@Retryable
maxAttempts :最大重试次数,默认为3
value:抛出指定异常才会重试
include:和value一样,默认为空,当exclude也为空时,默认所有异常
exclude:指定不处理的异常
backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L
@Backoff
value:隔多少毫秒后重试,默认为1000L
delay:和value一样,默认为0
multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调
② Controller层
@GetMapping("/spring/annotation")
public Boolean springRetryAnnotation(@RequestParam("param") String param) {
return demoTaskService.annotationTestTask(param);
}
访问 http://ip:port/retry/spring/annotation?param=str 模拟测试如下:
1、遇到参数异常,因为参数异常不在重试异常中,所以不重试,执行 annotationRecover 方法
2、遇到正常返回true或false,不重试
3、遇到重试的异常,达到重试次数或者不重试的异常,执行 annotationRecover 方法
Guava-Retry
1、添加依赖
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
2、编写方法
① service层
@Slf4j
@Service
public class DemoTaskService {
@Resource
@Qualifier("springRetryTemplate")
private RetryTemplate retryTemplate;
public boolean guavaTestTask(String param) {
// 构建重试实例 可以设置重试源且可以支持多个重试源 可以配置重试次数或重试超时时间,以及可以配置等待时间间隔
Retryer<Boolean> retriever = RetryerBuilder.<Boolean>newBuilder()
// 重试的异常类以及子类
.retryIfExceptionOfType(ServiceException.class)
// 根据返回值进行重试
.retryIfResult(result -> !result)
// 设置等待间隔时间,每次请求间隔1s
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
// 设置最大重试次数,尝试请求3次
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
try {
return retriever.call(() -> randomResult(param));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 根据随机数模拟各种情况
*/
public boolean randomResult(String param) {
log.info("进入测试任务,参数:{}", param);
int i = new Random().nextInt(10);
log.info("随机生成的数:{}", i);
switch (i) {
case 0: {
log.info("数字为0,返回成功");
return true;
}
case 1: {
log.info("数字为1,返回失败");
return false;
}
case 2:
case 3:
case 4:
case 5: {
log.info("数字为2、3、4、5,抛出参数异常");
throw new IllegalArgumentException("参数异常");
}
default: {
log.info("数字大于5,抛出自定义异常");
throw new ServiceException(10010, "远程调用失败");
}
}
}
}
② Controller层
@RestController
@RequestMapping("/retry")
@RequiredArgsConstructor
public class RetryController {
private final DemoTaskService demoTaskService;
@GetMapping("/spring")
public Boolean springRetry(@RequestParam("param") String param) {
return demoTaskService.testTask(param);
}
@GetMapping("/spring/annotation")
public Boolean springRetryAnnotation(@RequestParam("param") String param) {
return demoTaskService.annotationTestTask(param);
}
@GetMapping("/guava")
public Boolean guavaRetry(@RequestParam("param") String param) {
return demoTaskService.guavaTestTask(param);
}
}
访问 http://ip:port/retry/guava?param=str 模拟测试如下:
1、遇到参数异常,因为参数异常不在重试异常中,所以不重试
2、遇到正常返回true或false,不重试
3、遇到重试的异常,达到重试次数或者不重试的异常
3、实现原理
Retryer 接口
Retryer 接口
定义了执行方法重试的方法,并提供了多个配置方法来设置重试条件、等待策略、停止策略等。它包含一个
call
方法,将需要重试的操作以
Callable
形式传递给它
public V call(Callable<V> callable) throws ExecutionException, RetryException {
long startTime = System.nanoTime();
//根据attemptNumber进行循环次数
for (int attemptNumber = 1; ; attemptNumber++) {
// 进入方法不等待,立即执行一次
Attempt<V> attempt;
try {
// 执行callable中的具体业务
// attemptTimeLimiter限制了每次尝试等待的时长
V result = attemptTimeLimiter.call(callable);
// 利用调用结果构造新的attempt
attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
} catch (Throwable t) {
attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
}
// 遍历自定义的监听器
for (RetryListener listener : listeners) {
listener.onRetry(attempt);
}
// 判断是否满足重试条件,来决定是否继续等待并进行重试
if (!rejectionPredicate.apply(attempt)) {
return attempt.get();
}
// 此时满足停止策略,因为还没有得到想要的结果,因此抛出异常
if (stopStrategy.shouldStop(attempt)) {
throw new RetryException(attemptNumber, attempt);
} else {
// 行默认的停止策略——线程休眠
long sleepTime = waitStrategy.computeSleepTime(attempt);
try {
// 也可以执行定义的停止策略
blockStrategy.block(sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RetryException(attemptNumber, attempt);
}
}
}
}
RetryerBuilder 类
RetryerBuilder 类
创建
Retryer
实例的构建器类。您可以通过
RetryerBuilder
配置重试策略、条件和其他参数,最终构建出一个
Retryer
实例
重试条件
允许您根据结果和异常类型来定义重试条件。例如,您可以设置当返回结果为特定值、或者当发生特定类型的异常时进行重试
等待策略
等待策略定义了每次重试之间的等待时间。
Guava-Retry
提供了多种等待策略,包括固定等待、指数等待等。可以根据情况选择适合的等待策略
停止策略
停止策略决定在何时停止重试。可以定义最大重试次数、重试时间限制等。一旦达到停止条件,重试将不再执行
执行重试
当构建好了
Retryer
实例并设置好相关策略后,可以将需要重试的操作(以
Callable
形式)传递给
Retryer
的
call
方法。
Retryer
将根据配置的策略执行方法重试,直到达到停止条件或重试成功
总结
Guava-Retry
和
Spring Retry
都是用于在Java应用程序中实现方法重试的库,但它们来自不同的框架,并在一些方面有所不同。以下是它们之间的一些比较:
-
框架来源
:-
Guava-Retry
:
Guava-Retry
是Google Guava库的一部分,它提供了一种用于重试操作的机制。 -
Spring Retry
:
Spring Retry
是Spring框架的一个模块,专门用于在Spring应用程序中实现重试逻辑。
-
-
库依赖
:-
Guava-Retry
:您需要添加Guava库的依赖来使用
Guava-Retry
。 -
Spring Retry
:您需要添加
spring-retry
模块的依赖来使用
Spring Retry
。
-
-
配置和注解
:-
Guava-Retry
:重试逻辑通过构建
Retryer
实例并定义重试条件、等待策略等来配置。 -
Spring Retry
:
Spring Retry
提供了注解(如
@Retryable
、
@Recover
等)和编程式配置来实现重试逻辑。
-
-
重试策略
:-
Guava-Retry
:
Guava-Retry
允许您基于结果和异常类型来定义重试条件。您可以使用
RetryerBuilder
来自定义重试策略。 -
Spring Retry
:
Spring Retry
允许您使用注解来定义重试条件和相关属性,如最大重试次数、重试间隔等。
-
-
等待策略
:-
Guava-Retry
:
Guava-Retry
提供了不同的等待策略(如固定等待、指数等待等),您可以选择适合您的应用程序的策略。 -
Spring Retry
:
Spring Retry
允许您通过注解或编程式配置来指定等待时间。
-
-
适用范围
:-
Guava-Retry
:可以用于任何Java应用程序,不仅限于Spring框架。 -
Spring Retry
:专门设计用于Spring应用程序中,可以与其他Spring功能(如Spring AOP)集成。
-
-
依赖性
:-
Guava-Retry
:相对较轻量级,如果您只需要重试功能,可以考虑使用Guava库的一部分。 -
Spring Retry
:如果您已经在使用Spring框架,可以方便地集成
Spring Retry
,但可能需要更多的Spring依赖。
-