1、前言
在項目中,我們經常會遇到網絡波動,或者調用第三方接口偶爾異常的情況。為了保證資料的可用性、程式的健壯性。我們常常加入重試的操作,多請求幾次,若幹次請求之後,如果還是失敗,才算是真正的失敗。成熟的架構上都有展現,比如dubbo的重試機制,spring全家桶自帶的重試、以及google出品的guava裡的重試機制等。今天以Spring架構中的重試機制為例分析、學習。
2、接入依賴
這裡的依賴引用的是目前最新的版本,和老版本裡的使用稍微有點差異,後面會詳細說明。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
3、初體驗
@Test
public void retry01(){
RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).fixedBackoff(2000).build();
retryTemplate.execute(new RetryCallback(){
@Override
public Object doWithRetry(RetryContext context) throws Throwable {
System.out.println(">>>>>>>>>>>>>>>>>retry test"+ DateUtils.formatDate(new Date()));
throw new RuntimeException("retry excption");
}
}, new RecoveryCallback(){
@Override
public Object recover(RetryContext context) throws Exception {
System.out.println(">>>>>>>>>>>>>>>>>retry test fail");
return null;
}
});
System.out.println("over");
}
4、結果
5、詳細分析
上述demo中,實作了每隔2s,重試一次,重試三次後再執行後處理(“retry test fail”),最終結束程式的運作。
RetryTemplate:通過重試模闆建構一個指定的模闆,通過設定請求的次數(maxAttempts),設定時間間 隔(fixedBackoff).
通過excute方法,并傳入參數執行方法。
RetryCallback:重試回調接口。指重複請求的業務邏輯需要實作RetryCallback.doWithRetry()方法。
RetryContext: 執行重試過程的上下文。
RecoveryCallback:重試結束依然沒有想要的結果,需要處理的業務接口。同樣需要實作RecoveryCallback.recover() 方法。
那麼問題來了,什麼情況下,才能觸發重試機制呢???
是異常,源碼裡給了注釋:
追蹤源碼:
從源碼中可以看到,他是通過傳遞的重試政策,判斷是否可以重試。該案例中沒有設定重試政策,故使用的是預設的重試政策(SimpleRetryPolicy)。
從spring框中可以看出,提供了11種重試政策:
預設的重試政策對于canRetry()的邏輯是這樣的:
從這裡我們就可看出是通過異常觸發重試政策的。
RetryTemplate 預設提供了簡單測重試機制,為了滿足多變的業務場景,我們可以定制屬于自己的重試架構,比如監聽器,不同的重試政策等。
6、擴充
Google提供的重試架構更靈活一些、更輕量級一些。可以支援線程池、以及多種條件觸發重試機制。這裡有一個小demo,就不做源碼的分析了。
@Test
public void retry02() {
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfResult(Predicates.equalTo(false))
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
try {
RetryerCallable<Boolean> wrap = retryer.wrap(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
System.out.println("dosomething>>>>>>>>>:" + DateUtils.formatDate(new Date()));
return false;
}
});
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<Boolean> submit = executorService.submit(wrap);
submit.get();
}catch (Exception e){
System.out.println("opver");
}
}