上一篇文章我们介绍了实际项目开发中重试的应用场景,以及spring-retry原理和源码的详细介绍,那么此篇我们将会详细介绍一下另外一个常用的重试组件guava-retrying。 那么guava-retrying是什么?官方的解释是,guava重试模块提供了一个通用方法,用于重试具有特定停止、重试和异常处理功能的任意Java代码,这些功能通过guava的谓词匹配得到了增强。 接下来我们就guava-retrying的使用方式、工作原理以及源码展开介绍和分析。
二.使用方式1.引入依赖
在项目中引入guava-retrying依赖:
复制代码 com.github.rholder guava-retrying2.0.0
2.定义方法回调
利用Callable接口定义业务方法回调:
Callablecallable = new Callable () { public Boolean call() throws Exception { return true; // do something useful here } }; 复制代码
该回调方法有两个点需要注意,一是返回类型,是根据具体业务场景定义,二是业务逻辑,在call方法中实现自定义重试逻辑实现(调用远程接口或者本地方法)。
3.创建重试器并执行重试
利用RetryerBuilder构建重试器并执行重试逻辑:
Retryerretryer = RetryerBuilder. newBuilder() .withRetryListener(new RetryListener() { @Override public void onRetry(Attempt attempt) { log.info("listener receive attempt={}",attempt); } }) .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1, TimeUnit.SECONDS)) .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .withWaitStrategy(WaitStrategies.fixedWait(2,TimeUnit.SECONDS)) .withBlockStrategy(BlockStrategies.threadSleepStrategy()) .retryIfException() .retryIfExceptionOfType(ServiceRuntimeException.class) .retryIfException(Predicates.equalTo(new Exception())) .retryIfRuntimeException() .retryIfResult(Predicates.equalTo(false)) .build(); try { retryer.call(new callable()); } catch (ExecutionException e) { log.error("occur error",e); } catch (RetryException e) { log.error("occur error",e); } catch (Exception e) { log.error("occur error",e); } 复制代码
我们构建了一个具有最丰富的各种策略的重试器,并执行了重试逻辑。分别包含了重试监听器、重试限制器、终止策略、等待策略、阻塞策略和各种重试策略。这样我们就可以在真实场景中使用guava-retrying提供的重试能力了。
三.原理&源码分析前边我们介绍了guava-retrying的使用方式,能够看出其使用过程和工作原理就是现根据各种策略构建一个重试器,然后使用重试器调用我们的业务逻辑回调,那么我们将参照源码来逐步分析guava-retrying的工作原理。
1.构造重试器
guava-retrying使用工厂模式创建重试器,入口是RetryerBuilder,我们逐个分析一个各种策略的构建和重试器的构建。
I)监听器
public RetryerBuilderwithRetryListener(@Nonnull RetryListener listener) { Preconditions.checkNotNull(listener, "listener may not be null"); listeners.add(listener); return this; } 复制代码
RetryerBuilder.withRetryListener为重试器添加监听器,每次重试的时候将被通知,监听器需实现RetryListener接口,支持多次调用。
II)重试限制器
public RetryerBuilderwithAttemptTimeLimiter(@Nonnull AttemptTimeLimiter attemptTimeLimiter) { Preconditions.checkNotNull(attemptTimeLimiter); this.attemptTimeLimiter = attemptTimeLimiter; return this; } 复制代码
配置重试器的重试周期和次数限制,默认提供的限制器有两种:
III)终止策略
public RetryerBuilderwithStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException { Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null"); Preconditions.checkState(this.stopStrategy == null, "a stop strategy has already been set %s", this.stopStrategy); this.stopStrategy = stopStrategy; return this; } 复制代码
重试器的终止策略配置,默认不终止,guava-retrying提供了三种终止策略:
IV)等待策略
public RetryerBuilderwithWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException { Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null"); Preconditions.checkState(this.waitStrategy == null, "a wait strategy has already been set %s", this.waitStrategy); this.waitStrategy = waitStrategy; return this; } 复制代码
重试器到的等待策略配,配置每次重试失败后的休眠时间,guava-retrying提供了8中等待策略:
V)阻塞策略
public RetryerBuilderwithBlockStrategy(@Nonnull BlockStrategy blockStrategy) throws IllegalStateException { Preconditions.checkNotNull(blockStrategy, "blockStrategy may not be null"); Preconditions.checkState(this.blockStrategy == null, "a block strategy has already been set %s", this.blockStrategy); this.blockStrategy = blockStrategy; return this; } 复制代码
阻塞策略配置每次重试之前如何阻塞流程,默认是线程休眠,guava-retrying只提供了一种阻塞策略:
VI)重试策略
public RetryerBuilderretryIfException() { rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate (Exception.class)); return this; } public RetryerBuilder retryIfRuntimeException() { rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate (RuntimeException.class)); return this; } public RetryerBuilder retryIfExceptionOfType(@Nonnull Class extends Throwable> exceptionClass) { Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate (exceptionClass)); return this; } public RetryerBuilder retryIfException(@Nonnull Predicate exceptionPredicate) { Preconditions.checkNotNull(exceptionPredicate, "exceptionPredicate may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionPredicate (exceptionPredicate)); return this; } public RetryerBuilder retryIfResult(@Nonnull Predicate resultPredicate) { Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ResultPredicate (resultPredicate)); return this; } 复制代码
重试策略也就是配置哪些异常类型或者自定义返回类型需要重试,guava-retrying定义并提供了一下几种重试策略:
VII)构造重试器
public Retryerbuild() { AttemptTimeLimiter theAttemptTimeLimiter = attemptTimeLimiter == null ? AttemptTimeLimiters. noTimeLimit() : attemptTimeLimiter; StopStrategy theStopStrategy = stopStrategy == null ? StopStrategies.neverStop() : stopStrategy; WaitStrategy theWaitStrategy = waitStrategy == null ? WaitStrategies.noWait() : waitStrategy; BlockStrategy theBlockStrategy = blockStrategy == null ? BlockStrategies.threadSleepStrategy() : blockStrategy; return new Retryer (theAttemptTimeLimiter, theStopStrategy, theWaitStrategy, theBlockStrategy, rejectionPredicate, listeners); } 复制代码
如果没有传入重试限制器,则默认使用AttemptTimeLimiters.noTimeLimit();如果没有定义终止策略,则默认使用永不终止策略;如果没有定义等待策略,则默认使用无需等待策略;如果没有定义阻塞策略,则默认使用线程阻塞策略,最有使用重试限制器、终止策略、等待策略、阻塞策略、重试策略和监听器创建重试器。
2.执行重试
构建完重试器之后,就会调用重试逻辑,我们看一下重试逻辑的核心代码:
public V call(Callablecallable) throws ExecutionException, RetryException { long startTime = System.nanoTime(); for (int attemptNumber = 1; ; attemptNumber++) { Attempt attempt; try { V result = attemptTimeLimiter.call(callable); attempt = new ResultAttempt (result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } catch (Throwable t) { attempt = new ExceptionAttempt (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); } } } } 复制代码
执行给定的调用,如果重试策略接受重试,则使用停止策略来决定是否必须进行新的尝试,然后,使用等待策略来决定睡眠时间,并进行新的尝试。我们按照顺序也逐个分析一下重试逻辑的原理:
I)执行逻辑
V result = attemptTimeLimiter.call(callable); 复制代码
在构造重试器逻辑中我们知道限制器有两种,分别是FixedAttemptTimeLimit和NoAttemptTimeLimit,前者是带超时时间的重试后者是直接调用Callable的call方法。
II)通知监听器
for (RetryListener listener : listeners) { listener.onRetry(attempt); } 复制代码
通知监听器执行了重试。
III)重试策略裁定
if (!rejectionPredicate.apply(attempt)) { return attempt.get(); } 复制代码
如果不满足重试策略直接返回结果,这里的重试策略谓词是多种重试策略并集,任何一种满足就认为满足:
private static class OrPredicateimplements Predicate , Serializable { private final List extends Predicate super T>> components; private OrPredicate(List extends Predicate super T>> components) { this.components = components; } @Override public boolean apply(@Nullable T t) { // Avoid using the Iterator to avoid generating garbage (issue 820). for (int i = 0; i < components.size(); i++) { if (components.get(i).apply(t)) { return true; } } return false; } //省略 } 复制代码
IV)终止策略裁定
if (stopStrategy.shouldStop(attempt)) { throw new RetryException(attemptNumber, attempt); } 复制代码
如果命中终止策略,则直接抛重试异常终止流程,终止策略这里不再表述。
V)等待和阻塞
else { long sleepTime = waitStrategy.computeSleepTime(attempt); try { blockStrategy.block(sleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RetryException(attemptNumber, attempt); } } 复制代码
如果没有命中终止策略,则进入等待和阻塞,等待策略和阻塞策略结合使用,先根据等待策略计算出需要休眠的时间,然后调用阻塞策略阻塞相应时间。
如果for循环体重没有异常终止或者正常返回,那么进入下一次重试,直到资格耗尽(或者无限重试)。
四.优缺点使用过guava-retrying或者分析过其源码你会发现,guava-retrying重试组件特别轻量级,核心类就那几个,并且使用简单设计优雅,但是它也存在缺点,和spring-retry一样我们也枚举一下guava-retrying的优缺点:
优点
- 策略丰富并且支持自定义
- 使用简单
- 设计优雅
缺点
- 不支持注解
- 侵入业务代码
- 重复性强
本篇从使用和源码维度详细介绍了guava-retrying使用方式和实现原理,以及其优缺点,当然我从翻阅guava-retrying重试组件的过程中也学到了很多东西,优雅的设计、面向接口编程的炉火纯青的使用、以及高度结构化的编码方式,这些东西是我从其他框架中很少见到的。当然回归到本篇主题,希望通过本篇文章以及上一篇文章对重试组件的介绍,对大家在重试组件的选型、使用和理解上有所帮助。
分类:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)