Java利用Guava?Retry實(shí)現(xiàn)重處理
在日常開發(fā)中,尤其是在微服務(wù)盛行的時代下,我們在調(diào)用外部接口時,經(jīng)常會因?yàn)榈谌浇涌诔瑫r、限流等問題從而造成接口調(diào)用失敗,那么此時我們通常會對接口進(jìn)行重試,那么問題來了,如何重試呢?該重試幾次呢?如果要設(shè)置重試時間超過多長時間后還不成功就不重試了該怎么做呢?所幸guava-retrying為我們提供了強(qiáng)大而簡單易用的重試框架guava-retrying。
guava-retrying是谷歌的Guava庫的一個小擴(kuò)展,允許為任意函數(shù)調(diào)用創(chuàng)建可配置的重試策略,比如與正常運(yùn)行時間不穩(wěn)定的遠(yuǎn)程服務(wù)對話的函數(shù)調(diào)用。
一、pom依賴
<dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency>
二、使用示例
我們可以通過RetryerBuilder來構(gòu)造一個重試器,通過RetryerBuilder可以設(shè)置什么時候需要重試(即重試時機(jī))、停止重試策略、失敗等待時間間隔策略、任務(wù)執(zhí)行時長限制策略
先看一個簡單的例子:
private int invokeCount = 0; public int realAction(int num) { invokeCount++; System.out.println(String.format("當(dāng)前執(zhí)行第 %d 次,num:%d", invokeCount, num)); if (num <= 0) { throw new IllegalArgumentException(); } return num; } @Test public void guavaRetryTest001() { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 非正數(shù)進(jìn)行重試 .retryIfRuntimeException() // 偶數(shù)則進(jìn)行重試 .retryIfResult(result -> result % 2 == 0) // 設(shè)置最大執(zhí)行次數(shù)3次 .withStopStrategy(StopStrategies.stopAfterAttempt(3)).build(); try { invokeCount=0; retryer.call(() -> realAction(0)); } catch (Exception e) { System.out.println("執(zhí)行0,異常:" + e.getMessage()); } try { invokeCount=0; retryer.call(() -> realAction(1)); } catch (Exception e) { System.out.println("執(zhí)行1,異常:" + e.getMessage()); } try { invokeCount=0; retryer.call(() -> realAction(2)); } catch (Exception e) { System.out.println("執(zhí)行2,異常:" + e.getMessage()); } }
輸出:
當(dāng)前執(zhí)行第 1 次,num:0
當(dāng)前執(zhí)行第 2 次,num:0
當(dāng)前執(zhí)行第 3 次,num:0
執(zhí)行0,異常:Retrying failed to complete successfully after 3 attempts.
當(dāng)前執(zhí)行第 1 次,num:1
當(dāng)前執(zhí)行第 1 次,num:2
當(dāng)前執(zhí)行第 2 次,num:2
當(dāng)前執(zhí)行第 3 次,num:2
執(zhí)行2,異常:Retrying failed to complete successfully after 3 attempts.
三、重試時機(jī)
RetryerBuilder的retryIfXXX()方法用來設(shè)置**在什么情況下進(jìn)行重試,總體上可以分為根據(jù)執(zhí)行異常進(jìn)行重試和根據(jù)方法執(zhí)行結(jié)果進(jìn)行重試兩類。關(guān)注公眾號:“碼猿技術(shù)專欄”,回復(fù)關(guān)鍵詞:“1111” 獲取阿里內(nèi)部Java調(diào)優(yōu)手冊
1. 根據(jù)異常進(jìn)行重試
2. 根據(jù)返回結(jié)果進(jìn)行重試
retryIfResult(@Nonnull Predicate resultPredicate) 這個比較簡單,當(dāng)我們傳入的resultPredicate返回true時則進(jìn)行重試
四、停止重試策略StopStrategy
停止重試策略用來決定什么時候不進(jìn)行重試,其接口com.github.rholder.retry.StopStrategy,停止重試策略的實(shí)現(xiàn)類均在com.github.rholder.retry.StopStrategies中,它是一個策略工廠類。
public interface StopStrategy { /** * Returns <code>true</code> if the retryer should stop retrying. * * @param failedAttempt the previous failed {@code Attempt} * @return <code>true</code> if the retryer must stop, <code>false</code> otherwise */ boolean shouldStop(Attempt failedAttempt); }
1. NeverStopStrategy
此策略將永遠(yuǎn)重試,永不停止,查看其實(shí)現(xiàn)類,直接返回了false:
@Override public boolean shouldStop(Attempt failedAttempt) { return false; }
2. StopAfterAttemptStrategy
當(dāng)執(zhí)行次數(shù)到達(dá)指定次數(shù)之后停止重試,查看其實(shí)現(xiàn)類:
private static final class StopAfterAttemptStrategy implements StopStrategy { private final int maxAttemptNumber; public StopAfterAttemptStrategy(int maxAttemptNumber) { Preconditions.checkArgument(maxAttemptNumber >= 1, "maxAttemptNumber must be >= 1 but is %d", maxAttemptNumber); this.maxAttemptNumber = maxAttemptNumber; } @Override public boolean shouldStop(Attempt failedAttempt) { return failedAttempt.getAttemptNumber() >= maxAttemptNumber; } }
3. StopAfterDelayStrategy
當(dāng)距離方法的第一次執(zhí)行超出了指定的delay時間時停止,也就是說一直進(jìn)行重試,當(dāng)進(jìn)行下一次重試的時候會判斷從第一次執(zhí)行到現(xiàn)在的所消耗的時間是否超過了這里指定的delay時間,查看其實(shí)現(xiàn):
private static final class StopAfterAttemptStrategy implements StopStrategy { private final int maxAttemptNumber; public StopAfterAttemptStrategy(int maxAttemptNumber) { Preconditions.checkArgument(maxAttemptNumber >= 1, "maxAttemptNumber must be >= 1 but is %d", maxAttemptNumber); this.maxAttemptNumber = maxAttemptNumber; } @Override public boolean shouldStop(Attempt failedAttempt) { return failedAttempt.getAttemptNumber() >= maxAttemptNumber; } }
五、重試間隔策略、重試阻塞策略
這兩個策略放在一起說,它們合起來的作用就是用來控制重試任務(wù)之間的間隔時間,以及如何任務(wù)在等待時間間隔時如何阻塞。也就是說WaitStrategy決定了重試任務(wù)等待多久后進(jìn)行下一次任務(wù)的執(zhí)行,BlockStrategy用來決定任務(wù)如何等待。它們兩的策略工廠分別為com.github.rholder.retry.WaitStrategies和BlockStrategies。
1.BlockStrategy
(1) ThreadSleepStrategy
這個是BlockStrategies,決定如何阻塞任務(wù),其主要就是通過**Thread.sleep()**來進(jìn)行阻塞的,查看其實(shí)現(xiàn):
@Immutable private static class ThreadSleepStrategy implements BlockStrategy { @Override public void block(long sleepTime) throws InterruptedException { Thread.sleep(sleepTime); } }
2. WaitStrategy
(1) IncrementingWaitStrategy
該策略在決定任務(wù)間隔時間時,返回的是一個遞增的間隔時間,即每次任務(wù)重試間隔時間逐步遞增,越來越長,查看其實(shí)現(xiàn):
private static final class IncrementingWaitStrategy implements WaitStrategy { private final long initialSleepTime; private final long increment; public IncrementingWaitStrategy(long initialSleepTime, long increment) { Preconditions.checkArgument(initialSleepTime >= 0L, "initialSleepTime must be >= 0 but is %d", initialSleepTime); this.initialSleepTime = initialSleepTime; this.increment = increment; } @Override public long computeSleepTime(Attempt failedAttempt) { long result = initialSleepTime + (increment * (failedAttempt.getAttemptNumber() - 1)); return result >= 0L ? result : 0L; } }
該策略輸入一個起始間隔時間值和一個遞增步長,然后每次等待的時長都遞增increment時長。
(2) RandomWaitStrategy
顧名思義,返回一個隨機(jī)的間隔時長,我們需要傳入的就是一個最小間隔和最大間隔,然后隨機(jī)返回介于兩者之間的一個間隔時長,其實(shí)現(xiàn)為:
private static final class RandomWaitStrategy implements WaitStrategy { private static final Random RANDOM = new Random(); private final long minimum; private final long maximum; public RandomWaitStrategy(long minimum, long maximum) { Preconditions.checkArgument(minimum >= 0, "minimum must be >= 0 but is %d", minimum); Preconditions.checkArgument(maximum > minimum, "maximum must be > minimum but maximum is %d and minimum is", maximum, minimum); this.minimum = minimum; this.maximum = maximum; } @Override public long computeSleepTime(Attempt failedAttempt) { long t = Math.abs(RANDOM.nextLong()) % (maximum - minimum); return t + minimum; } }
(3) FixedWaitStrategy
該策略是返回一個固定時長的重試間隔。查看其實(shí)現(xiàn):
private static final class FixedWaitStrategy implements WaitStrategy { private final long sleepTime; public FixedWaitStrategy(long sleepTime) { Preconditions.checkArgument(sleepTime >= 0L, "sleepTime must be >= 0 but is %d", sleepTime); this.sleepTime = sleepTime; } @Override public long computeSleepTime(Attempt failedAttempt) { return sleepTime; } }
() ExceptionWaitStrategy
該策略是由方法執(zhí)行異常來決定是否重試任務(wù)之間進(jìn)行間隔等待,以及間隔多久。
private static final class ExceptionWaitStrategy<T extends Throwable> implements WaitStrategy { private final Class<T> exceptionClass; private final Function<T, Long> function; public ExceptionWaitStrategy(@Nonnull Class<T> exceptionClass, @Nonnull Function<T, Long> function) { this.exceptionClass = exceptionClass; this.function = function; } @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ConstantConditions", "unchecked"}) @Override public long computeSleepTime(Attempt lastAttempt) { if (lastAttempt.hasException()) { Throwable cause = lastAttempt.getExceptionCause(); if (exceptionClass.isAssignableFrom(cause.getClass())) { return function.apply((T) cause); } } return 0L; } }
(5) CompositeWaitStrategy
這個沒啥好說的,顧名思義,就是一個策略的組合,你可以傳入多個WaitStrategy,然后所有WaitStrategy返回的間隔時長相加就是最終的間隔時間。查看其實(shí)現(xiàn):
private static final class CompositeWaitStrategy implements WaitStrategy { private final List<WaitStrategy> waitStrategies; public CompositeWaitStrategy(List<WaitStrategy> waitStrategies) { Preconditions.checkState(!waitStrategies.isEmpty(), "Need at least one wait strategy"); this.waitStrategies = waitStrategies; } @Override public long computeSleepTime(Attempt failedAttempt) { long waitTime = 0L; for (WaitStrategy waitStrategy : waitStrategies) { waitTime += waitStrategy.computeSleepTime(failedAttempt); } return waitTime; } }
(6) FibonacciWaitStrategy
這個策略與IncrementingWaitStrategy有點(diǎn)相似,間隔時間都是隨著重試次數(shù)的增加而遞增的,不同的是,F(xiàn)ibonacciWaitStrategy是按照斐波那契數(shù)列來進(jìn)行計(jì)算的,使用這個策略時,我們需要傳入一個乘數(shù)因子和最大間隔時長,其實(shí)現(xiàn)就不貼了
(7) ExponentialWaitStrategy
這個與IncrementingWaitStrategy、FibonacciWaitStrategy也類似,間隔時間都是隨著重試次數(shù)的增加而遞增的,但是該策略的遞增是呈指數(shù)級遞增。查看其實(shí)現(xiàn):
private static final class ExponentialWaitStrategy implements WaitStrategy { private final long multiplier; private final long maximumWait; public ExponentialWaitStrategy(long multiplier, long maximumWait) { Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", multiplier); Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", maximumWait); Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", multiplier); this.multiplier = multiplier; this.maximumWait = maximumWait; } @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; } }
六、重試監(jiān)聽器RetryListener
當(dāng)發(fā)生重試時,將會調(diào)用RetryListener的onRetry方法,此時我們可以進(jìn)行比如記錄日志等額外操作。
public int realAction(int num) { if (num <= 0) { throw new IllegalArgumentException(); } return num; } @Test public void guavaRetryTest001() throws ExecutionException, RetryException { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder().retryIfException() .withRetryListener(new MyRetryListener()) // 設(shè)置最大執(zhí)行次數(shù)3次 .withStopStrategy(StopStrategies.stopAfterAttempt(3)).build(); retryer.call(() -> realAction(0)); } private static class MyRetryListener implements RetryListener { @Override public <V> void onRetry(Attempt<V> attempt) { System.out.println("第" + attempt.getAttemptNumber() + "次執(zhí)行"); } }
輸出:
第1次執(zhí)行
第2次執(zhí)行
第3次執(zhí)行
七、重試原理
其實(shí)到這一步之后,實(shí)現(xiàn)原理大概就很清楚了,就是由上述各種策略配合從而達(dá)到了非常靈活的重試機(jī)制。在這之前我們看一個上面沒說的東東-Attempt:
public interface Attempt<V> { public V get() throws ExecutionException; public boolean hasResult(); public boolean hasException(); public V getResult() throws IllegalStateException; public Throwable getExceptionCause() throws IllegalStateException; public long getAttemptNumber(); public long getDelaySinceFirstAttempt(); }
通過接口方法可以知道Attempt這個類包含了任務(wù)執(zhí)行次數(shù)、任務(wù)執(zhí)行異常、任務(wù)執(zhí)行結(jié)果、以及首次執(zhí)行任務(wù)至今的時間間隔,那么我們后續(xù)的不管重試時機(jī)、還是其他策略都是根據(jù)此值來決定。
接下來看關(guān)鍵執(zhí)行入口Retryer##call:
public V call(Callable<V> callable) throws ExecutionException, RetryException { long startTime = System.nanoTime(); // 執(zhí)行次數(shù)從1開始 for (int attemptNumber = 1; ; attemptNumber++) { Attempt<V> attempt; try { // 嘗試執(zhí)行 V result = attemptTimeLimiter.call(callable); // 執(zhí)行成功則將結(jié)果封裝為ResultAttempt attempt = new Retryer.ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } catch (Throwable t) { // 執(zhí)行異常則將結(jié)果封裝為ExceptionAttempt attempt = new Retryer.ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); } // 這里將執(zhí)行結(jié)果傳給RetryListener做一些額外事情 for (RetryListener listener : listeners) { listener.onRetry(attempt); } // 這個就是決定是否要進(jìn)行重試的地方,如果不進(jìn)行重試直接返回結(jié)果,執(zhí)行成功就返回結(jié)果,執(zhí)行失敗就返回異常 if (!rejectionPredicate.apply(attempt)) { return attempt.get(); } // 到這里,說明需要進(jìn)行重試,則此時先決定是否到達(dá)了停止重試的時機(jī),如果到達(dá)了則直接返回異常 if (stopStrategy.shouldStop(attempt)) { throw new RetryException(attemptNumber, attempt); } else { // 決定重試時間間隔 long sleepTime = waitStrategy.computeSleepTime(attempt); try { // 進(jìn)行阻塞 blockStrategy.block(sleepTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RetryException(attemptNumber, attempt); } } }
八、總結(jié)
通篇下來可以看到其實(shí)核心實(shí)現(xiàn)并不難,但是此框架通過建造者模式和策略模式組合運(yùn)用,提供了十分清晰明了且靈活的重試機(jī)制,其設(shè)計(jì)思路還是值得借鑒學(xué)習(xí)!
以上就是Java利用Guava Retry實(shí)現(xiàn)重處理的詳細(xì)內(nèi)容,更多關(guān)于Java Guava Retry的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java的Hibernate框架中用于操作數(shù)據(jù)庫的HQL語句講解
這篇文章主要介紹了Java的Hibernate框架中用于操作數(shù)據(jù)庫的HQL語句講解,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2016-01-01Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之稀疏矩陣定義與用法,結(jié)合實(shí)例形式分析了java稀疏矩陣的定義、運(yùn)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01Java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(使用數(shù)據(jù)庫)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),使用數(shù)據(jù)庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式
這篇文章主要介紹了詳解Mybatis多參數(shù)傳遞入?yún)⑺姆N處理方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Spring Boot利用Thymeleaf發(fā)送Email的方法教程
spring Boot默認(rèn)就是使用thymeleaf模板引擎的,下面這篇文章主要給大家介紹了關(guān)于在Spring Boot中利用Thymeleaf發(fā)送Email的方法教程,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-08-08