使用场景
做自动化场景,对于一些数据统计,有时数据同步更新会遇到延迟的场景,延时的时间可能不确定,此时,如果用 sleep 一定时间的话,可能会造成时间的浪费。那么有什么方法了避免呢,比如使用重试机制,设置重试的时间和重试的次数,当获取不到数据时根据预定的时间来重新请求接口。
重试的实现的要素
- 重试触发时机
- 什么条件去停止
- 重试等待时间
- 重试停止条件
- 接口请求时间限制
- 如果结束
- 重试时的监听
guava-retrying 策略介绍
- ** retryIfException() **: 当发生任何异常时触发重试。
- ** retryIfExceptionOfType(Class<? extends Throwable> exceptionType) ** : 当发生指定类型的异常时触发重试。
- ** retryIfRuntimeException() **:当发生 RuntimeException 或其子类异常时触发重试。
- ** retryIfResult(Predicate<R> resultPredicate) ** :根据返回结果的条件触发重试。可以使用自定义的 Predicate 对返回结果进行判断。
- ** retryIfException(Predicate<Throwable> exceptionPredicate) **:根据异常的条件触发重试。可以使用自定义的 Predicate 对异常进行判断。
- ** withStopStrategy(StopStrategy stopStrategy) **:配置停止策略,指定在何时停止重试。常用的停止策略包括:
- ** StopStrategies.stopAfterAttempt(int maxAttempts) **:在达到最大尝试次数后停止重试。
- ** StopStrategies.neverStop() **:永不停止重试,会一直进行重试
- ** withWaitStrategy(WaitStrategy waitStrategy) **:配置等待策略,指定每次重试之间的等待时间。常用的等待策略包括:
- ** WaitStrategies.fixedWait(long sleepTime, TimeUnit timeUnit) **:固定等待时间,每次重试之间等待指定的时间间隔。
- ** WaitStrategies.randomWait(long minimumTime, long maximumTime, TimeUnit timeUnit) **:随机等待时间,每次重试之间等待随机的时间间隔。
- ** withAttemptTimeLimiter(AttemptTimeLimiter<V> attemptTimeLimiter) **:配置重试时间限制器,用于限制每次重试的时间。可以使用 FixedAttemptTimeLimit、ExponentialAttemptTimeLimit 或自定义的时间限制器。
- ** withRetryListener(RetryListener retryListener) **:添加重试监听器,用于在重试过程中触发相应的事件。可以实现 RetryListener 接口自定义监听器
- ** withBlockStrategy() **:方法用于配置阻塞策略,指定在进行重试时的阻塞行为。阻塞策略定义了在重试之间是否需要进行阻塞等待,以及等待的方式和时长
构造一个 Retryer
Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
.retryIfException()
// 自定义重试条件
.retryIfExceptionOfType(NeedRetryException.class)
//停止策略,最大尝试请求, attemptNumber次
.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))
//等待策略,每次请求间隔1s
.withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.SECONDS))
//尝试请求时间限制,不能超过 taskTime
.withAttemptTimeLimiter(new FixedAttemptTimeLimit(taskTime, TimeUnit.SECONDS, Executors.newCachedThreadPool()))
//方法用于配置阻塞策略,指定在进行重试时的阻塞行为
.withBlockStrategy(spinBlockStrategy)
//重试过程的监听策略,打印了尝试的次数
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
log.info("尝试次数 " + attempt.getAttemptNumber());
}
})
.build();
尝试定义一个方法,使用 Callable 接口作为参数传入一个执行的任务
public static <T> T retry(Callable<T> task, int attemptNumber, int sleepTime, int taskTime) throws Throwable {
SpinBlockStrategy spinBlockStrategy = new SpinBlockStrategy();
spinBlockStrategy.block(1);
Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
.retryIfException()
// 自定义重试条件
.retryIfExceptionOfType(NeedRetryException.class)
//停止策略,最大尝试请求, attemptNumber次
.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNumber))
//等待策略,每次请求间隔1s
.withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.SECONDS))
//尝试请求时间限制,不能超过 taskTime
.withAttemptTimeLimiter(new FixedAttemptTimeLimit(taskTime, TimeUnit.SECONDS, Executors.newCachedThreadPool()))
//方法用于配置阻塞策略,指定在进行重试时的阻塞行为
.withBlockStrategy(spinBlockStrategy)
//重试过程的监听策略,打印了尝试的次数
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
log.info("尝试次数 " + attempt.getAttemptNumber());
}
})
.build();
try {
return retryer.call(task);
} catch (RetryException | ExecutionException e) {
//throw inner exception
String msg = "";
log.error("retry internal error: {}", msg);
throw new RuntimeException(msg);
}
}
guava-retrying3 结合testng使用,如果当我们Assert来作为判断任务的尝试条件时,有一个点需要注意,retry中的 call 方法最终抛出的对象为 throw new ExecutionError((Error)cause),这边 new 出了一个 com.google.common.util.concurrent.ExecutionError 类的对象。而不是 java.util 类的。因此,我们需要重写重试报错类型

代码中call的方法
retryer.call(task)
private void wrapAndThrowRuntimeExecutionExceptionOrError(Throwable cause) {
if (cause instanceof Error) {
throw new ExecutionError((Error)cause);
} else {
throw new UncheckedExecutionException(cause);
}
}
网友评论