該項目爲Spring應用程序提供聲明式重試支持,它用於Spring Batch、Spring Integration、Apache Hadoop的Spring(以及其餘),命令式重試也支持顯式使用。web
@Configuration @EnableRetry public class Application { @Bean public Service service() { return new Service(); } } @Service class Service { @Retryable(RemoteAccessException.class) public void service() { // ... do something } @Recover public void recover(RemoteAccessException e) { // ... panic } }
調用service
方法,若是它因爲RemoteAccessException
失敗,那麼它將重試(默認狀況下最多三次),若是繼續失敗,則執行recover
方法,@Retryable
註解屬性中有各類選項,用於包含和排除異常類型、限制重試次數和回退策略。spring
使用上面顯示的@Retryable
註解應用重試處理的聲明式方法對AOP類有一個額外的運行時依賴,有關如何解決項目中的這種依賴關係的詳細信息,請參閱下面的「重試代理的Java配置」部分。數據庫
RetryTemplate template = RetryTemplate.builder() .maxAttempts(3) .fixedBackoff(1000) .retryOn(RemoteAccessException.class) .build(); template.execute(ctx -> { // ... do something });
舊版本:參見RetryTemplate
部分中的示例。express
要求Java 1.7和Maven 3.0.5(或更高)。緩存
$ mvn install
爲了使處理更健壯、更不容易失敗,有時自動重試失敗的操做會有所幫助,以防它在隨後的嘗試中可能成功,易受這種處理影響的錯誤本質上是暫時的。例如,對web服務或RMI服務的遠程調用因爲網絡故障或數據庫更新中的DeadLockLoserException
而失敗,可能在短期的等待後自行解決,要自動化這些操做的重試,Spring Retry具備RetryOperations
策略,RetryOperations
接口看起來是這樣的:網絡
public interface RetryOperations { <T> T execute(RetryCallback<T> retryCallback) throws Exception; <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception; <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception, ExhaustedRetryException; <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState) throws Exception; }
基本回調是一個簡單的接口,容許你插入一些要重試的業務邏輯:app
public interface RetryCallback<T> { T doWithRetry(RetryContext context) throws Throwable; }
執行回調,若是它失敗(經過拋出Exception
),將重試它,直到成功或實現決定停止爲止。RetryOperations
接口中有許多重載的execute
方法,它們處理各類用例,以便在全部重試嘗試都耗盡時進行恢復,還有重試狀態,這容許客戶端和實如今調用之間存儲信息(稍後將詳細介紹)。dom
RetryOperations
最簡單的通用實現是RetryTemplate
,它能夠這樣用:ide
RetryTemplate template = new RetryTemplate(); TimeoutRetryPolicy policy = new TimeoutRetryPolicy(); policy.setTimeout(30000L); template.setRetryPolicy(policy); Foo result = template.execute(new RetryCallback<Foo>() { public Foo doWithRetry(RetryContext context) { // Do stuff that might fail, e.g. webservice operation return result; } });
在本例中,咱們執行一個web服務調用並將結果返回給用戶,若是該調用失敗,則重試該調用,直到達到超時爲止。函數
從1.3版開始,RetryTemplate
的流暢配置也可用:
RetryTemplate.builder() .maxAttempts(10) .exponentialBackoff(100, 2, 10000) .retryOn(IOException.class) .traversingCauses() .build(); RetryTemplate.builder() .fixedBackoff(10) .withinMillis(3000) .build(); RetryTemplate.builder() .infiniteRetry() .retryOn(IOException.class) .uniformRandomBackoff(1000, 3000) .build();
RetryCallback
的方法參數是一個RetryContext
,許多回調將簡單地忽略上下文,可是若是須要,它能夠做爲一個屬性包來存儲迭代期間的數據。
若是同一個線程中正在進行嵌套重試,則RetryContext
將具備父上下文,父上下文有時對於存儲須要在執行的調用之間共享的數據頗有用。
當重試耗盡時,RetryOperations
能夠將控制權傳遞給另外一個回調RecoveryCallback
,要使用此功能,客戶端只需將回調函數一塊兒傳遞給相同的方法,例如:
Foo foo = template.execute(new RetryCallback<Foo>() { public Foo doWithRetry(RetryContext context) { // business logic here }, new RecoveryCallback<Foo>() { Foo recover(RetryContext context) throws Exception { // recover logic here } });
若是在模板決定停止以前業務邏輯沒有成功,那麼客戶端就有機會經過恢復回調執行一些替代處理。
在最簡單的狀況下,重試只是一個while
循環,RetryTemplate
能夠一直嘗試,直到成功或失敗。RetryContext
包含一些狀態來決定是重試仍是停止,可是這個狀態位於堆棧上,不須要將它存儲在全局的任何位置,所以咱們將此稱爲無狀態重試。無狀態重試和有狀態重試之間的區別包含在RetryPolicy
的實現中(RetryTemplate
能夠同時處理這兩種狀況),在無狀態重試中,回調老是在重試失敗時在同一個線程中執行。
若是失敗致使事務性資源無效,則須要特別考慮,這並不適用於簡單的遠程調用,由於(一般)沒有事務資源,但有時確實適用於數據庫更新,尤爲是在使用Hibernate時。在這種狀況下,只有當即從新拋出調用失敗的異常纔有意義,以便事務能夠回滾並啓動一個新的有效的事務。
在這些狀況下,無狀態重試是不夠的,由於從新拋出和回滾必然會離開RetryOperations.execute()
方法,並可能丟失堆棧上的上下文。爲了不丟失它,咱們必須引入一種存儲策略,將它從堆棧中取出並(至少)放入堆存儲中,爲此,Spring Retry提供了一種存儲策略RetryContextCache
,能夠將其注入RetryTemplate
。RetryContextCache
的默認實如今內存中,使用一個簡單的Map
,它有一個嚴格執行的最大容量,以免內存泄漏,但它沒有任何高級緩存功能,如生存時間。若是須要,應該考慮注入具備這些特性的Map
,在集羣環境中對多個進程的高級使用可能還會考慮使用某種集羣緩存實現RetryContextCache
(不過,即便在集羣環境中,這也多是多餘的)。
RetryOperations
的部分職責是在失敗的操做在新執行中返回時識別它們(一般封裝在新事務中),爲了促進這一點,Spring Retry提供了RetryState
抽象,這與RetryOperations
中的特殊execute
方法一塊兒工做。
識別失敗操做的方法是跨重試的多個調用標識狀態,要標識狀態,用戶能夠提供RetryState
對象,該對象負責返回標識該項的惟一鍵,標識符用做RetryContextCache
中的鍵。
在RetryState
返回的鍵中實現Object.equals()
和Object.hashCode()
要很是當心,最好的建議是使用業務鍵來標識項,對於JMS消息,可使用消息ID。
當重試耗盡時,還能夠選擇以另外一種方式處理失敗的項,而不是調用RetryCallback
(如今假定極可能會失敗),就像在無狀態的狀況下同樣,這個選項是由RecoveryCallback
提供的,它能夠經過將其傳遞給RetryOperations
的execute
方法來提供。
重試或不重試的決定實際上委託給了一個常規的RetryPolicy
,所以能夠在那裏注入對限制和超時的常見關注(參見下面)。
在RetryTemplate
中,execute
方法中重試或失敗的決定由RetryPolicy
決定,RetryPolicy
也是RetryContext
的工廠。RetryTemplate
有責任使用當前策略建立RetryContext
,並在每次嘗試時將其傳遞給RetryCallback
。回調失敗後,RetryTemplate
必須調用RetryPolicy
來要求它更新狀態(該狀態將存儲在RetryContext
中),而後它詢問策略是否能夠進行另外一次嘗試。若是沒法進行另外一次嘗試(例如達到限制或檢測到超時),則策略還負責標識耗盡狀態,但不負責處理異常。RetryTemplate
將拋出原始異常,除非在有狀態的狀況下,當沒有可用的恢復,在這種狀況下,它將拋出RetryExhaustedException
。你還能夠在RetryTemplate中設置一個標誌,讓它無條件地從回調(即從用戶代碼)拋出原始異常。
失敗本質上要麼是可重試的,要麼是不可重試的 — 若是老是要從業務邏輯中拋出相同的異常,那麼重試是沒有幫助的。因此不要在全部異常類型上重試 — 試着只關注那些你但願能夠重試的異常。更積極地重試一般不會對業務邏輯形成損害,但這是浪費,由於若是失敗是肯定的,那麼重試一些預先知道是致命的東西就會花費時間。
Spring Retry提供了一些無狀態RetryPolicy
的簡單通用實現,例如SimpleRetryPolicy
和上面示例中使用的TimeoutRetryPolicy
。
SimpleRetryPolicy
只容許對指定的異常類型列表中的任何一種進行重試,最多能夠重試固定次數:
// Set the max attempts including the initial attempt before retrying // and retry on all exceptions (this is the default): SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true)); // Use the policy... RetryTemplate template = new RetryTemplate(); template.setRetryPolicy(policy); template.execute(new RetryCallback<Foo>() { public Foo doWithRetry(RetryContext context) { // business logic here } });
還有一個更靈活的實現稱爲ExceptionClassifierRetryPolicy
,它容許用戶經過ExceptionClassifier
抽象爲任意一組異常類型配置不一樣的重試行爲。策略的工做原理是調用分類器將異常轉換爲委託RetryPolicy
,例如,經過將一種異常類型映射到另外一種策略,能夠在失敗以前重試更屢次。
用戶可能須要實現本身的重試策略來進行更定製的決策,例如,若是有一個衆所周知的、特定於解決方案的異常分類,則將其分爲可重試和不可重試。
在短暫故障以後重試時,在重試以前稍做等待一般會有所幫助,由於一般故障是由某些問題引發的,而這些問題只能經過等待來解決,若是RetryCallback
失敗,RetryTemplate
能夠根據適當的BackoffPolicy
暫停執行。
public interface BackoffPolicy { BackOffContext start(RetryContext context); void backOff(BackOffContext backOffContext) throws BackOffInterruptedException; }
回退策略能夠自由地以其選擇的任何方式實現回退,Spring Retry開箱即用提供的策略都使用Thread.sleep()
。一個常見的用例是用指數級增加的等待時間來回退,以免兩次重試進入鎖步,兩次都失敗 — 這是從以太網中學到的教訓。爲此,Spring Retry提供了ExponentialBackoffPolicy
,還有一些隨機版本的延遲策略,對於避免在複雜系統中的相關故障之間產生共振很是有用。
對於跨多個不一樣重試的橫切關注點,可以接收額外的回調一般是有用的,爲此,Spring Retry提供了RetryListener
接口,RetryTemplate
容許用戶註冊RetryListeners
,在迭代期間,他們將使用RetryContext
得到回調,並在可用的地方使用Throwable
。
接口是這樣的:
public interface RetryListener { void open(RetryContext context, RetryCallback<T> callback); void onError(RetryContext context, RetryCallback<T> callback, Throwable e); void close(RetryContext context, RetryCallback<T> callback, Throwable e); }
在最簡單的狀況下,open
和close
回調出如今整個重試以前和以後,onError
應用於各個RetryCallback
調用,close
方法也可能接收到一個Throwable
,若是出現錯誤,則是RetryCallback
拋出的最後一個錯誤。
注意,當有多個監聽器時,它們位於列表中,所以有一個順序,在這種狀況下,open
將以相同的順序調用,而onError
和close
將以相反的順序調用。
當處理用@Retryable
註解的方法或用Spring AOP攔截的方法時,spring-retry提供了在RetryListener
實現中詳細檢查方法調用的可能性。
當須要監視某個方法調用被重試的頻率並使用詳細的標記信息(例如:類名、方法名,甚至在某些特殊狀況下的參數值)公開它時,這種場景可能特別有用。
template.registerListener(new MethodInvocationRetryListenerSupport() { @Override protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) { monitoringTags.put(labelTagName, callback.getLabel()); Method method = callback.getInvocation() .getMethod(); monitoringTags.put(classTagName, method.getDeclaringClass().getSimpleName()); monitoringTags.put(methodTagName, method.getName()); // register a monitoring counter with appropriate tags // ... } });
有時候,有些業務處理你知道每次發生時都要重試,這方面的經典示例是遠程服務調用,Spring Retry提供了一個AOP攔截器,它將方法調用封裝在RetryOperations
中正是出於這個目的。RetryOperationsInterceptor
執行攔截方法,並根據所提供的RetryTemplate
中的RetryPolicy
在失敗時重試。
將@EnableRetry
註解添加到你的@Configuration
類之一,並在要重試的方法(或全部方法的類型級別)上使用@Retryable
,你還能夠指定任意數量的重試監聽器,例如:
@Configuration @EnableRetry public class Application { @Bean public Service service() { return new Service(); } @Bean public RetryListener retryListener1() { return new RetryListener() {...} } @Bean public RetryListener retryListener2() { return new RetryListener() {...} } } @Service class Service { @Retryable(RemoteAccessException.class) public service() { // ... do something } }
@Retryable
的屬性能夠用來控制RetryPolicy
和BackoffPolicy
,例如:
@Service class Service { @Retryable(maxAttempts=12, backoff=@Backoff(delay=100, maxDelay=500)) public service() { // ... do something } }
在100
到500
毫秒之間進行隨機回退,最多嘗試12次,還有一個stateful
屬性(默認爲false
)來控制重試是否有狀態,要使用有狀態重試,攔截方法必須有參數,由於它們用於構造狀態的緩存鍵。
@EnableRetry
註解還查找類型爲Sleeper
的bean,以及RetryTemplate
和攔截器中用於控制運行時重試行爲的其餘策略。
@EnableRetry
註解爲@Retryable
bean建立代理,代理(應用程序中的bean實例)中添加了Retryable
接口,這純粹是一個標記接口,但對於但願應用重試建議的其餘工具可能頗有用(若是bean已經實現了Retryable
,那麼它們一般不須要麻煩)。
能夠提供恢復方法,以便在重試耗盡時採用另外一種代碼路徑,方法應該與@Retryable
在同一個類中聲明,並標記爲@Recover
,返回類型必須匹配@Retryable
方法。恢復方法的參數能夠有選擇地包括拋出的異常,也能夠有選擇地包括傳遞給原始retryable方法的參數(或者它們的部分列表,只要沒有一個被省略),例如:
@Service class Service { @Retryable(RemoteAccessException.class) public void service(String str1, String str2) { // ... do something } @Recover public void recover(RemoteAccessException e, String str1, String str2) { // ... error handling making use of original args if required } }
1.2版引入了對某些屬性使用表達式的功能:
@Retryable(exceptionExpression="message.contains('this can be retried')") public void service1() { ... } @Retryable(exceptionExpression="message.contains('this can be retried')") public void service2() { ... } @Retryable(exceptionExpression="@exceptionChecker.shouldRetry(#root)", maxAttemptsExpression = "#{@integerFiveBean}", backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}")) public void service3() { ... }
從Spring Retry 1.2.5,對於exceptionExpression
,不推薦使用模板表達式(#{...}
),而支持簡單表達式字符串(message.contains('this can be retried')
)。
表達式能夠包含屬性佔位符,好比#{${max.delay}}
或#{@exceptionChecker.${retry.method}(#root)}
exceptionExpression
做爲#root
對象對拋出的異常求值。maxAttemptsExpression
和@BackOff
表達式屬性在初始化期間只計算一次,沒有用於計算的根對象,可是它們能夠在上下文中引用其餘bean。使用上面顯示的@Retryable
註解應用重試處理的聲明式方法對AOP類有額外的運行時依賴性,須要在項目中聲明這些類,若是你的應用程序是使用Spring Boot實現的,那麼最好使用AOP的Spring Boot starter解決這個依賴關係,例如,對於Gradle,在build.gradle
中添加如下行:
runtime('org.springframework.boot:spring-boot-starter-aop')
對於非Boot應用程序,聲明運行時依賴於AspectJ的aspectjweaver模塊的最新版本,例如,對於Gradle,在build.gradle
中添加如下行:
runtime('org.aspectj:aspectjweaver:1.8.13')
下面是一個使用Spring AOP來重複對一個名爲remoteCall
的方法的服務調用的聲明式迭代的例子(有關如何配置AOP攔截器的更多細節,請參閱Spring用戶指南):
<aop:config> <aop:pointcut id="transactional" expression="execution(* com..*Service.remoteCall(..))" /> <aop:advisor pointcut-ref="transactional" advice-ref="retryAdvice" order="-1"/> </aop:config> <bean id="retryAdvice" class="org.springframework.retry.interceptor.RetryOperationsInterceptor"/>
上面的示例在攔截器中使用默認的RetryTemplate
,要更改策略或監聽器,只須要將RetryTemplate
實例注入攔截器。