文章目录
- Guava-retrying
- 1. 主要相关类
- 2. WaitStrategies 重试等待策略
- 2.1 ExponentialWaitStrategy 指数等待策略(WaitStrategies.exponentialWait)
- 2.2 FibonacciWaitStrategy 斐波那契等待策略(WaitStrategies.fibonacciWait)
- 2.3 FixedWaitStrategy 固定时长等待策略(WaitStrategies.fixedWait)
- 2.4 RandomWaitStrategy 随机时长等待策略(WaitStrategies.randomWait)
- 2.5 IncrementingWaitStrategy 递增等待策略(WaitStrategies.incrementingWait)
- 2.6 ExceptionWaitStrategy 异常等待策略(WaitStrategies.exceptionWait)
- 2.7 CompositeWaitStrategy 复合等待策略 【***】(WaitStrategies.join)
- 3. StopStrategies 重试停止策略
- 4 AttemptTimeLimiters 任务执行时长限制(withAttemptTimeLimiter)
- 5. BlockStrategies 阻塞策略
- 6. 封装使用
Guava-retrying
当调用外部服务或相关接口的场景时,外部服务对调用来说一般都是不可靠的,特别现在的微服务,在网络环境较差的情况下,网络抖动非常容易导致请求超时等异常情况,这种情况就需要进行失败重试来重新调用API容错。重试策略在服务治理方面有很广泛的使用,一般通过定时检测查看服务是否存活。
Guava Retrying
是Google Guava库的一个扩展包,可以为任意函数调用创建可配置的重试机,是一个灵活方便的 重试组件,包含了多种的重试策略,最重要的是扩展起来非常容易
通过Guava-retrying
自定义重试机制,解决系统中的各种不稳定因素,同时监控每次重试的结果和行为
1. 主要相关类
1.1 Attemp 类
Attemp
** 是一次任务重试(call),也是一次请求的结果**,主要保存当前请求的重试次数、是否包含异常、请求返回值。一般配合监听器使用,用于处理重试过程的具体细节
Attemp 方法 | 描述 |
---|---|
long getAttemptNumber() |
当前重试的次数(第几次重试)从 1 开始 |
long getDelaySinceFirstAttempt() |
距离第一次重试延迟时间,也就是与第一次重试的时间差,单位毫秒 |
boolean hasException() |
判断是否存在异常(可以根据异常重试/特殊结果值重试) |
boolean hasResult() |
判断是否返回数据结果(对满足特殊结果值进行重试) |
Throwable getExceptionCause() throws IllegalStateException |
异常重试的数据,获取异常信息 |
V getResult() throws IllegalStateException |
获取重试结果信息 |
V get() throws ExecutionException |
类似于 getResult() 返回重试结果,但是处理异常方式不同 |
1.2 Retryer 类
Retryer
是核心类,用于执行重试策略,一帮通过RetryerBuilder
类进行构造(Factory 创建者),且RetryerBuilder
负责将设置好的重试策咯添加到Retryer
中,最终通过执行Retryer
的核心方法call
来执行重试策略
大概流程如下流程:
- 判断是否超过任务时长限制
- 执行重试listener
- 判断是否满足重试条件
- 重试停止策略
- 重试等待策略
- 阻塞策略
call
方法源码如下:public V call(Callable<V> callable) throws ExecutionException, RetryException { long startTime = System.nanoTime(); for (int attemptNumber = 1; ; attemptNumber++) { Attempt<V> attempt; try { // 任务执行的时间限制 V result = attemptTimeLimiter.call(callable); attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } catch (Throwable t) { 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); } } } }
RetryerBuilder
是一个 Factory 创建者,可以自定义设置重试源且支持多个重试源,可以Exception
异常对象 和 自定义断言对象 ,通过retryIfException
和retryIfResult
设置,同时支持多个且能兼容。
RetryerBuilder 属性 | 描述 |
---|---|
retryIfExceptionOfType(Class<? extends Throwable> exceptionClass) |
只在发生特定异常的时候才重试,比如 NullPointerException :retryIfExceptionOfType(Exception.class);
|
通过 Predicate 实现retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class)
|
Predicates.instanceOf(NullPointerException.class))) |
retryIfException |
抛出 runtime 异常、checked 异常时会重试,但抛出 error 不会重试 |
retryIfRuntimeException |
抛 runtime 异常的时重试,checked 异常和 error 都不重试 |
retryIfExceptionOfType(Error.class) |
只在抛出error重试 |
retryIfResult 可以指定 Callable 方法在返回值的时候进行重试 |
|
retryIfResult(Predicates.equalTo(false)) |
返回 false 重试 |
retryIfResult(Predicates.containsPattern("_customInfo$")) |
以 _customInfo 结尾才重试 |
1.3 RetryListener
监听器,当执行
call
方法时,会调用监听器 RetryListener 中的onRetry
方法,通过实现然后重写该类,来实现自定义的重试逻辑机制。
@Beta
public interface RetryListener {
// 监听方法
<V> void onRetry(Attempt<V> var1);
}
2. WaitStrategies 重试等待策略
当执行失败后,通过
WaitStrategies
来指定不同的等待策略来进行第 n 次的重试通过
withWaitStrategy
方法可以设置不同的重试等待策略,常见策略如下几种:
2.1 ExponentialWaitStrategy 指数等待策略(WaitStrategies.exponentialWait)
指数补偿算法实现(wikipedia Exponential Backoff),根据重试次数来计算等待的时长,源码如下:
@Override public long computeSleepTime(Attempt failedAttempt) { double exp = Math.pow(2, failedAttempt.getAttemptNumber()); long result = Math.round(multiplier * exp); if (result > maximumWait) { result = maximumWait; } return result >= 0L ? result : 0L; }
// 默认倍数(乘) multiplier = 1,最大值是 Long.MAX_VALUE RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.exponentialWait()); // 指定乘数multiplier 和 最大值,第一次失败后,依次等待时长:2^1 * 100、2^2 * 100、2^3 * 100...直到最多5分钟。 // 5分钟后,每隔5分钟重试一次 // 3个参数, multiplier: 乘数, maximumTime: 最大等待时长, maximumTimeUnit: 最大等待时长单位 RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES));
2.2 FibonacciWaitStrategy 斐波那契等待策略(WaitStrategies.fibonacciWait)
失败后按斐波那契数列进行等待
// 默认乘数multiplier是1,最大值是Long.MAX_VALUE RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fibonacciWait()); // 指定乘数multiplier 和 最大值,第一次失败后,依次等待时长,1*100、1*100、2*100、3*100、5*100...直到最多5分钟,5分钟后每隔5分钟重试一次 // 3个参数, multiplier: 乘数, maximumTime: 最大等待时长, maximumTimeUnit: 最大等待时长单位 RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fibonacciWait(100, 5, TimeUnit.MINUTES));
2.3 FixedWaitStrategy 固定时长等待策略(WaitStrategies.fixedWait)
失败后,将等待固定的时长进行重试
// 每 100 ms 重试一次 RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS));
2.4 RandomWaitStrategy 随机时长等待策略(WaitStrategies.randomWait)
通过设置随机等待的时长区间,或者随机等待的最大时长,从中取随机数,随机时间重试
// 最大随机时长10s RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.randomWait(10, TimeUnit.SECONDS)); // 随机区间配置,[2, 10] 2-10s随机等待,四个参数分别为: // minimumTime: 最小值,minimumTimeUnit: 最小值单位; maximumTime: 最大值, maximumTimeUnit: 最大值单位 RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.randomWait(2, TimeUnit.SECONDS, 10, TimeUnit.SECONDS));
2.5 IncrementingWaitStrategy 递增等待策略(WaitStrategies.incrementingWait)
根据初始值和递增值,等待时长依次递增
// 递增配置,初始2s,后面每次在前面的基础上加3s,等待时长: 2、5、8、11、14、17、20 // 四个参数 >>> initialSleepTime: 初始等待时长,initialSleepTimeUnit: 初始等待时长单位, increment: 递增时长值, incrementTimeUnit: 递增时长单位 RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.incrementingWait(2, TimeUnit.SECONDS, 3, TimeUnit.SECONDS))
2.6 ExceptionWaitStrategy 异常等待策略(WaitStrategies.exceptionWait)
根据配置异常信息指定重试的等待时长,如果异常不匹配,则等待时长为0
// 当出现空指针异常时,等待1s,出现数组越界异常时等待2s (可以配置多个) // 参数: exceptionClass: 异常类,Function<T, Long> function: 处理函数,出现对应异常,返回等待时长 RetryerBuilder<Object> builder = RetryerBuilder.newBuilder(); builder.withWaitStrategy(WaitStrategies.exceptionWait(NullPointerException.class, e -> 1000L)); builder.withWaitStrategy(WaitStrategies.exceptionWait(ArrayIndexOutOfBoundsException.class, e -> 2000L));
2.7 CompositeWaitStrategy 复合等待策略 【***】(WaitStrategies.join)
当同时满足一个或多个等待策略,等待时间为所有等待策略时间的总和。例如:
// 固定时长策略 + 异常等待策略,对于空指针异常,等待3s,其它情况等待2s // join 组合多个策略 RetryerBuilder.newBuilder().withWaitStrategy( WaitStrategies.join(WaitStrategies.exceptionWait(NullPointerException.class, e -> 1000L), WaitStrategies.fixedWait(2, TimeUnit.SECONDS)));
3. StopStrategies 重试停止策略
指定重试多少次后停止重试,最好都配置,不然可能出现无限制的重试,通过
withStopStrategy
方法可以设置重试停止策略
3.1 NeverStopStrategy 无限制重试(StopStrategies.neverStop)
// 无限次数重试,谨慎使用 RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.neverStop());
3.2 StopAfterAttemptStrategy 重试指定次数停止(StopStrategies.stopAfterAttempt)
// 重试五次结束 RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.stopAfterAttempt(5));
3.3 StopAfterDelayStrategy 重试指定时长后结束(StopStrategies.stopAfterDelay)
// 10s重试,超过10s结束 RetryerBuilder.newBuilder().withStopStrategy(StopStrategies.stopAfterDelay(10, TimeUnit.SECONDS));
4 AttemptTimeLimiters 任务执行时长限制(withAttemptTimeLimiter)
表示单次任务执行时间限制【如果出现单次任务执行超时,则终止执行当前重试任务】,通过
withAttemptTimeLimiter
方法设置任务的执行时间限制
4.1 FixedAttemptTimeLimit 指定执行时长限制(AttemptTimeLimiters.fixedTimeLimit)
指定任务的执行时长限制,为了控制线程管理,最好指定相应的线程池
// 重试方法超过2s中断 RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS)); RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS), Executors.newCachedThreadPool());
4.2 NoAttemptTimeLimit 无时长限制(AttemptTimeLimiters.noTimeLimit)
RetryerBuilder.newBuilder().withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit())
5. BlockStrategies 阻塞策略
重试等待的过程中,根据等待策略计算的时间,来阻塞执行
默认只提供一种阻塞策略:ThreadSleepStrategy
,实现方式是通过Thread.sleep()
睡眠方式来现【睡眠方式实现优势: 可以响应外部中断请求】默认的阻塞策略是线程休眠,可以自定义阻塞策略,这里使用自旋锁实现,不阻塞线程
public class GuavaBlockStrategy implements BlockStrategy { @Override public void block(long sleepTime) throws InterruptedException { long start = System.currentTimeMillis(); long end = start; while (end - start <= sleepTime) { end = System.currentTimeMillis(); } LogUtil.info("block end", start, end, sleepTime); } }
使用:
// 自定义阻塞策略:自旋锁实现 RetryerBuilder.newBuilder().withBlockStrategy(new SpinBlockStrategy());
6. 封装使用
实际项目使用参考: Guava retry 封装使用