日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用 guava-retrying 实现灵活的重试机制

發布時間:2025/3/16 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 guava-retrying 实现灵活的重试机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們的后端業務系統可能會出現接口調用失敗、網絡擁塞超時、任務執行失敗、系統錯誤等異常情況,需要進行重試操作。但某些場景下我們對重試有特殊要求,比如延遲重試、降頻重試等,此時自己編寫重試代碼會很繁瑣,在 Java 中,可以使用 guava-retrying 幫我們實現靈活的重試機制。

guava-retrying 簡介

guava-retrying 是一個線程安全的 Java 重試類庫,提供了一種通用方法去處理任意需要重試的代碼,可以方便靈活地控制重試次數、重試時機、重試頻率、停止時機等,并具有異常處理功能。

GitHub地址:https://github.com/rholder/guava-retrying

有意思的是,這個項目最初源于 Jean-Baptiste Nizet 在 guava 倉庫下的評論。

guava-retrying 入門

下面通過一個場景幫助大家快速入門 guava-retrying,再具體講解其更多用法。

作者在 GitHub 提供了入門代碼,先通過 maven 或 gradle 引入:

maven引入代碼:

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

gradle引入代碼:

compile "com.github.rholder:guava-retrying:2.0.0"

假定我們需要調用一個qps限制很低的第三方接口,如果調用失敗,需要依次在失敗后的第10s、30s、60s進行降頻重試。

如果不使用框架,實現邏輯大致如下:

// 調用接口 boolean result; AtomicInteger atomicInteger = new AtomicInteger(0); int sleepNum = 10000;while(!result && atomicInteger.get() < 4) {atomicInteger.incrementAndGet();result = thirdApi.invoke();Thread.sleep(sleepNum);sleepNum += sleepNum * atomicInteger.get(); }

雖然看起來代碼行數并不多,只需要自己定義計數器、計算休眠時間等,但是再考慮到異常處理、異步等情況,重試邏輯的代碼占整體代碼的比重太大了(真正的業務邏輯只有 thirdApi.invoke 對么?)。如果業務中多處需要重試,還要反復編寫類似的代碼,而這不應該是開發者關心的。

guava-retrying 為我們封裝了一套很好的通用重試方法,來試下用它實現上述邏輯:

Callable<Boolean> callable = () -> { return thirdApi.invoke(); // 業務邏輯 };// 定義重試器 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder().retryIfResult(Predicates.<Boolean>isNull()) // 如果結果為空則重試.retryIfExceptionOfType(IOException.class) // 發生IO異常則重試.retryIfRuntimeException() // 發生運行時異常則重試.withWaitStrategy(WaitStrategies.incrementingWait(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS)) // 等待.withStopStrategy(StopStrategies.stopAfterAttempt(4)) // 允許執行4次(首次執行 + 最多重試3次).build();try {retryer.call(callable); // 執行 } catch (RetryException | ExecutionException e) { // 重試次數超過閾值或被強制中斷e.printStackTrace(); }

分析上述代碼:

1. 首先定義了一個 Callable 任務,其中執行我們需要重試的業務邏輯。

2. 通過 RetryerBuilder 構造重試器,構造包含如下部分:

  • 重試條件 retryIfResult、retryIfExceptionOfType、retryIfRuntimeException

  • 重試等待策略(延遲)withWaitStrategy

  • 重試停止策略 withStopStrategy

  • 阻塞策略、超時限制、注冊重試監聽器(上述代碼未使用)

3. 通過 retryer.call 執行任務

4. 當重試次數超過設定值或者被強制中斷時,會拋出異常,需要捕獲處理

通過上述代碼我們定義了一個重試器來實現降頻重試機制。顯然這種方式相較自己實現重試來說具有如下優點:

1. 對代碼的侵入性更小

2. 更直觀,改動方便

3. 可復用重試器至多個任務(代碼段)

RetryerBuilder 方法介紹

RetryerBuilder 用于構造重試器,是整個 guava-retrying 庫的核心,決定了重試的行為,下面詳細介紹 RetryerBuilder 的方法。

通過 newBuilder 方法獲取 RetryerBuilder 實例,通過 build 方法構造 Retryer:

RetryerBuilder<V> newBuilder() Retryer<V> build()

可以通過下面的方法改變重試器的行為。

重試條件

1. 根據執行結果判斷是否重試 retryIfResult

RetryerBuilder<V> retryIfResult(@Nonnull Predicate<V> resultPredicate)

2. 發生異常時重試

// 發生任何異常都重試 retryIfException() // 發生 Runtime 異常都重試 RetryerBuilder<V> retryIfRuntimeException() // 發生指定 type 異常時重試 RetryerBuilder<V> retryIfExceptionOfType(@Nonnull Class<? extends Throwable> exceptionClass) // 匹配到指定類型異常時重試 RetryerBuilder<V> retryIfException(@Nonnull Predicate<Throwable> exceptionPredicate)

等待策略

等待策略可以控制重試的時間間隔,通過 withWaitStrategy 方法注冊等待策略:

RetryerBuilder<V> withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException

WaitStrategy 是等待策略接口,可通過 WaitStrategies 的方法生成該接口的策略實現類,共有7種策略:

1. FixedWaitStrategy:固定等待時長策略,比如每次重試等待5s

// 參數:等待時間,時間單位 WaitStrategy fixedWait(long sleepTime, @Nonnull TimeUnit timeUnit) throws IllegalStateException

2. RandomWaitStrategy:隨機等待時長策略,每次重試等待指定區間的隨機時長

// 參數:隨機上限,時間單位 WaitStrategy randomWait(long maximumTime, @Nonnull TimeUnit timeUnit) // 參數:隨機下限,下限時間單位,隨機上限,上限時間單位 WaitStrategy randomWait(long minimumTime,@Nonnull TimeUnit minimumTimeUnit, long maximumTime,@Nonnull TimeUnit maximumTimeUnit)

3. IncrementingWaitStrategy:遞增等待時長策略,指定初始等待值,然后重試間隔隨次數等差遞增,比如依次等待10s、30s、60s(遞增值為10)

// 參數:初始等待時長,初始值時間單位,遞增值,遞增值時間單位 WaitStrategy incrementingWait(long initialSleepTime,@Nonnull TimeUnit initialSleepTimeUnit, long increment,@Nonnull TimeUnit incrementTimeUnit)

4. ExponentialWaitStrategy:指數等待時長策略,指定初始值,然后每次重試間隔乘2(即間隔為2的冪次方),如依次等待 2s、6s、14s。可以設置最大等待時長,達到最大值后每次重試將等待最大時長。

// 無參數(默認初始值為1) WaitStrategy exponentialWait() // 參數:最大等待時長,最大等待時間單位(默認初始值為1) WaitStrategy exponentialWait(long maximumTime, @Nonnull TimeUnit maximumTimeUnit) // 參數:初始值,最大等待時長,最大等待時間單位 WaitStrategy exponentialWait(long multiplier, long maximumTime, @Nonnull TimeUnit maximumTimeUnit)

5. FibonacciWaitStrategy :斐波那契等待時長策略,類似指數等待時長策略,間隔時長為斐波那契數列。

// 無參數(默認初始值為1) WaitStrategy fibonacciWait() // 參數:最大等待時長,最大等待時間單位(默認初始值為1) WaitStrategy fibonacciWait(long maximumTime, @Nonnull TimeUnit maximumTimeUnit) // 參數:最大等待時長,最大等待時間單位(默認初始值為1) WaitStrategy fibonacciWait(long multiplier, long maximumTime, @Nonnull TimeUnit maximumTimeUnit)

6. ExceptionWaitStrategy:異常時長等待策略,根據出現的異常類型決定等待的時長

// 參數:異常類型,計算等待時長的函數 <T extends Throwable> WaitStrategy exceptionWait(@Nonnull Class<T> exceptionClass, @Nonnull Function<T, Long> function)

7. CompositeWaitStrategy :復合時長等待策略,可以組合多個等待策略,基本可以滿足所有等待時長的需求

// 參數:等待策略數組 WaitStrategy join(WaitStrategy... waitStrategies)

阻塞策略

阻塞策略控制當前重試結束至下次重試開始前的行為,通過 withBlockStrategy 方法注冊阻塞策略:

RetryerBuilder<V> withBlockStrategy(@Nonnull BlockStrategy blockStrategy) throws IllegalStateException

BlockStrategy 是等待策略接口,可通過 BlockStrategies 的方法生成實現類,默認只提供一種策略 ThreadSleepStrategy:

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

很好理解,除了睡眠,阻塞著啥也不干。

停止策略

停止策略決定了何時停止重試,比如限制次數、時間等,通過 withStopStrategy 方法注冊等待策略:

RetryerBuilder<V> withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException

可通過 StopStrategies 的方法生成 StopStrategy 接口的策略實現類,共有3種策略:

1. NeverStopStrategy:永不停止,直到重試成功

2. StopAfterAttemptStrategy:指定最多重試次數,超過次數拋出 RetryException 異常

3. StopAfterDelayStrategy:指定最長重試時間,超時則中斷當前任務執行且不再重試,并拋出 RetryException 異常

超時限制

通過 withAttemptTimeLimiter 方法為任務添加單次執行時間限制,超時則中斷執行,繼續重試。

RetryerBuilder<V> withAttemptTimeLimiter(@Nonnull AttemptTimeLimiter<V> attemptTimeLimiter)

默認提供了兩種 AttemptTimeLimiter:

  • NoAttemptTimeLimit:不限制執行時間

  • FixedAttemptTimeLimit:限制執行時間為固定值

  • 監聽器

    可以通過 withRetryListener 方法為重試器注冊***,每次重試結束后,會按注冊順序依次回調 Listener 的 onRetry 方法,可在其中獲取到當前執行的信息,比如重試次數等。

    示例代碼如下:

    import com.github.rholder.retry.Attempt; import com.github.rholder.retry.RetryListener;public class MyRetryListener<T> implements RetryListener {@Override public <T> void onRetry(Attempt<T> attempt) { // 第幾次重試,(注意:第一次重試其實是第一次調用) System.out.print("[retry]time=" + attempt.getAttemptNumber());// 距離第一次重試的延遲 System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());// 重試結果: 是異常終止, 還是正常返回 System.out.print(",hasException=" + attempt.hasException()); System.out.print(",hasResult=" + attempt.hasResult());// 是什么原因導致異常 if (attempt.hasException()) { System.out.print(",causeBy=" + attempt.getExceptionCause().toString());} else { // 正常返回時的結果 System.out.print(",result=" + attempt.getResult());}} }

    看下原理

    顧名思義,guava-retrying 依賴 guava 庫,如作者所說,源碼中大量依賴 guava 的 Predicates(斷言)來判斷是否繼續重試。

    通過方法、對象名也可以看出,該庫主要使用了策略模式、構造器模式和觀察者模式(Listener),對調用方非常友好。

    從哪兒開始執行任務就從哪兒開始看,直接打開 Retryer 類的 call 方法:

    public V call(Callable<V> callable) throws ExecutionException, RetryException { long startTime = System.nanoTime(); // 1. 記錄開始時間,用于后續的時間計算 for (int attemptNumber = 1; ; attemptNumber++) {Attempt<V> attempt; try {V result = attemptTimeLimiter.call(callable); // 2. 執行callable任務,得到attemptattempt = 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) { // 3. 如果有***,通知listener.onRetry(attempt);}if (!rejectionPredicate.apply(attempt)) { // 4. 如果執行callable出現異常,則返回異常的attempt return attempt.get();} if (stopStrategy.shouldStop(attempt)) { // 5. 根據停止策略判斷是否停止重試 throw new RetryException(attemptNumber, attempt); // 若停止,拋出異常} else { long sleepTime = waitStrategy.computeSleepTime(attempt); // 6. 根據等待策略計算休眠時間 try {blockStrategy.block(sleepTime); // 7. 根據阻塞策略決定休眠行為,默認為sleep} catch (InterruptedException e) {Thread.currentThread().interrupt(); throw new RetryException(attemptNumber, attempt);}}} }

    這個方法邏輯很清晰,可以結合作者的注釋閱讀,主要流程如下:

    1. 記錄開始時間,便于后續判斷是否超過限制時間

    2. 通過 attemptTimeLimiter 執行 callable 任務,得到 attempt。attempt 代表著每次執行,記錄了如執行結果、執行次數、距離第一次執行的延遲時間、異常原因等信息。

    • 如果 attemptTimeLimiter 是 NoAttemptTimeLimit,則直接調用 callable.call 執行。

    • 如果 attemptTimeLimiter 是 FixedAttemptTimeLimit,則調用 timeLimiter.callWithTimeout 限制執行時間。

    3. 通知監聽器,進行一些回調操作

    4. rejectionPredicate 默認為 alwaysFalse,如果執行 callable 出現異常,則 rejectionPredicate 會返回異常的 attempt

    rejectionPredicate = Predicates.or(rejectionPredicate, new ExceptionClassPredicate<V>(RuntimeException.class));

    5. 根據停止策略判斷是否停止重試,若停止,拋出 RetryException 異常表示最終重試失敗

    6. 根據等待策略計算休眠時間

    7. 根據阻塞策略決定休眠行為,默認為 Thread.sleep(躺著啥也不干)

    就是這樣,該庫能夠實現靈活的重試,并不復雜,有興趣的同學可以去看下源碼~

    有道無術,術可成;有術無道,止于術

    歡迎大家關注Java之道公眾號

    好文章,我在看??

    總結

    以上是生活随笔為你收集整理的使用 guava-retrying 实现灵活的重试机制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。