retry: 英
/ˌriːˈtraɪ/
美/ˌriːˈtraɪ/
github地址
官网地址
在调用第三方接口或者使用Mq时,会出现网络抖动,连接超时等网络异常,所以需要重试。
网络抖动:标识一个网络的稳定性。抖动越小,网络越稳定。
Spring Retry是从Spring Batch 2.2.0
版本独立出来的一个功能,主要实现了重试和熔断
。
在 Spring Retry需要指定触发重试的异常类型
,并设置每次重试的间隔
以及如果重试失败是继续重试还是熔断(停止重试)。
对于
重试是有场景限制的,不是什么场景都适合重试
,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。远程调用超时、网络突然中断可以重试
- 在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。
public class TestRetry {
//最大重试次数
private static final Integer tryTimes = 6;
//重试间隔时间单位秒
private static final Integer intervalTime = 1;
public static void main(String[] args) throws InterruptedException {
boolean flag = TestRetry.retryBuss();
System.out.println("最终执行结果:" + (flag ? "成功" : "失败"));
}
public static boolean retryBuss() throws InterruptedException {
Integer retryNum = 1;
boolean flag = false;
while (retryNum <= tryTimes) {
try {
flag = execute(retryNum);
if (flag) {
System.out.println("第" + retryNum + "次执行成功!!!");
break;
}
System.err.println("第" + retryNum + "次执行失败...");
retryNum++;
} catch (Exception e) {
retryNum++;
TimeUnit.SECONDS.sleep(intervalTime);
continue;
}
}
return flag;
}
/**
* 具体业务
* @param retryNum
* @return
*/
private static boolean execute(int retryNum) {
Random random = new Random();
int a = random.nextInt(10);
boolean flag = true;
try {
if (a != 6) {
flag = false;
throw new RuntimeException();
}
} catch (Exception e) {
//这里捕获异常只是为了能,返回flag的结果
}
return flag;
}
}
1.引入依赖
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
dependency>
2.配置类
@EnabelRetry
开启重试才行,写在配置类或者启动类
上都是可以的@Configuration
@EnableRetry
public class RetryConfiguration {
@Bean
public PayService payService() {
return new PayService();
}
}
3.实现类
@Retryable
//@Service
@Slf4j
public class PayService {
private final int totalNum = 100000;
@Retryable(value = {
Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
public int minGoodsNum(int num) {
log.info("减库存开始=>" + LocalTime.now());
log.info("库存=>" + totalNum);
if (num <= 0) {
throw new IllegalArgumentException("数量不对");
}
log.info("减库存执行结束=>" + LocalTime.now());
return totalNum - num;
}
/**
* 使用@Recover注解,当重试次数达到设置的次数的时候,还是失败抛出异常,执行的回调函数。
*/
@Recover
public int recover(Exception e) {
log.warn("减库存失败!!!" + LocalTime.now());
//记日志到数据库
return totalNum;
}
}
测试代码
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RetryTest {
@Autowired
private PayService payService;
/**
* 应该使用 org.junit.Test 才是正确的
*/
@Test
public void minGoodsNum() {
payService.minGoodsNum(-1);
}
}
@Retryable
修饰的minGoodsNum()
方法,如果调用期间报了异常
,那么它将进行重试3次(默认三次),如果超过最大重试次数,则执行@Retryable修饰的recover()
方法,
@Retryable注解有各种配置,用于包含和排除异常类型、限制重试次数和回退策略。
@EnableRetry:启用重试,proxyTargetClass属性为true时(默认false),使用CGLIB代理
@Retryable:标记当前方法会使用重试机制
@Backoff:重试回退策略(立即重试还是等待一会再重试)
- value: 重试的间隔时间默认为
1000L
,我们设置为2000L
- delay:重试的间隔时间,
就是value
- maxDelay:重试次数之间的最大时间间隔,
默认为0
,如果小于delay的设置,则默认为30000L
- multiplier:delay时间的间隔倍数,
默认为0,表示固定暂停1秒后进行重试
,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
.
FixedBackOffPolicy(固定时间等待策略),重试等待1000ms
只设置delay时
,使用FixedBackOffPolicy,重试等待指定的毫秒数delay和maxDealy
时,重试等待在这两个值之间均态分布delay,maxDealy和multiplier
时,使用ExponentialBackOffPolicy(倍数等待策略)
multiplier不等于0
时,同时也设置了random
时,使用ExponentialRandomBackOffPolicy(随机倍数等待策略)
@Recover标记方法为@Retryable失败时的“兜底”处理方法
- 传参与@Retryable的配置的value必须一样
- @Recover标记方法必须要
与@Retryable注解的方法“形参”保持一致
,第一入参为要重试的异常(一定要是@Retryable方法里抛出的异常或者异常父类)
,其他参数与@Retryable保持一致,返回值
也要一样,否则无法执行!
@CircuitBreaker:用于标记方法,实现熔断模式。
- include 指定处理的异常类。默认为空
- exclude指定不需要处理的异常。默认为空
- vaue指定要重试的异常。默认为空
- maxAttempts 最大重试次数。默认3次
- openTimeout 配置熔断器打开的超时时间,默认5s,当超过openTimeout之后熔断器电路变成半打开状态(只要有一次重试成功,则闭合电路)
- resetTimeout 配置熔断器重新闭合的超时时间,默认20s,超过这个时间断路器关闭
使用了@Retryable注解
的方法直接实例化
调用不会触发重试,要先将实现类实例化到Spring容器
中,然后通过注入
等方式使用
Spring-Retry是通过捕获异常的方式来触发重试的,@Retryable标注方法产生的异常不能使用try-catch捕获
,要在方法上抛出异常,不然不会触发重试
重试原则
RetryOperations的具体实现
,组合了RetryListener[],BackOffPolicy,RetryPolicy。重试下的上下文
,可用于在多次Retry或者Retry 和Recover之间传递参数或状态;重试的策略
,可以固定次数的重试,也可以是指定超时时间进行重试;重试的等待策略
,在业务逻辑执行发生异常时。如果需要重试,我们可能需要等一段时间(可能服务器过于繁忙,如果一直不间隔重试可能拖垮服务器),当然这段时间可以是 0,也可以是固定的,可以是随机的RetryOperations是重试的顶级接口:
定义了实现“重试”的基本框架(模板)
)RetryOperations
的模板模式实现
,实现了重试和熔断etryTemplate将重试、提供健壮和不易出错的API供大家使用。public interface RetryOperations {
<T, E extends Throwable> T execute(RetryCallback<T, E> var1) throws E;
<T, E extends Throwable> T execute(RetryCallback<T, E> var1, RecoveryCallback<T> var2) throws E;
<T, E extends Throwable> T execute(RetryCallback<T, E> var1, RetryState var2) throws E, ExhaustedRetryException;
<T, E extends Throwable> T execute(RetryCallback<T, E> var1, RecoveryCallback<T> var2, RetryState var3) throws E;
}
编写需要执行重试的业务逻辑
,定义好业务逻辑后,就是如何重试的问题了。默认的重试策略是SimpleRetryPlicy(会重试3次)
第1次重试如果成功后面就不会继续重试了。如果3次都重试失败了流程结束或者返回兜底结果。而返回兜底结果需要配置
RecoveyCallBack
也就是重试失败后执行的逻辑。
当重试超过最大重试时间或最大重试次数后可以调用
RecoveryCallback进行恢复
,比如返回假数据或托底数据。
public interface RetryPolicy extends Serializable {
boolean canRetry(RetryContext var1);
RetryContext open(RetryContext var1);
void close(RetryContext var1);
void registerThrowable(RetryContext var1, Throwable var2);
}
方法说明:
每次重试
的时候调用,是否可以继续重试的判断条件重试开始前
调用,会创建一个重试上下文到RetryContext
,保存重试的堆栈等信息
每次重试异常
时调用(有异常会继续重试)以 SimpleRetryPolicy为例,当重试次数达到3(默认3次)停止重试,重试次数保存在重试上下文中
RetryPolicy提供了如下策略实现:
调用RetryCallback一次
,不允许重试无限重试
,直到成功,此方式逻辑不当会导致死循环
固定次数
重试策略,默认重试最大次数为3次
,RetryTemplate默认使用的策略超时时间
重试策略,默认超时时间为1秒
,在指定的超时时间内允许重试不同异常
的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试熔断功能
的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
- delegate:真正执行的重试策略,由
构造方法传入
,当重试失败时,则执行熔断策略,默认SimpleRetryPolicy策略
- openTimeout:openWindow,熔断器电路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(主要有一次重试成功,则闭合电路),
默认5000毫秒
- resetTimeout:timeout,重置熔断器重新闭合的超时时间。
默认20000毫秒
组合重试
策略,有两种
组合方式
乐观组合重试策略:指只要有一个策略允许重试即可以
悲观组合重试策略:指只要有一个策略不允许重试即可以
但不管哪种组合方式,组合中的每一个策略都会执行
回退策略: 指的是每次重试是立即重试还是等待一段时间后重试
。默认情况下是立即重试
,如果需要配置
等待一段时间后重试则需要指定回退策略
比如是网络错误,立即重试将导致立即失败,最好等待一小段时间后重试,还要防止很多服务同时重试导致DDos。
NoBackOffPolicy:无等待
策略,每次重试时立即重试
FixedBackOffPolicy:固定时间
的等待策略
需设置参数
sleeper和backOffPeriod
- sleeper指定等待策略,默认是Thread.sleep,即线程休眠
- backOffPeriod指定休眠时间,默认1000毫秒
UniformRandomBackOffPolicy:随机时间
回退策略
需设置
sleeper、minBackOffPeriod和maxBackOffPeriod
该策略在[minBackOffPeriod,maxBackOffPeriod
之间取一个随机休眠时间。
- sleeper指定等待策略,默认是Thread.sleep,即线程休眠
- minBackOffPeriod 默认500毫秒
- maxBackOffPeriod 默认1500毫秒
ExponentialBackOffPolicy:倍数
等待策略
需设置参数
sleeper、initialInterval、maxInterval和multiplier
- sleeper指定等待策略,默认是Thread.sleep,即线程休眠
- initialInterval指定初始休眠时间,默认100毫秒
- maxInterval指定最大休眠时间,默认30秒
- multiplier指定乘数,即下一次休眠时间为
当前休眠时间*multiplier
,之前说过固定倍数可能会引起很多服务同时重试导致DDos,使用随机休眠时间来避免这种情况。
ExponentialRandomBackOffPolicy:随机倍数
等待策略,引入随机乘数可以实现随机乘数回退
RetryTemplate类是对 RetryOperations的具体实现
,组合了RetryListener[],BackOffPolicy,RetryPolicy
。
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state)
throws E, ExhaustedRetryException {
//重试策略
RetryPolicy retryPolicy = this.retryPolicy;
//退避策略
BackOffPolicy backOffPolicy = this.backOffPolicy;
//重试上下文,当前重试次数等都记录在上下文中
RetryContext context = open(retryPolicy, state);
try {
//拦截器模式,执行RetryListener#open
boolean running = doOpenInterceptors(retryCallback, context);
//判断是否可以重试执行
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
//执行RetryCallback回调
return retryCallback.doWithRetry(context);
} catch (Throwable e) {
//异常时,要进行下一次重试准备
//遇到异常后,注册该异常的失败次数
registerThrowable(retryPolicy, state, context, e);
//执行RetryListener#onError
doOnErrorInterceptors(retryCallback, context, e);
//如果可以重试,执行退避算法,比如休眠一小段时间后再重试
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
backOffPolicy.backOff(backOffContext);
}
//在有状态重试时,如果是需要执行回滚操作的异常,则立即抛出异常
//shouldRethrow方法只有一行代码: state != null && state.rollbackFor(context.getLastThrowable())
if (shouldRethrow(retryPolicy, context, state)) {
throw RetryTemplate.<E>wrapIfNecessary(e);
}
}
//如果是有状态重试,且有GLOBAL_STATE属性,则立即跳出重试终止;当抛出的异常是非需要执行回滚操作的异常时,
//才会执行到此处,CircuitBreakerRetryPolicy会在此跳出循环;
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
//重试失败后,如果有RecoveryCallback,则执行此回调,否则抛出异常
return handleRetryExhausted(recoveryCallback, context, state);
} catch (Throwable e) {
throw RetryTemplate.<E>wrapIfNecessary(e);
} finally {
//清理环境
close(retryPolicy, context, state, lastException == null || exhausted);
//执行RetryListener#close,比如统计重试信息
doCloseInterceptors(retryCallback, context, lastException);
}
}
@Test
public void testSimple() throws Exception {
@Data
class Foo {
private String id;
}
RetryTemplate template = new RetryTemplate();
//超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(3000L);
template.setRetryPolicy(policy);
Foo result = template.execute(
//可能触发重试的业务逻辑
new RetryCallback<Foo, Exception>() {
@Override
public Foo doWithRetry(RetryContext context) {
try {
System.out.println("调用百度接口。。。。");
TimeUnit.MILLISECONDS.sleep(500);
throw new RuntimeException("调用百度接口超时");
} catch (InterruptedException e) {
//e.printStackTrace();
}
return new Foo();
}
},
//重试耗尽后的回调,配置了RecoveryCallback,超出重试次数后不会抛出异常,而是执行回调里的代码
new RecoveryCallback<Foo>() {
//可以没有RecoveryCallback,超出重试次数后依然会抛出异常
@Override
public Foo recover(RetryContext context) throws Exception {
System.out.println("调用百度接口。。。recover");
return new Foo();
}
});
System.out.println("result=>" + result);
}
我们模拟调用百度并将结果返回给用户,如果该调用失败,则重试该调用,直到达到超时为止。
如果不使用RecoveryCallback,当重试失败后,当重试失败后,抛出异常
如果使用RecoveryCallback,,当重试失败后,执行RecoveryCallback
无状态重试: 是在一个循环中执行完重试策略,即重试上下文保持在一个线程上下文中,在一次调用中进行完整的重试策略判断。
非常简单的情况,如远程调用某个查询方法
时是最常见的无状态重试。 SimpleRetryPolicy就属于无状态重试
,因为重试是在一个循环
中完成的。
public static void main(String[] args) {
RetryTemplate template = new RetryTemplate();
//重试策略:次数重试策略
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
template.setRetryPolicy(retryPolicy);
//退避策略:倍数回退策略
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(100);
backOffPolicy.setMaxInterval(3000);
backOffPolicy.setMultiplier(2);
backOffPolicy.setSleeper(new ThreadWaitSleeper());
template.setBackOffPolicy(backOffPolicy);
String resul = template.execute(
new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
System.out.println("retry count:" + context.getRetryCount());
throw new RuntimeException("timeout");
}
},
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
return "default";
}
});
System.out.println("result=>" + result);
}
有状态重试:就是不在一个线程上下文
完成重试,有2种场景
需要使用有状态重试,事务操作需要回滚
或者熔断器模式
。
数据库异常DataAccessException
,则不能进行重试
需要回滚
,而抛出其他异常则可以进行重试,可以通过RetryState
实现: @Test
public void retryState () {
RetryTemplate template = new RetryTemplate();
//重试策略:次数重试策略
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
template.setRetryPolicy(retryPolicy);
//当前状态的名称,当把状态放入缓存时,通过该key查询获取
Object key = "mykey";
//是否每次都重新生成上下文还是从缓存中查询,即全局模式(如熔断器策略时从缓存中查询)
boolean isForceRefresh = true;
//对DataAccessException进行回滚
BinaryExceptionClassifier rollbackClassifier = new BinaryExceptionClassifier(Collections.<? extends Throwable>>singleton(DataAccessException.class));
RetryState state = new DefaultRetryState(key, isForceRefresh, rollbackClassifier);
String result = template.execute(
new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
System.out.println("retry count:" + context.getRetryCount());
throw new TypeMismatchDataAccessException("");
}
},
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
System.out.println("recovery count:" + context.getRetryCount());
return "default";
}
}, state);
}
执行结果:报异常,没有进RecoveryCallback中
RetryTemplate中在有状态重试时,执行RecoveryCallback
时报异常
什么是熔断?
不在当前循环中处理重试
,而是全局重试模式
(不是线程上下文)。丢失线程上下文的堆栈信息
。那么肯定需要一种“全局模式”
保存这种信息,目前的实现放在一个缓存map
实现的,下次从缓存中获取就能继续重试了。熔断器场景
测试代码
@Test
public void circuitBreakerRetryPolicy () {
RetryTemplate template = new RetryTemplate();
//传入RetryPolicy(每个RetryPolicy实现都有自己的重试策略实现),是真正判断是否重试的策略,当重试失败时,则执行熔断策略;
CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3));
//熔断器电路打开的超时时间
retryPolicy.setOpenTimeout(5000);
//重置熔断器重新闭合的超时时间
retryPolicy.setResetTimeout(20000);
template.setRetryPolicy(retryPolicy);
for (int i = 0; i < 10; i++) {
try {
Object key = "circuit";
boolean isForceRefresh = false;
RetryState state = new DefaultRetryState(key, isForceRefresh);
String result = template.execute(
//重试逻辑
new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
System.out.println("retry count:" + context.getRetryCount());
throw new RuntimeException("timeout");
}
},
//重试失败兜底
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
return "default";
}
}, state);
System.out.println(result);
} catch (Exception e) {
System.out.println("catch=>" + e.getMessage());
}
}
}
isForceRefresh为false
,则在获取上下文时是根据key “circuit”从缓存中获取
,从而拿到同一个上下文
。有状态模式下,不会在循环中进行重试,会跳出循环
熔断器策略CircuitBreakerRetryPolicy需要配置三个参数:
delegate
:当前重试策略,由构造方法传入
,当重试失败时,则执行熔断策略openTimeout
:熔断器电`路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(主要有一次重试成功,则闭合电路)
源码的openWindow属性
resetTimeout
:重置熔断器重新闭合的超时时间。
源码的timeOut属性
CircuitBreakerRetryPolicy.isOpen()源码
public boolean isOpen() {
long time = System.currentTimeMillis() - this.start;
boolean retryable = this.policy.canRetry(this.context);
if (!retryable) {
//重试失败
//在重置熔断器超时后,熔断器器电路闭合,重置上下文
if (time > this.timeout) {
this.context = createDelegateContext(policy, getParent());
this.start = System.currentTimeMillis();
retryable = this.policy.canRetry(this.context);
} else if (time < this.openWindow) {
//当在熔断器打开状态时,熔断器电路打开,立即熔断
if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
setAttribute(CIRCUIT_OPEN, true);
}
this.start = System.currentTimeMillis();
return true;
}
} else {
//重试成功
//在熔断器电路半打开状态时,断路器电路闭合,重置上下文
if (time > this.openWindow) {
this.start = System.currentTimeMillis();
this.context = createDelegateContext(policy, getParent());
}
}
setAttribute(CIRCUIT_OPEN, !retryable);
return !retryable;
}
从如上代码可看出spring-retry的熔断策略相对简单:
使用CircuitBreakerRetryPolicy注意事项
delegate(重试策略)
应该配置基于次数的SimpleRetryPolicy
或者基于超时的TimeoutRetryPolicy策略
,且策略都是全局模式
,而非局部模式,所以要注意次数或超时
的配置合理性。比如SimpleRetryPolicy配置为3次,openWindow=5s,timeout=20s
,我们来CircuitBreakerRetryPolicy的极端情况。
Spring-retry通过RetryListener
实现拦截器模式,默认
提供了StatisticsListener
实现重试操作统计分析数据
。
统计分析数据
。 @Test
public void analyses() {
RetryTemplate template = new RetryTemplate();
DefaultStatisticsRepository repository = new DefaultStatisticsRepository();
StatisticsListener listener = new StatisticsListener(repository);
template.setListeners(new RetryListener[]{
listener});
for (int i = 0; i < 10; i++) {
String result = template.execute(new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
context.setAttribute(RetryContext.NAME, "method.key");
return "ok";
}
});
}
RetryStatistics statistics = repository.findOne("method.key");
System.out.println(statistics);
}
执行结果
场景描述: num作为计数器,如果num小于5则抛出异常,num会进行自增一操作,直到等于5方正常返回,否则根据重试策略进行重试操作,如果直到最后一直未重试成功,则返回Integer最大值。在重试上下文中添加一个value变量,后续通过其值实现根据返回值判断重试应用。最后打印最终的返回值。
公共方法
下面案例中调用的run方法就是下面这个
/**
* 运行重试方法
*
* @param retryTemplate
* @throws Exception
*/
public void run(RetryTemplate retryTemplate) throws Exception {
Integer result = retryTemplate.execute(
new RetryCallback<Integer, Exception>() {
int i = 0;
// 重试操作
@Override
public Integer doWithRetry(RetryContext retryContext) throws Exception {
retryContext.setAttribute("value", i);
log.info("重试 {} 次.", retryContext.getRetryCount());
return checkLen(i++);
}
},
new RecoveryCallback<Integer>() {
//兜底回调
@Override
public Integer recover(RetryContext retryContext) throws Exception {
log.info("重试{}次后,调用兜底方法", retryContext.getRetryCount());
return Integer.MAX_VALUE;
}
});
log.info("最终结果: {}", result);
}
/**
* 根据i判断是否抛出异常
*
* @param num
* @return
* @throws Exception
*/
public int checkLen(int num) throws Exception {
//小于5抛出异常
if (num < 5) throw new Exception(num + " le 5");
//否则正常返回
return num;
}
@Test
public void retryFixTimes() throws Exception {
//重试模板
RetryTemplate retryTemplate = new RetryTemplate();
//简单重试策略
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
//最大重试次数3次
simpleRetryPolicy.setMaxAttempts(3);
//模板设置重试策略
retryTemplate.setRetryPolicy(simpleRetryPolicy);
//开始执行- 超过3次最大重试次数,触发了recoveryCall,并返回Integer最大值。
run(retryTemplate);
}
执行结果
超过3次最大重试次数,触发了recoveryCall,并返回Integer最大值
AlwaysRetryPolicy 允许无限重试,直到成功,此方式逻辑不当会导致死循环
@Test
public void retryAlwaysTimes() throws Exception {
//重试模板
RetryTemplate retryTemplate = new RetryTemplate();
//设置为无限重试策略
retryTemplate.setRetryPolicy(new AlwaysRetryPolicy());
//开始执行-直到i等于5则正常返回,之前将实现无限重试。
run(retryTemplate);
}
@Test
public void retryTimeout() throws Exception {
//重试模板
RetryTemplate retryTemplate = new RetryTemplate();
TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
//超时时间为 1000 毫秒
timeoutRetryPolicy.setTimeout(1000);
//固定时间的回退策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒,单位毫秒
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
//休眠时间400毫秒
fixedBackOffPolicy.setBackOffPeriod(400);
//设置为超时时间重试策略
retryTemplate.setRetryPolicy(timeoutRetryPolicy);
//设置为固定时间的回退策略
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
//开始执行-设定1000ms后则认定为超时,每次重试等待时长400ms,故重试3次后即会超出超时阈值,触发RecoveryCallback回调,并返回Integer最大值。
run(retryTemplate);
}
执行策略
设定1000ms后则认定为超时,每次重试等待时长400ms,故重试3次后即会超出超时阈值,触发RecoveryCallback回调,并返回Integer最大值。
@Test
public void retryWithResult() throws Exception {
//重试模板
RetryTemplate retryTemplate = new RetryTemplate();
//设置为无限重试策略
retryTemplate.setRetryPolicy(new AlwaysRetryPolicy() {
private static final long serialVersionUID = 1213824522266301314L;
@Override
public boolean canRetry(RetryContext context) {
//小于1则重试
return context.getAttribute("value") == null || Integer.parseInt(context.getAttribute("value").toString()) < 1;
}
});
//开始执行-如果value值小于1或者为null则进行重试,反之不在进行重试,触发RecoveryCallback回调,并返回Integer最大值。
run(retryTemplate);
}
如果value值小于1或者为null则进行重试,反之不在进行重试,触发RecoveryCallback回调,并返回Integer最大值。
@Test
public void retryCircuitBreakerTest() {
RetryTemplate retryTemplate = new RetryTemplate();
//传入RetryPolicy(每个RetryPolicy实现都有自己的重试策略实现),是真正判断是否重试的策略,当重试失败时,则执行熔断策略;
CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(4));
//固定时间等待策略-每次重试等待300毫秒
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(300);
// 熔断器电路打开的超时时间
retryPolicy.setOpenTimeout(1500);
//重置熔断器重新闭合的超时时间
retryPolicy.setResetTimeout(2000);
//设置重试策略
retryTemplate.setRetryPolicy(retryPolicy);
//设置回退策略
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
long startTime = System.currentTimeMillis();
//
IntStream.range(0, 10).forEach(index -> {
try {
Thread.sleep(100);
RetryState state = new DefaultRetryState("circuit", false);
String result = retryTemplate.execute(
//重试业务逻辑
new RetryCallback<String, RuntimeException>() {
@Override
public String doWithRetry(RetryContext context) throws RuntimeException {
log.info("重试 {} 次", context.getRetryCount());
if (System.currentTimeMillis() - startTime > 1300 && System.currentTimeMillis() - startTime < 1500) {
return "retryCallback-success";
}
throw new RuntimeException("timeout");
}
},
//重试失败回调
new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
return "recoveryCallback-default";
}
}, state);
log.info("result: {}", result);
} catch (Exception e) {
log.error("报错了: type:{}:{}", e.getClass().getName(), e.getMessage());
}
});
}
设定重试次数为4次,在执行1300ms至1500ms期间连续两次调用成功,无需重试,其后继续抛出异常重试,第4次重试时(1405)仍然在1500ms内,故打开了断路器,后续请求异常均会直接返回 RecoveryCallback中回调定义。
以下代码需要引入spring-retry1.3 以上才能使用,目前仅供参考
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)//重试次数
.fixedBackoff(1000)//重试时间,单位ms
.retryOn(RemoteAccessException.class)//触发重试异常
.build();
template.execute(ctx -> {
// ... do something
});
1.3开始可以这样构建
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();
spring-retry重试与熔断详解—《亿级流量》内容补充