Spring Boot 整合——Spring retry有状态重试以及其注释和组件介绍
关于版本
依赖 | 版本 |
---|---|
springboot | 2.4.0 |
spring retry | 2.4.0 |
代码地址
因为每个例子涉及代码较多,且包含测试用例,如果都贴到文章中内容过多,所以只贴出了部分代码。全部的代码在这里:https://gitee.com/daifyutils/springboot-samples。
相关文章
Spring Boot 整合——Spring retry的基本使用
通过配置状态重试来使用CircuitBreaker
带熔断功能的重试
无状态的重试
无状态重试,是在一个循环中执行完重试策略,即重试上下文保持在一个线程上下文中。
有状态的重试
有状态的重试时,上下文会保存在一个公共的缓存中。
- 注释中设置有状态的重试
@Retryable(stateful = true)
- JAVA中使用
DefaultRetryState
保存状态
Object key = "retryState";
boolean isForceRefresh = false;
RetryState state = new DefaultRetryState(key, isForceRefresh);
带熔断功能的重试
使用CircuitBreaker我们可以实现熔断功能的重试。其需要设置三个参数
CircuitBreakerRetryPolicy retryPolicy =
new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(5));
// 此参数用来判断熔断器是否打开的超时时间
retryPolicy.setOpenTimeout(3000L);
// 此参数用来判断熔断器是否重置的超时时间
retryPolicy.setResetTimeout(9000L);
熔断逻辑的判断
其两个参数主要的应用是在判断熔断器电路是否打开的逻辑CircuitBreakerRetryPolicy
的isOpen
中
public boolean isOpen() {
long time = System.currentTimeMillis() - this.start;
boolean retryable = this.policy.canRetry(this.context);
if (!retryable) {// 如果不能重试
// 如果上次请求时间大于this.timeout 重置上下文
if (time > this.timeout) {
logger.trace("Closing");
this.context = createDelegateContext(policy, getParent());
this.start = System.currentTimeMillis();
retryable = this.policy.canRetry(this.context);
}
else if (time < this.openWindow) {
// 如果上次请求小于this.openWindow,则打开断路器
if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
logger.trace("Opening circuit");
setAttribute(CIRCUIT_OPEN, true);
}
this.start = System.currentTimeMillis();
return true;
}
}
else {// 可以进行重试
// 如果距离上次请求时间大于openWindow则断路器闭合,重置上下文
if (time > this.openWindow) {
logger.trace("Resetting context");
this.start = System.currentTimeMillis();
this.context = createDelegateContext(policy, getParent());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Open: " + !retryable);
}
setAttribute(CIRCUIT_OPEN, !retryable);
return !retryable;
}
上面的逻辑可以得到结论,总结下来。
this.timeout 为设置的 ResetTimeout (9000L)
this.openWindow 为设置的 OpenTimeout (3000L)
当this.policy.canRetry(this.context) == false
重试失败。
-
如果两次请求时间大于
ResetTimeout
则重置上下文后,重新判断canRetry -
如果两次请求时间小于
OpenTimeout
则打开断路器
当this.policy.canRetry(this.context) == true
可以继续进行重试。
- 如果两次请求时间大于
OpenTimeout
则重置上下文
带熔断功能重试的例子
JAVA
public void retryState(long sleepTime){
RetryTemplate template = RetryTemplate.builder().retryOn(Exception.class).build();
CircuitBreakerRetryPolicy retryPolicy =
new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(5));
// 此参数用来判断熔断器是否打开的超时时间动
retryPolicy.setOpenTimeout(2000L);
// 此参数用来判断熔断器是否重置的超时时间
retryPolicy.setResetTimeout(20000L);
template.setRetryPolicy(retryPolicy);
Object key = "retryState";
boolean isForceRefresh = false;
RetryState state = new DefaultRetryState(key, isForceRefresh);
for (int i = 0; i < 20; i++) {
try {
String result = template.execute(new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
log.info("重试次数:" + context.getRetryCount());
log.warn("业务执行失败!!!" + LocalTime.now());
try {
Thread.sleep(200L * 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException("timeout");
}
}, new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
log.warn("业务执行recover!!!" + LocalTime.now());
return "default";
}
},state);
if (result.equals("default")) {
try {
log.info("请求被拒绝,休眠{}毫秒后再次请求。",sleepTime);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
System.out.println(e);
}
}
}
使用注解
@CircuitBreaker(
openTimeout = 3000L,
resetTimeout = 7000L,
maxAttempts = 5 // 最大重试次数
)
public String retryAnnotationState(){
log.warn("业务执行失败!!!" + LocalTime.now());
throw new RuntimeException("timeout");
}
@Recover
public String recover() {
log.info("请求被拒绝。");
return "default";
}
测试用例
以JAVA代码中的demo为测试例子
/**
* 有状态的重试,此时5次重试后熔断,重试时间小于openTimeout
* @throws Exception
*/
@Test
public void baseRetry() throws Exception {
retryStateService.retryState(1000L * 1);
}
在熔断上下文被重置,然后使用小于OpenTimeout
的频率发送请求,如此断路器会被持续打开,且上下文无法重置,请求会被持续性的熔断。
2021-01-17 21:43:43.711 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:45.211 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:45.211
2021-01-17 21:43:45.211 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:46.711 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:46.711
2021-01-17 21:43:46.711 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:48.212 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:48.212
2021-01-17 21:43:48.212 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:49.712 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:49.712
2021-01-17 21:43:49.712 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:51.212 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:51.212
2021-01-17 21:43:51.212 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:52.713 WARN 17848 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:43:52.713
2021-01-17 21:43:52.713 INFO 17848 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠1500毫秒后再次请求。
同样以JAVA中代码为测试例子
/**
* 有状态的重试,后熔断,重试总时间大于ResetTimeout
* @throws Exception
*/
@Test
public void baseRetry2() throws Exception {
retryStateService.retryState(2100L);
}
在熔断上下文被重置,然后使用大于OpenTimeout
的频率发送请求,则在时间到达ResetTimeout
之后可以重新进行重试
2021-01-17 21:45:46.768 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 重试次数:4
2021-01-17 21:45:46.768 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行失败!!!21:45:46.768
java.lang.RuntimeException: timeout
2021-01-17 21:45:46.969 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:46.969
2021-01-17 21:45:46.969 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:49.070 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:49.070
2021-01-17 21:45:49.070 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:51.170 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:51.170
2021-01-17 21:45:51.170 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:53.271 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:53.271
2021-01-17 21:45:53.271 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:55.371 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:55.371
2021-01-17 21:45:55.371 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:57.471 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:57.471
2021-01-17 21:45:57.471 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:59.572 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:45:59.572
2021-01-17 21:45:59.572 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:01.672 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:46:01.672
2021-01-17 21:46:01.672 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:03.773 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:46:03.773
2021-01-17 21:46:03.773 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:05.873 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行recover!!!21:46:05.873
2021-01-17 21:46:05.874 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:07.975 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 重试次数:0
2021-01-17 21:46:07.975 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行失败!!!21:46:07.975
java.lang.RuntimeException: timeout
2021-01-17 21:46:08.175 INFO 22244 --- [ main] d.s.retry.service.RetryStateService : 重试次数:1
2021-01-17 21:46:08.175 WARN 22244 --- [ main] d.s.retry.service.RetryStateService : 业务执行失败!!!21:46:08.175
使用注释和RetryTemplate的区别
这是一个没有解决的问题,有清楚怎么配置的同学可以告知下
使用RetryTemplate进行重试的时候,其会进行多次重试后达到最大重试次数后进入熔断时进入Recover
方法。
而使用@CircuitBreaker进行熔断重试的时候,其进行多次重试时每次失败都会进入Recover
方法然后熔断后才会只执行Recover
方法。
重试注解介绍
用来定义重试策略的注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
// 设置兜底方法的方法名,默认为空获取当前类中Recover注解的方法
String recover() default "";
// 配置重试监听器bean名称
String interceptor() default "";
// 用来设置需要进行重试的异常
Class<? extends Throwable>[] value() default {};
// 等价于value一样是来确定需要进行重试的异常
Class<? extends Throwable>[] include() default {};
// 设置例外处理,例外的异常不需要处理
Class<? extends Throwable>[] exclude() default {};
// 定义重试统计的标签
String label() default "";
// 标识重试有状态的
boolean stateful() default false;
// 最大的重试次数
int maxAttempts() default 3;
// 返回一个重试最大次数的公式
String maxAttemptsExpression() default "";
// 为两次重试指定的Backoff属性
Backoff backoff() default @Backoff();
// 根据异常调用不同处理逻辑的公式
String exceptionExpression() default "";
// 重试接听器
String[] listeners() default {};
}
用来定义兜底方法的注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Recover {
}
用来在入口类中定义是否启动重试功能
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {
// 当proxyTargetClass属性为true时,使用CGLIB代理。默认使用基于标准Java接口的代理
boolean proxyTargetClass() default false;
}
提供熔断功能的重试
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(stateful = true)
public @interface CircuitBreaker {
//
Class<? extends Throwable>[] value() default {};
//
Class<? extends Throwable>[] include() default {};
//
Class<? extends Throwable>[] exclude() default {};
//
int maxAttempts() default 3;
//
String maxAttemptsExpression() default "";
//
String label() default "";
// 配置重置熔断器重新闭合的超时时间
long resetTimeout() default 20000;
// 熔断器重新闭合的超时时间的文字公式
String resetTimeoutExpression() default "";
// 配置熔断器电路打开的超时时间
long openTimeout() default 5000;
// 熔断器电路打开的超时时间的文字公式
String openTimeoutExpression() default "";
//
String exceptionExpression() default "";
}
退避策略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Backoff {
// 等同于delay,确定延迟时间
long value() default 1000;
// 指定重试的延迟时间
long delay() default 0;
// 最长的延迟时间
long maxDelay() default 0;
// 指定延迟的倍数
double multiplier() default 0;
// 延迟时间的函数公式
String delayExpression() default "";
// 最长延迟使劲的函数公式
String maxDelayExpression() default "";
// 延迟倍数的函数公式
String multiplierExpression() default "";
// 在multiplier设置的情况下设置该值为true将使再重试延迟随机化,
boolean random() default false;
}
重试JAVA代码介绍
重试策略
重试实现类 | 说明 |
---|---|
NeverRetryPolicy | 只允许调用RetryCallback一次,不允许重试 |
AlwaysRetryPolicy | 允许无限重试,直到成功,此方式逻辑不当会导致死循环 |
SimpleRetryPolicy | 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略 |
TimeoutRetryPolicy | 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试 |
ExceptionClassifierRetryPolicy | 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试 |
CircuitBreakerRetryPolicy | 有熔断功能的重试策略 |
CompositeRetryPolicy | 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以 |
NeverRetryPolicy
/**
* 只允许调用RetryCallback一次,不允许重试
* @return
*/
public static RetryPolicy getNeverRetryPolicy() {
NeverRetryPolicy neverRetryPolicy = new NeverRetryPolicy();
return neverRetryPolicy;
}
AlwaysRetryPolicy
/**
* 允许无限重试,直到成功,此方式逻辑不当会导致死循环
* @return
*/
public static RetryPolicy getAlwaysRetryPolicy() {
AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();
return alwaysRetryPolicy;
}
SimpleRetryPolicy
/**
* 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
* @return
*/
public static RetryPolicy getSimpleRetryPolicy() {
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(5);
return simpleRetryPolicy;
}
TimeoutRetryPolicy
/**
* 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
* @return
*/
public static RetryPolicy getTimeoutRetryPolicy() {
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
timeoutRetryPolicy.setTimeout(3000L);
return timeoutRetryPolicy;
}
ExceptionClassifierRetryPolicy
/**
* 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
* @return
*/
public static RetryPolicy getExceptionClassifierRetryPolicy() {
ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy();
Map map = new HashMap();
map.put(RuntimeException.class,getNeverRetryPolicy());
map.put(Exception.class,getSimpleRetryPolicy());
exceptionClassifierRetryPolicy.setPolicyMap(map);
return exceptionClassifierRetryPolicy;
}
CircuitBreakerRetryPolicy
/**
* 有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
* @return
*/
public static RetryPolicy getCircuitBreakerRetryPolicy() {
CircuitBreakerRetryPolicy circuitBreakerRetryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy());
circuitBreakerRetryPolicy.setOpenTimeout(5000);
circuitBreakerRetryPolicy.setResetTimeout(20000);
return circuitBreakerRetryPolicy;
}
CompositeRetryPolicy
/**
* 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行
* @return
*/
public static RetryPolicy getCompositeRetryPolicy() {
CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
// 设置是否乐观重试策略
compositeRetryPolicy.setOptimistic(true);
compositeRetryPolicy.setPolicies(new RetryPolicy[]{getSimpleRetryPolicy(),getTimeoutRetryPolicy()});
return compositeRetryPolicy;
}
退避策略
退避实现类 | 说明 |
---|---|
NoBackOffPolicy | 无退避算法策略,每次重试时立即重试 |
FixedBackOffPolicy | 固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒 |
UniformRandomBackOffPolicy | 随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒 |
ExponentialBackOffPolicy | 指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier |
ExponentialRandomBackOffPolicy | 随机指数退避策略,引入随机乘数可以实现随机乘数回退 |
NoBackOffPolicy
无退避算法策略,每次重试时立即重试
/**
* 无退避策略
* @return
*/
public static BackOffPolicy getNoBackOffPolicy() {
return new NoBackOffPolicy();
}
FixedBackOffPolicy
/**
* 设置固定退避策略
* @return
*/
public static BackOffPolicy getFixedBackOffPolicy() {
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
// 设置固定的延迟时间
fixedBackOffPolicy.setBackOffPeriod(2000L);
return fixedBackOffPolicy;
}
UniformRandomBackOffPolicy
/**
* 设置随机退避策略,设置其最小时间和最大时间
* @return
*/
public static BackOffPolicy getUniformRandomBackOffPolicy() {
UniformRandomBackOffPolicy uniformRandomBackOffPolicy = new UniformRandomBackOffPolicy();
// 设置最小延迟时间
uniformRandomBackOffPolicy.setMinBackOffPeriod(500L);
// 设置最大延迟时间
uniformRandomBackOffPolicy.setMaxBackOffPeriod(2000L);
return uniformRandomBackOffPolicy;
}
ExponentialBackOffPolicy
public static BackOffPolicy getExponentialBackOffPolicy() {
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
// 设置初始延迟时间
exponentialBackOffPolicy.setInitialInterval(300L);
// 设置最大延迟时间
exponentialBackOffPolicy.setMaxInterval(4000L);
// 设置两次延迟时间的倍数
exponentialBackOffPolicy.setMultiplier(3);
return new NoBackOffPolicy();
}
ExponentialRandomBackOffPolicy
/**
* 指数退避策略
* @return
*/
public static BackOffPolicy getExponentialRandomBackOffPolicy() {
ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
// 设置初始延迟时间
exponentialRandomBackOffPolicy.setInitialInterval(300L);
// 设置最大延迟时间
exponentialRandomBackOffPolicy.setMaxInterval(4000L);
// 设置两次延迟时间的倍数
exponentialRandomBackOffPolicy.setMultiplier(3);
return new NoBackOffPolicy();
}