目錄
一、引入
二、快速上手
2.1、導入依賴
2.2、第一個示例
三、重試設置
3.1、重試條件設置
3.2、重試次數設置
3.3、重試間隔設置
一、引入
在平時的開發工作中,重試機制,是一個很重要的邏輯,比如調用其他服務時,如果出現超時,那么可以等100毫秒后再進行調用,或者出現異常時,需要重試;可以重試多次,也可以重試1次,這個都是可以在程序中設定的。
實現上面的邏輯,最簡單的方式就是使用for循環了,示例如下:
package cn.ganlixin.guava; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UseRetryer { private static final Logger log = LoggerFactory.getLogger(UseRetryer.class); @Test public void testUseFor() throws InterruptedException { int retryTimes = 3; // 重試次數 // 使用for循環控制重試 for (int i = 0; i < retryTimes; i++) { try { // 邏輯代碼,比如調用其他服務的接口 } catch (Exception e) { log.warn("第{}次運行出現異常,", i); // 如果出現異常,休眠100毫秒后重試(繼續for循環) Thread.sleep(100); continue; } // 執行成功,立即終止for循環。 break; } } }
上面的代碼,實現起來很簡單,也很好理解,我之前也是這樣做的。但是這樣做,其實是有一個弊端的,因為業務邏輯代碼,和一些控制操作(什么時候重試、隔多久重試)是寫在一塊的,比較亂。
本文所講的Guava Retryer可以解決上面的問題(還有其他優點)。
二、快速上手
2.1、導入依賴
Guava Retryer並不屬於Guava,他是一個基於Guava,提供重試機制的庫。
guava retryer的github網址:https://github.com/rholder/guava-retrying,包含有使用文檔。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency> <dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> <!-- 排除與guava重復的依賴 --> <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> <exclusion> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> </exclusion> </exclusions> </dependency>
2.2、第一個示例
先提及一下, retryer是一個重試器,可以對重試器進行設置,比如什么情況下重試、隔多久重試、重試多少次...
創建好重試器后,就可以使用重試器來進行執行指定的操作(實際的業務邏輯):
package cn.ganlixin.guava; import com.github.rholder.retry.*; import org.junit.Test; import java.time.LocalTime; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseRetryer { // 需要重試執行的操作 private Boolean testCode() { System.out.println(LocalTime.now()); // 強制拋出異常,觸發重試 if (true) { throw new RuntimeException("手動測試拋出異常"); } else { return false; } } @Test public void testFirstRetryer() throws ExecutionException, RetryException, InterruptedException { // 創建一個重試器,重試器執行的方法,返回值為Boolean類型 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() // 出現異常時,會重試 .retryIfException() // 失敗后,隔2秒后重試 .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS)) // 重試3次后,仍未成功,就不再重試 .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .build(); // 使用重試器,執行具體邏輯 Boolean res = retryer.call(() -> { return testCode(); }); } }
運行程序,輸出:
23:35:37.753 23:35:39.754 23:35:41.759 com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts. .......... Caused by: java.lang.RuntimeException: 手動測試拋出異常 ..........
三、重試設置
上面簡單介紹了怎么使用guava retryer,但是並沒有看出retryer的優點,下面對guava retryer的重試設置進行介紹,比如,什么時候重試,重試多少次,每次充重試間隔多久..然后就可以發現guava retryer在進行重試時,挺“優雅”的。
3.1、重試條件設置
什么時候執行重試,Guava Retryer有多個匹配方法:
package cn.ganlixin.guava; import com.github.rholder.retry.RetryException; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.StopStrategies; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import java.util.concurrent.ExecutionException; public class UseRetryer { @Test public void testWhenRetry() throws ExecutionException, RetryException { final Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 當運行結果大於10的時候,需要重試(表達式返回true,則重試) .retryIfResult(res -> { return res > 10; }) // 當拋出異常時,就會進行重試 .retryIfException() // 或者當拋出ArithmeticException異常時,進行重試 .retryIfExceptionOfType(ArithmeticException.class) // 如果拋出RuntimeException異常時,進行重試 .retryIfRuntimeException() // 捕獲到異常,對異常進行處理,返回true時,會進行重試 .retryIfException(exception -> { System.out.println("捕獲到" + exception); // 可以對異常信息、異常種類進行處理,決定是否需要重試 return StringUtils.contains(exception.getMessage(), "wrong"); }) // 重試3次 .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .build(); Integer res = retryer.call(() -> { throw new Exception(); }); System.out.println(res); } }
3.2、重試次數設置
前面說了什么情況下需要重試,那么需要重試多少次呢?
guava retryer中,如果沒有設置重試多少次,那么將會沒有休止地一直重試(死循環),所以建議一定要設置重試次數。
package cn.ganlixin.guava; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.StopStrategies; import org.junit.Test; public class UseRetryer { @Test public void testRetryStop() { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 拋出異常時重試 .retryIfException() // 設置什么時候停止重試 // 這里設置的是,執行3次都失敗了就停止重試 .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .build(); } }
設置停止策略,主要是使用StopStrategies類的靜態方法,如下:
// 不停止,一直重試(默認) StopStrategies.neverStop() // attemptNumber次失敗后,停止重試 StopStrategies.stopAfterAttempt(int attemptNumber) // 執行多久后停止(在未到停止的時間節點前,如果失敗,會一致重試) StopStrategies.stopAfterDelay(2, TimeUnit.SECONDS)
3.3、重試間隔設置
重試時間間隔設置,是指當執行失敗后,執行下一次重試,需要等多久,這就是重試間隔策略。
下面是一個簡單示例:
package cn.ganlixin.guava; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.WaitStrategies; import org.junit.Test; import java.util.concurrent.TimeUnit; public class UseRetryer { @Test public void testRetryInteval() { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 出現異常時重試 .retryIfException() // 設置重試時間間隔,此處設置固定時間間隔(2秒) .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS)) .build(); } }
和重試次數類似,重試間隔,主要使用WaitStrategies類的靜態方法進行設置(有很多方法),常用的如下:
// 失敗后沒有間隔,立即重試(默認) WaitStrategies.noWait() // 固定時間間隔(3秒) WaitStrategies.fixedWait(3, TimeUnit.SECONDS) // 設置第一次重試的時間間隔,然后后面每次重試時間間隔的增量 incrementingWait(long initialSleepTime, TimeUnit initialSleepTimeUnit, long increment, TimeUnit incrementTimeUnit) WaitStrategies.incrementingWait(2, TimeUnit.SECONDS, 1, TimeUnit.SECONDS) // 解釋:第一次重試間隔2秒,后面每次間隔時間是在前一個間隔上加1秒(就是3秒),再下一次是4秒