在我們的開發中,api 接口調用異常是經常會遇到的,任何接口都會有不同概率的異常情況,對於可以重入的接口,爲了避免偶發性異常造成的服務的不可用,重試機制就非常有必要了.Guava-Retryiny 是一個非常靈活的重試組件,包含多種重試策略,擴展很方便。
一、maven依賴
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
在這個包中,最重要的類就是 Retryer,只要我們通過一定的策略創建出 Retryer 對象,通過調用 Retryer 對象的 call 方法,即可按照既定策略執行參數中傳入的 Callable 接口的 call 方法。這裏重試的方法需要包裝到Callable接口當中, 方法很多的話使用起來稍有不便,優化方式可以使用AOP的方式來使用Retry。
二、Retryer 介紹
Retryer 提供了構造方法,用來創建一個指定規則的 Retryer:這個方法可以通過傳入嘗試時間策略、停止重試策略、重試間隔等待策略、重試阻塞策略、拒絕策略等策略來指定一個請求的重試如何進行
Retryer(@Nonnull AttemptTimeLimiter<V> attemptTimeLimiter,
@Nonnull StopStrategy stopStrategy,
@Nonnull WaitStrategy waitStrategy,
@Nonnull BlockStrategy blockStrategy,
@Nonnull Predicate<Attempt<V>> rejectionPredicate,
@Nonnull Collection<RetryListener> listeners)
- attemptTimeLimiter -- 每次重試的最大超時,超過該超時則拋出 TimeoutException
- stopStrategy -- 停止重試的策略
- waitStrategy -- 重試間間隔等待策略
- blockStrategy -- 重試間間隔線程阻塞策略
- rejectionPredicate -- 拒絕嘗試斷言
- listeners -- 重試的監聽者
超時時長控制 -- AttemptTimeLimiter 接口
public interface AttemptTimeLimiter<V> {
V call(Callable<V> var1) throws Exception;
}
這個接口主要是用來控制每次重試的超時時間
通常來說,我們會通過 AttemptTimeLimiters 類來創建這個接口的實現
AttemptTimeLimiters
AttemptTimeLimiters 就是一個生產 AttemptTimeLimiter 實現的工廠類,主要的方法有三個:
不限制時間 -- noTimeLimit
public static <V> AttemptTimeLimiter<V> noTimeLimit()
限制超時 -- AttemptTimeLimiter
public static <V> AttemptTimeLimiter<V> fixedTimeLimit(long duration,
@Nonnull TimeUnit timeUnit)
public static <V> AttemptTimeLimiter<V> fixedTimeLimit(long duration,
@Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService)
兩個方法通過 duration 和 timeUnit 組合實現了對調用的接口實現總的超時控制
終止策略 -- StopStrategy接口
public interface StopStrategy {
boolean shouldStop(Attempt var1);
}
這個接口中只有一個方法,顧名思義,他就是用來判斷是否應該停止重試
他傳入了 Attempt 爲參數,通過這個參數,我們可以獲取到方法的返回值、拋出的異常、重試的次數等等
Attempt 類
public interface Attempt<V> {
V get() throws ExecutionException;
boolean hasResult();
boolean hasException();
V getResult() throws IllegalStateException;
Throwable getExceptionCause() throws IllegalStateException;
long getAttemptNumber();
long getDelaySinceFirstAttempt();
}
StopStrategys
我們也可以通過 StopStrategys 來生產 StopStrategy 對象它提供了三個 static 方法:
不停止 -- neverStop
public static StopStrategy neverStop()
在一定次數後停止 -- stopAfterAttempt
public static StopStrategy stopAfterAttempt(int attemptNumber)
在一定超時後停止 -- stopAfterDelay
public static StopStrategy stopAfterDelay(long duration, @Nonnull TimeUnit timeUnit)
間隔策略 -- WaitStrategy 接口
public interface WaitStrategy {
long computeSleepTime(Attempt var1);
}
WaitStrategy 接口只包含一個方法,顧名思義,就是間隔的時長
WaitStrategies
同樣的,我們也可以使用 WaitStrategies 類來生產 WaitStrategy,WaitStrategies 提供了非常強大而豐富的 static 方法
不間隔 -- noWait
public static WaitStrategy noWait()
指定時間間隔 -- fixedWait
public static WaitStrategy fixedWait(long sleepTime, @Nonnull TimeUnit timeUnit)
隨機間隔 -- randomWait
public static WaitStrategy randomWait(long maximumTime, @Nonnull TimeUnit timeUnit);
public static WaitStrategy randomWait(long minimumTime, @Nonnull TimeUnit
minimumTimeUnit, long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
線性遞增間隔 -- incrementingWait
public static WaitStrategy incrementingWait(long initialSleepTime,
@Nonnull TimeUnit initialSleepTimeUnit, long increment, @Nonnull
TimeUnit incrementTimeUnit)
這個方法設置了初始間隔和遞增步長
指數遞增間隔
public static WaitStrategy exponentialWait();
public static WaitStrategy exponentialWait(long maximumTime, @Nonnull
TimeUnit maximumTimeUnit);
public static WaitStrategy exponentialWait(long multiplier, long
maximumTime, @Nonnull TimeUnit maximumTimeUnit);
斐波那切數列遞增間隔 -- fibonacciWait
public static WaitStrategy fibonacciWait();
public static WaitStrategy fibonacciWait(long maximumTime, @Nonnull
TimeUnit maximumTimeUnit);
public static WaitStrategy fibonacciWait(long multiplier, long maximumTime
, @Nonnull TimeUnit maximumTimeUnit);
一旦拋出異常則間隔 -- exceptionWait
public static <T extends Throwable> WaitStrategy exceptionWait(@Nonnull
Class<T> exceptionClass, @Nonnull Function<T, Long> function)
他是 WaitStrategies 中最複雜的一個策略了,一旦上一次嘗試拋出了 exceptionClass 及其子類的異常,則調用回調方法 function,以其返回值作爲下一次嘗試前的時間間隔
合併多個策略 -- join
public static WaitStrategy join(WaitStrategy... waitStrategies)
阻塞策略 -- BlockStrategy
public interface BlockStrategy {
void block(long var1) throws InterruptedException;
}
這個策略指定了線程在本次嘗試後 sleep 多少毫秒
BlockStrategies
BlockStrategies 可以方便的創建 BlockStrategy
他只提供了一個 static 方法,一旦指定,則線程會在間隔時間內 sleep,否則不會
public static BlockStrategy threadSleepStrategy()
斷言 -- Predicate 接口
@FunctionalInterface
@GwtCompatible
public interface Predicate<T> extends java.util.function.Predicate<T> {
@CanIgnoreReturnValue
boolean apply(@Nullable T var1);
boolean equals(@Nullable Object var1);
default boolean test(@Nullable T input) {
return this.apply(input);
}
}
Predicate 接口最重要的方法是 apply 方法,返回是否需要拒絕嘗試,與 AttemptTimeLimiters 類似,Predicate 通常用 Predicates 來創建
Predicates
Predicates 提供了非常豐富的 static 方法集合,可以實現各種各樣的斷言,甚至是斷言的與或非組合
public static <T> Predicate<T> alwaysFalse();
public static <T> Predicate<T> isNull();
public static <T> Predicate<T> notNull();
public static <T> Predicate<T> not(Predicate<T> predicate);
public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T
>> components);
public static <T> Predicate<T> and(Predicate... components);
public static <T> Predicate<T> and(Predicate<? super T> first, Predicate
<? super T> second);
public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T
>> components);
public static <T> Predicate<T> or(Predicate... components);
public static <T> Predicate<T> or(Predicate<? super T> first, Predicate<?
super T> second);
public static <T> Predicate<T> equalTo(@NullableDecl T target);
public static Predicate<Object> instanceOf(Class<?> clazz);
public static Predicate<Class<?>> subtypeOf(Class<?> clazz);
public static <T> Predicate<T> in(Collection<? extends T> target);
public static <A, B> Predicate<A> compose(Predicate<B> predicate,
Function<A, ? extends B> function);
public static Predicate<CharSequence> containsPattern(String pattern);
public static Predicate<CharSequence> contains(Pattern pattern);
讓線程池中的線程也擁有重試功能 -- wrap
上面提到,Retryer 具有 call 方法,只要設置好重試策略,將相應方法封裝爲 Callable 接口的實現,傳入 Retryer 的 call 方法,即可按照預定的重試策略調用對應的方法
但是,如果我們的方法不是直接執行,而是需要放入線程池中呢?Retryer 提供了 wrap 接口實現將方法的重試策略封裝到一個 Callable 實現中,從而讓我們可以直接通過線程池調用:
public Retryer.RetryerCallable<V> wrap(Callable<V> callable)
三、Retryer 創建工具 -- RetryerBuilder
由於 Retryer 類構造方法參數較多,較爲複雜,而使用 RetryerBuilder 要更加簡潔明瞭,也是更加常用的方式.如下:
-
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() // 如果Callable結果爲null,繼續重試 .retryIfResult(Predicates.<Boolean>isNull()) // IOException,繼續重試 .retryIfExceptionOfType(IOException.class) // RuntimeException,繼續重試 .retryIfRuntimeException() // 指定重試策略,重試三次後停止 .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .build();