程序調用第三方接口可能會出現網絡抖動、超時等異常狀況,這時咱們一般會想到當是重試。咱們首先模擬一段業務邏輯,而後開始咱們重試代碼當編寫java
/** * 這個是須要執行的業務邏輯 * 定義了一個隨機數,當低於閾值的時候,拋出異常 * 調用方catch住異常後進行重試 */
private void doSomething() {
log.info("開始業務邏輯...");
int random = RandomUtils.nextInt(100);
log.info("隨機數爲:{}", random);
if (random < 90) {
log.info("隨機數低於閾值,準備重試");
throw new ServiceException("業務異常");
}
log.info("結束業務邏輯...");
}
複製代碼
最普通的重試邏輯,就是在調用方捕獲到異常後,再次調用業務邏輯方法(遞歸),直到成功。該方案簡單粗暴spring
public void normal(int count) {
if (count < 5) {
count += 1;
try {
doSomething();
} catch (Exception e) {
//此處能夠定時休眠一點時間再次重試
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//重試
normal(count);
}
}
}
複製代碼
Spring-retry是一個開源工具包,該工具把重試操做模板定製化,能夠設置重試策略和回退策略。同時重試執行實例保證線程安全,下面給出對應的示例代碼安全
/** * Spring提供的Retry機制 */
public void springRetry() {
// 構建重試模板實例
RetryTemplate retryTemplate = new RetryTemplate();
// 設置重試策略,主要設置重試次數和須要捕獲的異常
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
// 設置重試回退操做策略,主要設置重試間隔時間
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
//初始間隔
backOffPolicy.setInitialInterval(1000);
//最大間隔
backOffPolicy.setMaxInterval(10 * 1000L);
//遞增倍數(即下次間隔是上次的多少倍)
backOffPolicy.setMultiplier(2);
retryTemplate.setRetryPolicy(policy);
retryTemplate.setBackOffPolicy(backOffPolicy);
// 經過RetryCallback 重試回調實例包裝正常邏輯邏輯,第一次執行和重試執行執行的都是這段邏輯
final RetryCallback<Object, Exception> retryCallback = context -> {
log.info("開始重試,重試次數爲: {}", context.getRetryCount());
doSomething();
return null;
};
// 經過RecoveryCallback 重試流程正常結束或者達到重試上限後的退出恢復操做實例
final RecoveryCallback<Object> recoveryCallback = context -> {
log.info("執行重試結束依然失敗後的代碼");
return null;
};
try {
// 由retryTemplate 執行execute方法開始邏輯執行
retryTemplate.execute(retryCallback, recoveryCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
在上面的代碼基礎上,Spring提供了重試操做的註解,下面給出對應的示例代碼。使用該方式的時候須要注意如下兩點:bash
(1) 須要在入口類上添加@EnableRetry網絡
(2) @Retryable只能出如今最外層的方法,同一個類中,當某個方法調用加類該註解的方法時,重試不生效數據結構
/**
* Spring提供的Retry機制
*/
@Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
public void springRetryAnnotation() {
doSomething();
}
/**
* 重試失敗後調用的方法(注意,需跟重處理方法在同一個類中)
*/
@Recover
public void recover(ServiceException e) {
log.info("執行重試結束依然失敗後的代碼");
}
複製代碼
SpringRetry版本只能對異常進行重試,對於自定義對數據結構不能支持,若是有這方面需求的化,能夠考慮用GuavaRetry進行重試,具體示例代碼以下(SpringTime默認不集成,使用時須要手動添加maven依賴)dom
/** * guava提供的retry機制 */
public void guavaRetry() {
//設置重試5次,一樣能夠設置重試超時時間
StopStrategy stopStrategy = StopStrategies.stopAfterAttempt(5);
//設置每次重試間隔
WaitStrategy waitStrategy = WaitStrategies.exponentialWait(2, 10, TimeUnit.SECONDS);
Retryer<Void> retryer = RetryerBuilder.<Void>newBuilder().retryIfException().withStopStrategy(stopStrategy)
.withWaitStrategy(waitStrategy).build();
try {
retryer.call(() -> {
doSomething();
return null;
});
} catch (ExecutionException | RetryException e) {
e.printStackTrace();
}
}
複製代碼