業(yè)務(wù)開發(fā)中為了保證銜接模塊的偶爾不確定性,需要做一些重試保障機(jī)制. 為了讓我們的重試代碼更優(yōu)雅簡(jiǎn)單, 這里介紹兩個(gè)方案:
Guava-Retry
和Spring-Retry
Guava-Retrying
Guava Retrying 是一個(gè)靈活方便的重試組件,包含了多種的重試策略牛哺,而且擴(kuò)展起來非常容易.
maven依賴
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
應(yīng)用示例
示例展示目標(biāo)接口在返回true時(shí)進(jìn)行邏輯重試, 重試次數(shù)為3
次, 重試時(shí)間間隔為每間隔2s執(zhí)行一次重試
.
public static void main(String[] args) {
Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
.retryIfException()
.retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
.build();
boolean withRetry = delayRetry(retryer);
System.out.println("[RETRY-RESULT]: " + withRetry);
}
public static boolean delayRetry(Retryer<Boolean> retryer){
boolean result = false;
try {
result = retryer.call(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
System.out.println("[TIME-STAMP]:" + System.currentTimeMillis());
return true;
} catch (Exception e) {
throw new Exception(e);
}
}
});
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
}
return result;
}
示例輸出
[TIME-STAMP]:1521125204339
[TIME-STAMP]:1521125206340
[TIME-STAMP]:1521125208342
[RETRY-RESULT]: false
代碼解讀
RetryerBuilder用于構(gòu)造重試實(shí)例, 用于設(shè)置重試源(可以支持多個(gè)重試源)、重試次數(shù)、重試超時(shí)時(shí)間以及等待時(shí)間間隔等.
- retryIfException(): 設(shè)置異常重試源
- retryIfResult(Predicates.equalTo(true)): 設(shè)置自定義段元重試源, call方法返回true重試.
- withStopStrategy(StopStrategies.stopAfterAttempt(3)): 設(shè)置重試3次
- withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS)): 重試等待策略, 間隔2s重試一次.
策略說明
任務(wù)阻塞策略 (BlockStrategies)
通俗的講就是當(dāng)前任務(wù)執(zhí)行完,下次任務(wù)還沒開始這段時(shí)間做什么, 默認(rèn)策略為 BlockStrategies.THREAD_SLEEP_STRATEGY
也就是調(diào)用 Thread.sleep(sleepTime)
.
停止重試策略 (StopStrategy)
- stopAfterDelay(): 設(shè)定一個(gè)最長(zhǎng)允許的執(zhí)行時(shí)間; 比如設(shè)定最長(zhǎng)執(zhí)行10s, 無論任務(wù)執(zhí)行多少次, 只要重試的時(shí)候超出了最長(zhǎng)時(shí)間, 則任務(wù)終止并返回重試異常RetryException.
- neverStop(): 一直重試直到成功.
- stopAfterAttempt(): 設(shè)定最大重試次數(shù),超出最大重試次數(shù)則停止重試并返回重試異常.
重試間隔策略 (WaitStrategies)
- noWait(): 不等待策略
代碼示例: 異常直接重試4次
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(4))
.withWaitStrategy(WaitStrategies.noWait())
.build();
執(zhí)行輸出:
[TIME-STAMP]:1521196411381
[TIME-STAMP]:1521196411381
[TIME-STAMP]:1521196411381
[TIME-STAMP]:1521196411381
[RETRY-RESULT]: false
- exceptionWait(): 異常時(shí)長(zhǎng)等待策略
- fixedWait(): 固定等待時(shí)長(zhǎng)策略
代碼示例: 每隔2秒執(zhí)行一次重試
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
.build();
執(zhí)行輸出
[TIME-STAMP]:1521195170114
[TIME-STAMP]:1521195172119
[TIME-STAMP]:1521195174122
[RETRY-RESULT]: false
- randomWait(): 隨機(jī)等待時(shí)長(zhǎng)策略(可以提供一個(gè)最小和最大時(shí)長(zhǎng),等待時(shí)長(zhǎng)為其區(qū)間隨機(jī)值)
代碼示例: 隨機(jī)間隔0~2秒重試
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withWaitStrategy(WaitStrategies.randomWait(2, TimeUnit.SECONDS))
.build();
執(zhí)行輸出:
[TIME-STAMP]:1521601715654
[TIME-STAMP]:1521601717496
[TIME-STAMP]:1521601718763
[RETRY-RESULT]: false
- incrementingWait(): 遞增等待時(shí)長(zhǎng)策略(提供一個(gè)初始值和步長(zhǎng),等待時(shí)間隨重試次數(shù)增加而增加)
代碼示例: 首次間隔1s,以后每次增加3s重試. 時(shí)間維度為: initialSleepTime + increment * (attemptNumber - 1)
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(4))
// initialSleepTime:第一次到第二次嘗試的間隔, increment: 每增加一次嘗試,需要增加的時(shí)間間隔
.withWaitStrategy(WaitStrategies.incrementingWait(1, TimeUnit.SECONDS, 3, TimeUnit.SECONDS))
.build();
[TIME-STAMP]:1521194872168
[TIME-STAMP]:1521194873172
[TIME-STAMP]:1521194877173
[TIME-STAMP]:1521194884175
[RETRY-RESULT]: false
- fibonacciWait(): 斐波那契數(shù)列時(shí)長(zhǎng)間隔策略
fibonacciWait(long multiplier,long maximumTime,TimeUnit maximumTimeUnit); multiplier單位固定是ms, maximumTime最大等待時(shí)間.
代碼示例: 采用斐波那契數(shù)列時(shí)長(zhǎng)進(jìn)行重試
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(4))
// 斐波那契數(shù)列
.withWaitStrategy(WaitStrategies.fibonacciWait(5, TimeUnit.SECONDS))
.build();
執(zhí)行輸出
[TIME-STAMP]:1521195661799
[TIME-STAMP]:1521195661801
[TIME-STAMP]:1521195661802
[TIME-STAMP]:1521195661805
[RETRY-RESULT]: false
- exponentialWait(): 按照指數(shù)遞增(2的n次方)來等待, 各個(gè)參數(shù)含義與fibonacciWait相同.
代碼示例
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException().retryIfResult(Predicates.equalTo(true))
.withBlockStrategy(BlockStrategies.threadSleepStrategy())
.withStopStrategy(StopStrategies.stopAfterAttempt(4))
.withWaitStrategy(WaitStrategies.exponentialWait(100, 10, TimeUnit.SECONDS))
.build();
執(zhí)行輸出
[TIME-STAMP]:1521196302323
[TIME-STAMP]:1521196302527
[TIME-STAMP]:1521196302932
[TIME-STAMP]:1521196303736
[RETRY-RESULT]: false
Spring-Retry
spring-retry非常簡(jiǎn)單,在配置類加上
@EnableRetry
注解啟用spring-retry, 然后在需要失敗重試的方法加@Retryable
注解即可, spring-retry通過捕獲異常來觸發(fā)重試機(jī)制.
Maven依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
應(yīng)用示例
硬編碼方式
示例代碼
@Test
public void springRetry() throws Exception {
// 構(gòu)建重試模板實(shí)例
RetryTemplate retryTemplate = new RetryTemplate();
// 設(shè)置重試策略
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
// 設(shè)置退避策略
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(100);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
boolean withRetry = retryTemplate.execute(
// 重試行為
new RetryCallback<Boolean, Exception>() {
@Override
public Boolean doWithRetry(RetryContext retryContext) throws Exception {
System.out.println("[TIME-STAMP]:" + System.currentTimeMillis() + ", retry:" + retryContext.getRetryCount());
return sample(5);
}
},
// 多次重試無效后執(zhí)行邏輯
new RecoveryCallback<Boolean>() {
@Override
public Boolean recover(RetryContext retryContext) throws Exception {
System.out.println("[TIME-STAMP]:" + System.currentTimeMillis() + ", recover:" + retryContext.getRetryCount());
return false;
}
}
);
System.out.println("[RETRY-RESULT]: " + withRetry);
}
private boolean sample(int id) throws Exception {
if(id < 10){
throw new RuntimeException(String.valueOf(id));
}
return true;
}
程序輸出
[TIME-STAMP]:1521207239963, retry:0
[TIME-STAMP]:1521207240065, retry:1
[TIME-STAMP]:1521207240166, retry:2
[TIME-STAMP]:1521207240166, recover:3
[RETRY-RESULT]: false
上面示例中我們的sample()方法入?yún)?時(shí)輸出結(jié)果如上圖, 當(dāng)參數(shù)設(shè)置大于10時(shí)輸出結(jié)果如下.
[TIME-STAMP]:1521207481718, retry:0
[RETRY-RESULT]: true
重試策略
- NeverRetryPolicy: 執(zhí)行一次待執(zhí)行操作,如果出現(xiàn)異常不進(jìn)行重試.
- AlwaysRetryPolicy: 異常后一直重試直到成功.
- SimpleRetryPolicy: 對(duì)指定的異常進(jìn)行若干次重試,默認(rèn)情況下對(duì)Exception異常及其子類重試3次(默認(rèn)策略).
- CircuitBreakerRetryPolicy: 有個(gè)內(nèi)部類CircuitBreakerRetryContext, 斷路器重試上下文。提供過載保護(hù)的策略, 如果在時(shí)間間隔openTimeout內(nèi)炊甲,直接短路模狭,不允許重試嫂粟,只有超過間隔的才能重試.
- CompositeRetryPolicy: 用戶指定一組策略,隨后根據(jù)optimistic選項(xiàng)來確認(rèn)如何重試.
- ExceptionClassifierRetryPolicy: 根據(jù)產(chǎn)生的異常選擇重試策略
- ExpressionRetryPolicy: 擴(kuò)展自SimpleRetryPolicy, 在父類canRetry的基礎(chǔ)上加上對(duì)lastThrowable的的表達(dá)式判斷,符合特地表達(dá)式的異常才能重試.
- TimeoutRetryPolicy: 在執(zhí)行execute方法時(shí)從open操作開始到調(diào)用TimeoutRetryPolicy的canRetry方法這之間所經(jīng)過的時(shí)間,這段時(shí)間未超過TimeoutRetryPolicy定義的超時(shí)時(shí)間,那么執(zhí)行操作,否則拋出異常.
退避策略
- NoBackOffPolicy: 實(shí)現(xiàn)了空方法,因此采用次策略,重試不會(huì)等待读虏。這也是RetryTemplate采用的默認(rèn)退避(backOff)策略
- FixedBackOffPolicy: 在等待一段固定的時(shí)間后再進(jìn)行重試(默認(rèn)為1秒).
- UniformRandomBackOffPolicy: 均勻隨機(jī)退避策略,等待時(shí)間為:最小退避時(shí)間 + [0,最大退避時(shí)間 - 最小退避時(shí)間)間的一個(gè)隨機(jī)數(shù),如果最大退避時(shí)間等于最小退避時(shí)間那么等待時(shí)間為0
- ExponentialBackOffPolicy: 指數(shù)退避策略,每次等待時(shí)間為:等待時(shí)間 = 等待時(shí)間 * N 责静,即每次等待時(shí)間為上一次的N倍。如果等待時(shí)間超過最大等待時(shí)間盖桥,那么以后的等待時(shí)間為最大等待時(shí)間
- ExponentialRandomBackOffPolicy: 指數(shù)隨機(jī)策略
如果每次有重試需求的時(shí)候都寫一個(gè)RetryTemplate
太臃腫了,SpringRetry也提供了使用注解方式進(jìn)行重試操作.
注解方式
應(yīng)用示例
示例代碼
@Slf4j
@Service
@EnableRetry
public class SpringRetry {
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5))
public String withRetry(long id){
System.out.println("[TIME-STAMP]:" + System.currentTimeMillis() + ", id=" + id);
if(id < 10){
throw new IllegalArgumentException(String.valueOf(id));
}
return String.valueOf(id);
}
@Recover
public String withRecover(Exception exception, long id){
System.out.println("[TIME-STAMP]:" + System.currentTimeMillis() + ", id=" + id + ", withRecover");
return StringUtils.EMPTY;
}
}
執(zhí)行結(jié)果
[TIME-STAMP]:1521207829018, id=11
[TIME-STAMP]:1521207829018, id=8
[TIME-STAMP]:1521207831023, id=8
[TIME-STAMP]:1521207834027, id=8
[TIME-STAMP]:1521207834028, id=8, withRecover
注解說明
@EnableRetry: 在需要執(zhí)行重試的類上使用@EnableRetry,如果設(shè)置了proxyTargetClass=true(默認(rèn)值為false)表示使用CGLIB動(dòng)態(tài)代理
-
@Retryable: 注解需要被重試的方法
- value: 指定要重試的異常(默認(rèn)為空).
- include: 指定處理的異常類(默認(rèn)為空).
- exclude: 指定不需要處理的異常(默認(rèn)為空).
- maxAttempts: 最大重試次數(shù)(默認(rèn)3次)
- backoff: 重試等待策略(默認(rèn)使用@Backoff注解)
@Backoff:重試回退策略(立即重試還是等待一會(huì)再重試),不設(shè)置參數(shù)時(shí)默認(rèn)使用FixedBackOffPolicy,重試等待1000ms; 只設(shè)置delay()屬性時(shí),使用FixedBackOffPolicy,重試等待指定的毫秒數(shù); 當(dāng)設(shè)置delay()和maxDealy()屬性時(shí),重試等待在這兩個(gè)值之間均態(tài)分布;
@Recover: 用于方法上,用于@Retryable失敗時(shí)的"兜底"處理方法, @Recover注釋的方法第一入?yún)橐卦嚨漠惓?其他參數(shù)與@Retryable保持一致,返回值也要一樣,否則無法執(zhí)行灾螃!
-
@CircuitBreaker:用于方法,實(shí)現(xiàn)熔斷模式.
- include 指定處理的異常類。默認(rèn)為空
- exclude指定不需要處理的異常揩徊。默認(rèn)為空
- value指定要重試的異常睦焕。默認(rèn)為空
- maxAttempts 最大重試次數(shù)。默認(rèn)3次
- openTimeout 配置熔斷器打開的超時(shí)時(shí)間,默認(rèn)5s,當(dāng)超過openTimeout之后熔斷器電路變成半打開狀態(tài)(只要有一次重試成功,則閉合電路)
- resetTimeout 配置熔斷器重新閉合的超時(shí)時(shí)間,默認(rèn)20s,超過這個(gè)時(shí)間斷路器關(guān)閉