Guava-Retry實踐

前言
在實際業務中,有非常多場景需要我們進行重試操作,編碼中通過採用各種回調的方式來抽象重試的實現,但都不是那麼理想。通過簡單的調研,目前主要有Guava-Retry和Spring-Retry作爲三方庫比較流行,本章節將介紹Guava-Retry的實際應用。
Guave在github地址(https://github.com/rholder/guava-retrying),可以看到其已經有很長一段時間沒有更新維護,但這並不影響其正常使用,他已經足夠的穩定。其相比較於Spring-Retry在是否重試的判斷條件上有更多的選擇性,如類似的retryIf方法。
其主要接口及策略介紹:
Attempt:一次執行任務;
AttemptTimeLimiter:單次任務執行時間限制(如果單次任務執行超時,則終止執行當前任務);
BlockStrategies:任務阻塞策略(通俗的講就是當前任務執行完,下次任務還沒開始這段時間做什麼……),默認策略爲:BlockStrategies.THREAD_SLEEP_STRATEGY 也就是調用 Thread.sleep(sleepTime);
RetryException:重試異常;
RetryListener:自定義重試監聽器,可以用於異步記錄錯誤日誌;
StopStrategy:停止重試策略,提供三種:
  • StopAfterDelayStrategy :設定一個最長允許的執行時間;比如設定最長執行10s,無論任務執行次數,只要重試的時候超出了最長時間,則任務終止,並返回重試異常RetryException;
  • NeverStopStrategy :不停止,用於需要一直輪訓直到返回期望結果的情況;
  • StopAfterAttemptStrategy :設定最大重試次數,如果超出最大重試次數則停止重試,並返回重試異常;
WaitStrategy:等待時長策略(控制時間間隔),返回結果爲下次執行時長:
  • FixedWaitStrategy:固定等待時長策略;
  • RandomWaitStrategy:隨機等待時長策略(可以提供一個最小和最大時長,等待時長爲其區間隨機值)
  • IncrementingWaitStrategy:遞增等待時長策略(提供一個初始值和步長,等待時間隨重試次數增加而增加)
  • ExponentialWaitStrategy:指數等待時長策略;
  • FibonacciWaitStrategy :Fibonacci 等待時長策略;
  • ExceptionWaitStrategy :異常時長等待策略;
  • CompositeWaitStrategy :複合時長等待策略;

本章概要
1、根據結果判斷是否重試
2、根據異常判斷是否重試
3、重試策略——設定無限重試
4、重試策略——設定最大的重試次數
5、等待策略——設定重試等待固定時長策略
6、等待策略——設定重試等待時長固定增長策略
7、等待策略——設定重試等待時長按指數增長策略
8、等待策略——設定重試等待時長按斐波那契數列增長策略
9、等待策略——組合重試等待時長策略
10、重試監聽器——RetryListener實現重試過程細節處理

根據結果判斷是否重試
場景:如果返回值不是'good'則需要重試,返回值通過counter控制,直到等於5方會返回’good‘。

示例代碼:
private <T> T run(Retryer<T> retryer, Callable<T> callable) {
try {
return retryer.call(callable);
} catch (RetryException | ExecutionException e) {
LOGGER.trace(ExceptionUtils.getFullStackTrace(e));
LOGGER.warn(e.getMessage());
}
return null;
}

private Callable<String> callableWithResult() {
return new Callable<String>() {
int counter = 0;

public String call() throws Exception {
counter++;
LOGGER.info("do sth : {}", counter);
if (counter < 5) {
return "sorry";
}
return "good";
}
};
}

@Test
public void retryWithResult() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfResult(result -> !result.contains("good"))
.withStopStrategy(StopStrategies.neverStop())
.build();
run(retryer, callableWithResult());
}

打印:

根據異常判斷是否重試
場景:如果counter值小於5則拋出異常,等於5則正常返回停止重試;

示例代碼:
private Callable<String> callableWithException() {
return new Callable<String>() {
int counter = 0;

public String call() throws Exception {
counter++;
LOGGER.info("do sth : {}", counter);
if (counter < 5) {
throw new RuntimeException("sorry");
}
return "good";
}
};
}

@Test
public void retryWithException() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:


重試策略——設定無限重試
場景:在有異常情況下,無限重試,直到返回正常有效結果;

示例代碼:
@Test
public void retryNeverStop() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:


重試策略——設定最大的重試次數
場景:在有異常情況下,最多重試3次,如果超過3次則會拋出異常;

示例代碼:
@Test
public void retryStopAfterAttempt() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:


等待策略——設定重試等待固定時長策略
場景:設定每次重試等待間隔固定爲100ms;

示例代碼:
@Test
public void retryWaitFixStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:

基本每次的間隔維持在100ms。

等待策略——設定重試等待時長固定增長策略
場景:設定初始等待時長值,並設定固定增長步長,但不設定最大等待時長;

示例代碼:
@Test
public void retryWaitIncreaseStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.incrementingWait(200, TimeUnit.MILLISECONDS, 100, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:

可以看到,等待時長依次爲200ms、300ms、400ms、500ms,符合策略約定。

等待策略——設定重試等待時長按指數增長策略
場景:根據multiplier值按照指數級增長等待時長,並設定最大等待時長;

示例代碼:
@Test
public void retryWaitExponentialStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.exponentialWait(100, 1000, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:

可以看到,等待時長依次爲200ms、400ms、800ms、1000ms,符合策略約定。


等待策略——設定重試等待時長按斐波那契數列增長策略
場景:根據multiplier值按照斐波那契數列增長等待時長,並設定最大等待時長,斐波那契數列:1、1、2、3、5、8、13、21、34、……

示例代碼:
@Test
public void retryWaitFibonacciStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.fibonacciWait(100, 1000, TimeUnit.MILLISECONDS))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:

可以看到,等待時長依次爲100ms、100ms、200ms、300ms,符合策略約定。

等待策略——組合重試等待時長策略
場景:組合ExponentialWaitStrategyFixedWaitStrategy策略。

示例代碼:
@Test
public void retryWaitJoinStrategy() {
Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withWaitStrategy(WaitStrategies.join(WaitStrategies.exponentialWait(25, 500, TimeUnit.MILLISECONDS)
, WaitStrategies.fixedWait(50, TimeUnit.MILLISECONDS)))
.build();
LOGGER.info("result : " + run(retryer, callableWithException()));
}
打印:

可以看到,等待時長依次爲100(50+50)ms、150(100+50)ms、250(200+50)ms、450(400+50)ms,符合策略約定。

監聽器——RetryListener實現重試過程細節處理
場景:定義兩個監聽器,分別打印重試過程中的細節,未來可更多的用於異步日誌記錄,亦或是特殊處理。

示例代碼:
private RetryListener myRetryListener() {
return new RetryListener() {
@Override
public <T> void onRetry(Attempt<T> attempt) {
// 第幾次重試,(注意:第一次重試其實是第一次調用)
LOGGER.info("[retry]time=" + attempt.getAttemptNumber());

// 距離第一次重試的延遲
LOGGER.info(",delay=" + attempt.getDelaySinceFirstAttempt());

// 重試結果: 是異常終止, 還是正常返回
LOGGER.info(",hasException=" + attempt.hasException());
LOGGER.info(",hasResult=" + attempt.hasResult());

// 是什麼原因導致異常
if (attempt.hasException()) {
LOGGER.info(",causeBy=" + attempt.getExceptionCause().toString());
} else {
// 正常返回時的結果
LOGGER.info(",result=" + attempt.getResult());
}

// 增加了額外的異常處理代碼
try {
T result = attempt.get();
LOGGER.info(",rude get=" + result);
} catch (ExecutionException e) {
LOGGER.error("this attempt produce exception." + e.getCause().toString());
}
}
};
}

private RetryListener myRetryListener2() {
return new RetryListener() {
@Override
public <T> void onRetry(Attempt<T> attempt) {
LOGGER.info("myRetryListener2 : [retry]time=" + attempt.getAttemptNumber());
}
};
}

private <T> T runWithFixRetryAndListener(Callable<T> callable) {
Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.neverStop())
.withRetryListener(myRetryListener())
.withRetryListener(myRetryListener2())
.build();
return run(retryer, callable);
}

@Test
public void retryWithRetryListener() {
LOGGER.info("result : " + runWithFixRetryAndListener(callableWithException()));
}
打印:

RetryListener會根據註冊順序執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章