当新的世界出现,请立即向他奔去
上一章简单介绍了Melody 监控(四十九), 如果没有看过,请观看上一章
本章节文章参考:
https://juejin.cn/post/7234107489390116925
https://blog.csdn.net/hongyuan19/article/details/118995696
一. 重试
一.一 什么是重试
重试是指,当在一个程序运行过程中,突然遇到了例如网络延迟,中断等情况时,
为了保证程序容错性,可用性,一致性等的一个措施.
目前主流的框架大多都有一套自己的重试机制,例如 dubbo,mq,Spring 等
一.二 Spring 的 重试机制
Spring 也自己实现了一套重试机制,Spring Retry 是从 Spring batch 中独立出来的一个功能,
主要功能点在于重试和熔断,目前已经广泛应用于 Spring Batch,Spring Integration, Spring for Apache Hadoop 等 Spring 项目。
spring retry 提供了注解和编程 两种支持,提供了 RetryTemplate 支持,类似 RestTemplate。
整个流程如下:
二. 重试机制
二.一 未使用重试机制
编写一个有很大概率 产生异常的方法
@Override
public void noRetry() throws Exception {
Random random = new Random(System.currentTimeMillis());
int nextNum = random.nextInt(10);
if (nextNum >= 3) {
log.error(">>>> 运行时间是:{},超时失败", nextNum);
throw new Exception("运行超时");
} else {
log.info(">>>>>>>运行成功");
}
}
编写一个 Controller 进行调用
@RestController
public class RetryController {
@Resource
private UserBusiness userBusiness;
@GetMapping("/noRetry")
public String noRetry() {
try {
userBusiness.noRetry();
return "运行成功";
} catch (Exception e) {
return "运行失败";
}
}
}
访问网址: http://localhost:8081/noRetry
运行到第二次的时候,就失败了
对应的控制台日志是:
失败时,只运行一次。
二.二 Spring 重试机制
二.二.一 pom.xml 添加依赖
<!--添加重试的依赖-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
二.二.二 启用重试注解
在启动类上添加 @EnableRetry 注解
@SpringBootApplication
// 开启注解
@EnableRetry
public class RetryApp {
public static void main(String[] args) {
SpringApplication.run(RetryApp.class, args);
}
}
二.二.三 方法添加 @Retryable 注解
按照之前的,新复制一个方法。
添加 @Retryable 注解
/**
* value 表示 何种异常时,进行重试.
* maxAttempts 重试的次数,默认是 3次
* <p>
* backoff 表示 第一次调用失败时,执行的策略。
* delay 表示, 第一次隔多长时间 2s 后重新调用.
* multiplier 表示 重试的延迟倍数, 刚开始是 2s, 后来是 2*2 =4 秒, 再后来是 4*2 = 8s
*
* @Recover 表示重试一直失败,到了最大重试次数后调用
*/
@Override
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2))
public void retry() throws Exception {
Random random = new Random(System.currentTimeMillis());
int nextNum = random.nextInt(10);
if (nextNum >= 3) {
log.error(">>>> 运行时间是:{},超时失败", nextNum);
throw new Exception("运行超时");
} else {
log.info(">>>>>>>运行成功,运动时间是:{}", nextNum);
}
}
对应的 Controller 方法是
@GetMapping("/retry")
public String retry() {
try {
userBusiness.retry();
return "运行成功";
} catch (Exception e) {
return "运行失败";
}
}
二.二.四 请求验证
前端请求:
http://localhost:8081/retry
目前运行三次后,结果仍是失败, 查看控制台:
查看一个重试结果,最后是成功的
查看控制台, 第二次是成功的。
三. Retry 深入了解
三.一 重试 兜底操作
现在如果重试三次仍然失败后, 没有任何的处理。 希望在三次失败后,有一个兜底的操作机制
采用 @Recover 注解进行兜底
/**
* 重试一直失败 调用 。
* 如果一直失败,则抛出异常。 这样,外部才可以捕获到
*/
@Recover
public void retryUtilException() throws Exception {
log.error(">>>>>重试一直失败,执行一个兜底的操作");
throw new Exception("一直失败,但还是要努力噢");
}
当运行三次,仍然失败时:
三.二 熔断模式
熔断模式采用注解 @CircuitBreaker
指在具体的重试机制下失败后打开断路器,过了一段时间,断路器进入半开状态,允许一个进入重试,
若失败再次进入断路器,成功则关闭断路器,注解为 @CircuitBreaker
,具体包括熔断打开时间、重置过期时间
@CircuitBreaker(openTimeout = 10000, maxAttempts = 3,resetTimeout = 3000, value = Exception.class)
public void circuit() throws Exception {
log.info(">>> 不好了,触发熔断了");
throw new Exception("触发了熔断异常");
}
openTimeout 时间范围内失败 maxAttempts 次数后,熔断打开 resetTimeout 时长
这个方法的意思就是方法在一秒内失败三次时,触发熔断,下次在有请求过来时,直接进入熔断方法
该功能没有执行成功,哎,
有谁知道如何处理这一个,请底下评论一下,谢谢噢。
三.三 Spring 配置文件 配置重试策略信息
三.三.一 配置文件中定义重试的信息
三.三.二 引入配置文件
@SpringBootApplication
// 开启注解
@EnableRetry
// 引入配置文件
@PropertySource("classpath:retryConfig.properties")
public class RetryApp {
public static void main(String[] args) {
SpringApplication.run(RetryApp.class, args);
}
}
三.三.三 重试方法编写
通过 ${} 进行引用
/**
* 使用的是表达式配置处理
*/
@Override
@Retryable(value = Exception.class, maxAttemptsExpression = "${retry.maxAttempts}",
backoff = @Backoff(delayExpression = "${retry.delay}", maxDelayExpression = "${retry.maxDelay}", multiplierExpression = "${retry.multiplier}"))
public void propertyRetry() throws Exception {
Random random = new Random(System.currentTimeMillis());
int nextNum = random.nextInt(10);
if (nextNum >= 1) {
log.error(">>>> 运行时间是:{},超时失败", nextNum);
throw new Exception("运行超时");
} else {
log.info(">>>>>>>运行成功,运动时间是:{}", nextNum);
}
}
Controller 层调用
@GetMapping("/propRetry")
public String propRetry() {
try {
userBusiness.propertyRetry();
return "运行成功";
} catch (Exception e) {
return "运行失败";
}
}
也会进行重试操作
三.四 RetryTemplate 进行重试处理
三.四.一 定义重试的 Bean RetryTemplate
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
//设置重试回退
fixedBackOffPolicy.setBackOffPeriod(2000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
//设置 基本的,最大次数等。
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
// 注册监听器
// retryTemplate.registerListener(new MyListenerSupport());
return retryTemplate;
}
}
三.四.二 接口重试调用
/**
* '
* <p>
* 最后不会调用 retryUtilException() 方法。
*/
@Override
public void selfRetry() throws Exception {
try {
retryTemplate.execute(new RetryCallback<Object, Throwable>() {
@Override
public Object doWithRetry(RetryContext context) throws Throwable {
// 调用 重试方法
try {
noRetry();
return null;
} catch (Exception e) {
// 有异常,往上抛出。
throw e;
}
}
});
} catch (Throwable e) {
throw new Exception("重试失败");
}
}
对应的 Controller 重试方法
@GetMapping("/selfRetry")
public String selfRetry() {
try {
userBusiness.selfRetry();
return "运行成功";
} catch (Exception e) {
return "运行失败";
}
}
三.五 重试监听器
三.五.一 自定义监听器 继承 RetryListenerSupport 类
/**
* 自定义重试监听器
*
* @author yuejianli
* @date 2022-06-28
*/
@Slf4j
public class MyListenerSupport extends RetryListenerSupport {
/**
* 最后关闭的时候, 执行一次
*/
@Override
public <T, E extends Throwable> void close(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
log.info("onClose,关闭重试");
super.close(context, callback, throwable);
}
/**
* 每一次执行失败时调用 ,可能会执行多次。
*/
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
log.info("onError,重试的结果,最终是失败的");
super.onError(context, callback, throwable);
}
/**
* 最开始执行的时候, 执行一次
*/
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
log.info("onOpen,开始重试");
return super.open(context, callback);
}
}
三.五.二 RetryTemplate 注册监听器
@Configuration
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
//设置重试回退
fixedBackOffPolicy.setBackOffPeriod(2000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
//设置 基本的,最大次数等。
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
// 注册监听器
retryTemplate.registerListener(new MyListenerSupport());
return retryTemplate;
}
}
三.六 重试策略
- 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 可以使后退延迟随机化,从而使最大延迟乘以前一延迟,并且两个值之间的分布是均匀的。
@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 间随机
本章节的代码放置在 github 上:
https://github.com/yuejianli/Function/tree/develop/Retry
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!