Guava Retry 重试机制流程分析

Guava Retry 重试机制源码分析

在进行重试时,我们要考虑以下几点:

  • 何时启动重试机制(发生异常、网络延迟等);
  • 何时结束重试(调用成功);
  • 重试的时间间隔是多久,即计算等待时间(固定时间、指数退避等);
  • 如何进行等待(Thread.sleep)

而在 Guava Retry 库中都提供了这些的解决方法。

一、Guava Retry 重试机制使用

引入 guava-retry 库:

 

java

代码解读

复制代码

<dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency>

示例代码:

 

java

代码解读

复制代码

@Slf4j public class FixIntervalRetryStrategy implements RetryStrategy<RpcResponse> { @Override public RpcResponse doRetry(Callable<RpcResponse> callable) throws Exception { Retryer<RpcResponse> retryer = RetryerBuilder.<RpcResponse>newBuilder() .retryIfExceptionOfType(Exception.class) .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) .withRetryListener(new RetryListener() { @Override public <V> void onRetry(Attempt<V> attempt) { // 监听重试机制 log.info("重试次数:{}", attempt.getAttemptNumber()); } }) .build(); return retryer.call(callable); } }

  • retryIfExceptionOfType:在发生什么类型的异常时进行重试;
  • withStopStrategy:何时结束重试;
  • withWaitStrategy:每次等待多久后进行再次重试;
  • withRetryListener:定义重试的监听器;

1)对于何时进行重试,Guava 的 RetryerBuilder 类给我们提供了很多方法:

 

java

代码解读

复制代码

// 在发生Exception异常时触发重试 public RetryerBuilder<V> retryIfException() { rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate<V>(Exception.class)); return this; } // 在发生RuntimeException异常时触发重试 public RetryerBuilder<V> retryIfRuntimeException() { rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate<V>(RuntimeException.class)); return this; } // 在发生指定Exception异常时触发重试 public RetryerBuilder<V> retryIfExceptionOfType(@Nonnull Class<? extends Throwable> exceptionClass) { Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate<V>(exceptionClass)); return this; } public RetryerBuilder<V> retryIfException(@Nonnull Predicate<Throwable> exceptionPredicate) { Preconditions.checkNotNull(exceptionPredicate, "exceptionPredicate may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionPredicate<V>(exceptionPredicate)); return this; } public RetryerBuilder<V> retryIfResult(@Nonnull Predicate<V> resultPredicate) { Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ResultPredicate<V>(resultPredicate)); return this; }

2)对于重试间隔,Guava 的 WaitStrategies 给我们提供了很多种策略:

  • FixedWaitStrategy:固定时间进行重试;
  • RandomWaitStrategy:随机等待时间进行重试;
  • IncrementingWaitStrategy:根据失败次数计算等待时间
  • ExponentialWaitStrategy:随着指数倍数增长;
  • FibonacciWaitStrategy:随着失败尝试次数的增加,等待时间按照斐波那契数列增长;
  • CompositeWaitStrategy:这个策略类的特点是将多个不同的等待策略组合在一起,并按顺序将这些策略的等待时间相加,以计算总的等待时间;
  • ExceptionWaitStrategy:根据指定异常自定义过期时间;

3)对于何时结束重试,Guava 的 StopStrategies 给我们提供了很多种策略:

  • StopAfterDelayStrategy:在延迟多久后停止重试;
  • StopAfterAttemptStrategy:在重试指定次数后停止重试;
  • NeverStopStrategy:从不停止重试;

4)对于等待调用,Guava 只提供了一个类 ThreadSleepStrategy:

 

java

代码解读

复制代码

@Immutable private static class ThreadSleepStrategy implements BlockStrategy { @Override public void block(long sleepTime) throws InterruptedException { Thread.sleep(sleepTime); } }

  • 实质上是调用 Thread.sleep 方法进行阻塞等待;

二、重试机制的核心流程

2.1 构建 Retry 类

我们先看看 RetryBuilder 的 build 方法,它是如何构建出 Retry 的:

 

java

代码解读

复制代码

public Retryer<V> build() { AttemptTimeLimiter<V> theAttemptTimeLimiter = attemptTimeLimiter == null ? AttemptTimeLimiters.<V>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<V>(theAttemptTimeLimiter, theStopStrategy, theWaitStrategy, theBlockStrategy, rejectionPredicate, listeners); }

  • 该方法会设置停止策略,阻塞策略,等待策略以及 theAttemptTimeLimiter,其中 theAttemptTimeLimiter 的作用是确保重试方法调用时不会超过指定时间限制,避免阻塞进程,它可以设置为无限制等待或者指定时间等待。
  • 我们还需要注意 rejectionPredicate 这个参数,这个参数是判断是否进行重试的关键

rejectionPredicate 的构建我们需要 RetryBuilder 类里面的方法:

 

java

代码解读

复制代码

public RetryerBuilder<V> retryIfExceptionOfType(@Nonnull Class<? extends Throwable> exceptionClass) { Preconditions.checkNotNull(exceptionClass, "exceptionClass may not be null"); rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate<V>(exceptionClass)); return this; } public static <T extends @Nullable Object> Predicate<T> or( Predicate<? super T> first, Predicate<? super T> second) { return new OrPredicate<>(Predicates.<T>asList(checkNotNull(first), checkNotNull(second))); } // 這個是OrPredicate的構造函數 private OrPredicate(List<? extends Predicate<? super T>> components) { this.components = components; }

  • 通过将Predicate的判断实现类封装到 components 数组中,然后遍历该数组查找符合条件当前重试条件的一个,如果没有符合条件意味着当前不需要重试;

其中 Predicate 接口的实现类是 ExceptionClassPredicate,是 RetryBuilder 的内部类:

 

java

代码解读

复制代码

private static final class ExceptionClassPredicate<V> implements Predicate<Attempt<V>> { private Class<? extends Throwable> exceptionClass; public ExceptionClassPredicate(Class<? extends Throwable> exceptionClass) { this.exceptionClass = exceptionClass; } @Override public boolean apply(Attempt<V> attempt) { // 判断当前重试中是否存在异常 if (!attempt.hasException()) { return false; } return exceptionClass.isAssignableFrom(attempt.getExceptionCause().getClass()); } }

  • Attempt 是当前重试动作的抽象;

2.2 执行重试流程

创建出 Retry 后,我们看看 Retry 的 call 方法:

 

java

代码解读

复制代码

public V call(Callable<V> callable) throws ExecutionException, RetryException { long startTime = System.nanoTime(); // 循环记录重试次数 for (int attemptNumber = 1; ; attemptNumber++) { Attempt<V> attempt; try { // 执行callable接口方法 V result = attemptTimeLimiter.call(callable); // 封装重试结果 attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } catch (Throwable t) { // 重试执行如果发生异常就封装到ExceptionAttempt中 attempt = new ExceptionAttempt<V>(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); } } } }

该方法就是调用重试机制核心方法,它的主流程包括以下步骤:

  • 执行重试方法,判断当前是否存在异常信息决定是否创建 ExceptionAttempt;
  • 接着调用所有重试监听器;
  • 根据 Attempt 的 hasException 方法,在 rejectionPredicate 类中判断是否需要继续重试;
  • 若需要重试,则先根据 stopStrategy 判断当前重试是否需要结束;
  • 如果不需要结束,则再根据 waitStrategy 计算出当前需要暂停的时间;
  • 然后调用 blockStrategy 进行睡眠等待;

三、收获

  • 通过策略模式,提供不同的等待策略,停止策略供用户选择,同时用户可以自定义策略,只要实现特定的接口即可;
  • call 方法的流程符合依赖倒置原则,对于不同的处理过程(等待、停止、阻塞),符合依赖倒置原则,程序依赖于抽象接口,而不是具体实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值