1、前言
在项目中,我们经常会遇到网络波动,或者调用第三方接口偶尔异常的情况。为了保证数据的可用性、程序的健壮性。我们常常加入重试的操作,多请求几次,若干次请求之后,如果还是失败,才算是真正的失败。成熟的框架上都有体现,比如dubbo的重试机制,spring全家桶自带的重试、以及google出品的guava里的重试机制等。今天以Spring框架中的重试机制为例分析、学习。
2、接入依赖
这里的依赖引用的是目前最新的版本,和老版本里的使用稍微有点差异,后面会详细说明。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
3、初体验
@Test
public void retry01(){
RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).fixedBackoff(2000).build();
retryTemplate.execute(new RetryCallback(){
@Override
public Object doWithRetry(RetryContext context) throws Throwable {
System.out.println(">>>>>>>>>>>>>>>>>retry test"+ DateUtils.formatDate(new Date()));
throw new RuntimeException("retry excption");
}
}, new RecoveryCallback(){
@Override
public Object recover(RetryContext context) throws Exception {
System.out.println(">>>>>>>>>>>>>>>>>retry test fail");
return null;
}
});
System.out.println("over");
}
4、结果
5、详细分析
上述demo中,实现了每隔2s,重试一次,重试三次后再执行后处理(“retry test fail”),最终结束程序的运行。
RetryTemplate:通过重试模板构建一个指定的模板,通过设置请求的次数(maxAttempts),设置时间间 隔(fixedBackoff).
通过excute方法,并传入参数执行方法。
RetryCallback:重试回调接口。指重复请求的业务逻辑需要实现RetryCallback.doWithRetry()方法。
RetryContext: 执行重试过程的上下文。
RecoveryCallback:重试结束依然没有想要的结果,需要处理的业务接口。同样需要实现RecoveryCallback.recover() 方法。
那么问题来了,什么情况下,才能触发重试机制呢???
是异常,源码里给了注释:
追踪源码:
从源码中可以看到,他是通过传递的重试策略,判断是否可以重试。该案例中没有设置重试策略,故使用的是默认的重试策略(SimpleRetryPolicy)。
从spring框中可以看出,提供了11种重试策略:
默认的重试策略对于canRetry()的逻辑是这样的:
从这里我们就可看出是通过异常触发重试策略的。
RetryTemplate 默认提供了简单测重试机制,为了满足多变的业务场景,我们可以定制属于自己的重试框架,比如监听器,不同的重试策略等。
6、扩展
Google提供的重试框架更灵活一些、更轻量级一些。可以支持线程池、以及多种条件触发重试机制。这里有一个小demo,就不做源码的分析了。
@Test
public void retry02() {
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfResult(Predicates.equalTo(false))
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
try {
RetryerCallable<Boolean> wrap = retryer.wrap(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.out.println("dosomething>>>>>>>>>:" + DateUtils.formatDate(new Date()));
return false;
}
});
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<Boolean> submit = executorService.submit(wrap);
submit.get();
}catch (Exception e){
System.out.println("opver");
}
}