使用Spring-retry 1.1.4完成重試功能
前言
在實際專案中,經常需要在某種情況下對呼叫的方法進行重試,例如超時重試。通過Spring-retry能簡化重試功能的實現,並實現更多樣的重試操作。
Spring-retry結構
Spring-retry提供的RetryOperations介面,該介面提供了若干方法來執行重試操作。在Spring-retry 1.1.4 中該介面的定義如下。
public interface RetryOperations {
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
<T , E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
throws E;
}
呼叫者通過傳入RetryCallback來完成呼叫者的重試操作。如果callback執行失敗(丟擲某些異常),那麼會按照呼叫者設定的策略進行重試。重試操作直到成功,或根據使用者設定的條件而退出。
RetryCallback的介面定義如下:
public interface RetryCallback<T, E extends Throwable> {
T doWithRetry(RetryContext context) throws E;
}
Spring-retry提供了RetryOperations介面的實現類RetryTemplate。通過RetryTemplate來完成重試,下面是使用RetryTemplate重試的一個簡單例子。
public class Main {
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
template.setRetryPolicy(policy);
String result = template.execute(
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
return "Retry";
}
}
);
System.out.println(result);
}
}
程式碼定義了TimeoutRetryPolicy策略,TimeoutRetryPolicy超時時間預設是1秒。TimeoutRetryPolicy超時是指在execute方法內部,從open操作開始到呼叫TimeoutRetryPolicy的canRetry方法這之間所經過的時間。這段時間未超過TimeoutRetryPolicy定義的超時時間,那麼執行操作,否則丟擲異常。
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,RecoveryCallback<T> recoveryCallback, RetryState state) throws E,ExhaustedRetryException {
……略
RetryContext context = open(retryPolicy, state);
……略
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly())
// 呼叫canRetry檢查是否可以重試
……略
}
RetryCallback中的方法doWithRetry有一個RetryContext型別的引數。大多數的doWithRetry操作都會會略該引數。但也可以在重試的過程中通過RetryContext來儲存資料,RetryContext結構如下。
不同的RetryContext維護了不同型別的重試資訊,例如TimeoutRetryContext會維護超時時間與任務開始時間。
private static class TimeoutRetryContext extends RetryContextSupport {
private long timeout;
private long start;
public TimeoutRetryContext(RetryContext parent, long timeout) {
super(parent);
this.start = System.currentTimeMillis();
this.timeout = timeout;
}
public boolean isAlive() {
return (System.currentTimeMillis() - start) <= timeout;
}
}
執行重試時,具體的RetryContext型別,會根據使用者建立的重試策略來建立具體的RetryContext。例如,建立SimpleRetryPolicy,那麼最終建立的Context為SimpleRetryContext。
org.springframework.core.AttributeAccessorSupport實現了org.springframework.core.AttributeAccessor,並定了一個Map,通過AttributeAccessor中定義的方法來訪問map,以便在重試過程中進行資料儲存與獲取資料。
在RetryOperations介面中還會看到RecoveryCallback這個引數。RecoveryCallback的定義如下。
public interface RecoveryCallback<T> {
/**
* @param context the current retry context
* @return an Object that can be used to replace the callback result that failed
* @throws Exception when something goes wrong
*/
T recover(RetryContext context) throws Exception;
}
當重試執行完閉,操作還未成為,那麼可以通過RecoveryCallback完成一些失敗事後處理。
public class Main {
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
SimpleRetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(2);
template.setRetryPolicy(policy);
String result = template.execute(
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
throw new NullPointerException("nullPointerException");
}
}
,
new RecoveryCallback<String>() {
public String recover(RetryContext context) throws Exception {
return "recovery callback";
}
}
);
System.out.println(result);
}
}
上面的程式碼重試兩次後,仍然失敗,RecoveryCallback被呼叫,返回”recovery callback”。如果沒有定義RecoveryCallback,那麼重試2次後,將會丟擲異常。
重試策略
重試策略定義了當操作失敗時如何進行重試操作,框架提供的重試策略包括。
SimpleRetryPolicy 策略
該策略定義了對指定的異常進行若干次重試。預設情況下,對Exception異常及其子類重試3次。如果建立SimpleRetryPolicy並指定重試異常map,可以選擇性重試或不進行重試。下面的程式碼定義了對TimeOutException進行重試
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
Map<Class<? extends Throwable>, Boolean> maps = new HashMap<Class<? extends Throwable>, Boolean>();
maps.put(TimeoutException.class, true);
SimpleRetryPolicy policy2 = new SimpleRetryPolicy(2, maps);
template.setRetryPolicy(policy2);
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
throw new TimeoutException("TimeoutException");
}
}
,
new RecoveryCallback<String>() {
public String recover(RetryContext context) throws Exception {
return "recovery callback";
}
}
);
System.out.println(result);
}
當Map中的的value為false,那麼執行方法,隨後丟擲異常不進行重試。
NeverRetryPolicy 策略
執行一次待執行操作,若出現異常後不進行重試。
AlwaysRetryPolicy 策略
異常後一直重試直到成功。
TimeoutRetryPolicy 策略
在執行execute方法時從open操作開始到呼叫TimeoutRetryPolicy的canRetry方法這之間所經過的時間。這段時間未超過TimeoutRetryPolicy定義的超時時間,那麼執行操作,否則丟擲異常。
ExceptionClassifierRetryPolicy 策略
根據產生的異常選擇重試策略。
Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
policyMap.put(TimeoutException.class, new AlwaysRetryPolicy());
policyMap.put(NullPointerException.class, new NeverRetryPolicy());
policy.setPolicyMap(policyMap);
通過PolicyMap定義異常及其重試策略。下面的程式碼在丟擲NullPointerException採用NeverRetryPolicy策略,而TimeoutException採用AlwaysRetryPolicy。
public class Main {
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
ExceptionClassifierRetryPolicy policy = new ExceptionClassifierRetryPolicy();
Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
policyMap.put(TimeoutException.class, new AlwaysRetryPolicy());
policyMap.put(NullPointerException.class, new NeverRetryPolicy());
policy.setPolicyMap(policyMap);
template.setRetryPolicy(policy);
String result = template.execute(
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
if(arg0.getRetryCount() >= 2) {
Thread.sleep(1000);
throw new NullPointerException();
}
throw new TimeoutException("TimeoutException");
}
}
,
new RecoveryCallback<String>() {
public String recover(RetryContext context) throws Exception {
return "recovery callback";
}
}
);
System.out.println(result);
}
}
此外可以通過setExceptionClassifier來為異常指定重試策略。
Classifier<Throwable, RetryPolicy> exceptionClassifier = new Classifier<Throwable, RetryPolicy>(){
public RetryPolicy classify(Throwable classifiable) {
if(classifiable instanceof TimeoutException)
return new SimpleRetryPolicy();
return new NeverRetryPolicy();
}
};
policy.setExceptionClassifier(exceptionClassifier);
setPolicyMap與setExceptionClassifier使用一個即可。
CompositeRetryPolicy 策略
使用者指定一組策略,隨後根據optimistic選項來確認如何重試。
下面的程式碼中建立CompositeRetryPolicy策略,並建立了RetryPolicy陣列,陣列有兩個具體策略SimpleRetryPolicy與AlwaysRetryPolicy。當CompositeRetryPolicy設定optimistic為true時,Spring-retry會順序遍歷RetryPolicy[]陣列,如果有一個重試策略可重試,例如SimpleRetryPolicy沒有達到重試次數,那麼就會進行重試。 如果optimistic選項設定為false。那麼有一個重試策略無法重試,那麼就不進行重試。例如SimpleRetryPolicy達到重試次數不能再重試,而AlwaysRetryPolicy可以重試,那麼最終是無法重試的。
public class Main {
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
CompositeRetryPolicy policy = new CompositeRetryPolicy();
RetryPolicy[] polices = {new SimpleRetryPolicy(), new AlwaysRetryPolicy()};
policy.setPolicies(polices);
policy.setOptimistic(true);
template.setRetryPolicy(policy);
String result = template.execute(
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
if(arg0.getRetryCount() >= 2) {
Thread.sleep(1000);
throw new NullPointerException();
}
throw new TimeoutException("TimeoutException");
}
}
,
new RecoveryCallback<String>() {
public String recover(RetryContext context) throws Exception {
return "recovery callback";
}
}
);
System.out.println(result);
}
}
上述程式碼,設定setOptimistic(true),而AlwaysRetryPolicy一直可重試,那麼最終可以不斷進行重試。
退避(BackOff)策略
當操作執行失敗時,根據設定的重試策略進行重試。通過BackoffPolicy可以設定再次重試的時間間隔。
public interface BackOffPolicy {
BackOffContext start(RetryContext context);
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;
}
BackOffPolicy介面的實現具體的實現有如下幾種。
StatelessBackoffPolicy下的3個實現類
org.springframework.retry.backoff.FixedBackOffPolicy
在等待一段固定的時間後,再進行重試。預設為1秒。
org.springframework.retry.backoff.NoBackOffPolicy
實現了空方法,因此採用次策略,重試不會等待。這也是RetryTemplate採用的預設退避(backOff)策略。
private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();
org.springframework.retry.backoff.UniformRandomBackOffPolicy
均勻隨機退避策略,等待時間為 最小退避時間 + [0,最大退避時間 - 最小退避時間)間的一個隨機數,如果最大退避時間等於最小退避時間那麼等待時間為0。
SleepingbackOffPolicy的下的4個實現類
org.springframework.retry.backoff.ExponentialBackOffPolicy
指數退避策略 ,每次等待時間為 等待時間 = 等待時間 * N ,即每次等待時間為上一次的N倍。如果等待時間超過最大等待時間,那麼以後的等待時間為最大等待時間。
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1500);
exponentialBackOffPolicy.setMultiplier(2);
exponentialBackOffPolicy.setMaxInterval(6000);
上面的程式碼建立了一個ExponentialBackOffPolicy,初始時間間隔為1500毫秒,N = 2,¸最大間隔為6000毫秒,那麼從第4次重試開始,以後每次等待時間都為6000毫秒。
org.springframework.retry.backoff.ExponentialRandomBackOffPolicy
指數隨機策略,該類是ExponentialBackOffPolicy的子類,計算等待時間的演算法為
@Override
public synchronized long getSleepAndIncrement() {
long next = super.getSleepAndIncrement();
next = (long)(next*(1 + r.nextFloat()*(getMultiplier()-1)));
return next;
}
該方法呼叫ExponentialBackOffPolicy方法,返回等待時間,隨後next*(Multiplier – 1)最為等待時間。 即 [next,next* Multiplier) 之間的一個隨機數。
org.springframework.retry.backoff.FixedBackOffPolicy
org.springframework.retry.backoff.UniformRandomBackOffPolicy
這兩個類與StatelessBackoffPolicy的同名實現類返回等待時間的方法是一致的。而兩者的主要區別是,SleepingbackOffPolicy可以設定使用者定義的Sleeper。
public interface Sleeper {
void sleep(long backOffPeriod) throws InterruptedException;
}
監聽器
通過監聽器,可以在重試操作的某些位置嵌入呼叫者定義的一些操作,以便在某些場景觸發。
public class Main {
public static void main(String[] args) throws Exception {
RetryTemplate template = new RetryTemplate();
ExponentialRandomBackOffPolicy exponentialBackOffPolicy = new ExponentialRandomBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1500);
exponentialBackOffPolicy.setMultiplier(2);
exponentialBackOffPolicy.setMaxInterval(6000);
CompositeRetryPolicy policy = new CompositeRetryPolicy();
RetryPolicy[] polices = {new SimpleRetryPolicy(), new AlwaysRetryPolicy()};
policy.setPolicies(polices);
policy.setOptimistic(true);
template.setRetryPolicy(policy);
template.setBackOffPolicy(exponentialBackOffPolicy);
template.registerListener(new RetryListener() {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("open");
return true;
}
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("onError");
}
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("close");
}
});
template.registerListener(new RetryListener() {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("open2");
return true;
}
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("onError2");
}
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("close2");
}
});
String result = template.execute(
new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext arg0) throws Exception {
arg0.getAttribute("");
if(arg0.getRetryCount() >= 2) {
throw new NullPointerException();
}
throw new TimeoutException("TimeoutException");
}
}
);
System.out.println(result);
}
}
上述程式碼註冊了兩個Listener,Listener中的三個實現方法,onError,open,close會在執行重試操作時被呼叫,在RetryTemplate中doOpenInterceptors,doCloseInterceptors,doOnErrorInterceptors會呼叫監聽器對應的open,close,onError 方法。
doOpenInterceptors方法在第一次重試之前會被呼叫,如果該方法返回true,則會繼續向下直接,如果返回false,則丟擲異常,停止重試。
doCloseInterceptors 會在重試操作執行完畢後呼叫。
doOnErrorInterceptors 在丟擲異常後執行,
當註冊多個Listener時,open方法按會按Listener的註冊順序呼叫,而onError和close則按Listener註冊的順序逆序呼叫。