背景
在調(diào)用第三方接口或者使用MQ時,會出現(xiàn)網(wǎng)絡(luò)抖動,連接超時等網(wǎng)絡(luò)異常,所以需要重試。為了使處理更加健壯并且不太容易出現(xiàn)故障,后續(xù)的嘗試操作,有時候會幫助失敗的操作最后執(zhí)行成功。一般情況下,需要我們自行實現(xiàn)重試機(jī)制,一般是在業(yè)務(wù)代碼中加入一層循環(huán),如果失敗后,再嘗試重試,但是這樣實現(xiàn)并不優(yōu)雅。在SpringBoot中,已經(jīng)實現(xiàn)了相關(guān)的能力,通過@Retryable注解可以實現(xiàn)我們想要的結(jié)果。
@Retryable
首先來看一下Spring官方文檔的解釋:
@Retryable注解可以注解于方法上,來實現(xiàn)方法的重試機(jī)制。
POM依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
使用實例
SpringBoot retry的機(jī)制比較簡單,只需要兩個注解即可實現(xiàn)。
啟動類
@SpringBootApplication @EnableRetry public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
在啟動類上,需要加入@EnableRetry注解,來開啟重試機(jī)制。
Service類
前面提到過,@Retryable是基于方法級別的,因此在Service中,需要在你希望重試的方法上,增加重試注解。
@Service @Slf4j public class DoRetryService { @Retryable(value = Exception.class, maxAttempts = 4, backoff = @Backoff(delay = 2000L, multiplier = 1.5)) public boolean doRetry(boolean isRetry) throws Exception { log.info("開始通知下游系統(tǒng)"); log.info("通知下游系統(tǒng)"); if (isRetry) { throw new RuntimeException("通知下游系統(tǒng)異常"); } return true; } }
來簡單解釋一下注解中幾個參數(shù)的含義:
名稱 | 含義 |
---|---|
interceptor | Retry interceptor bean name to be applied for retryable method. |
value | Exception types that are retryable. Synonym for includes(). Defaults to empty (and if excludes is also empty all exceptions are retried). |
include | Exception types that are retryable. Defaults to empty (and if excludes is also empty all exceptions are retried). |
exclude | Exception types that are not retryable. Defaults to empty (and if includes is also empty all exceptions are retried). |
label | A unique label for statistics reporting. If not provided the caller may choose to ignore it, or provide a default. |
stateful | Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the retry policy is applied with the same policy to subsequent invocations with the same arguments. If false then retryable exceptions are not re-thrown. |
maxAttempts | the maximum number of attempts (including the first failure), defaults to 3 |
maxAttemptsExpression | an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3 |
backoff | Specify the backoff properties for retrying this operation. The default is a simple specification with no properties. |
exceptionExpression | Specify an expression to be evaluated after the SimpleRetryPolicy.canRetry() returns true - can be used to conditionally suppress the retry. |
listeners | Bean names of retry listeners to use instead of default ones defined in Spring context. |
上面是@Retryable的參數(shù)列表,參數(shù)較多,這里就選擇幾個主要的來說明一下:
interceptor:可以通過該參數(shù),指定方法攔截器的bean名稱
value:拋出指定異常才會重試
include:和value一樣,默認(rèn)為空,當(dāng)exclude也為空時,默認(rèn)所以異常
exclude:指定不處理的異常
maxAttempts:最大重試次數(shù),默認(rèn)3次
backoff:重試等待策略,默認(rèn)使用@Backoff,@Backoff的value默認(rèn)為1000L,我們設(shè)置為2000L;multiplier(指定延遲倍數(shù))默認(rèn)為0,表示固定暫停1秒后進(jìn)行重試,如果把multiplier設(shè)置為1.5,則第一次重試為2秒,第二次為3秒,第三次為4.5秒。
我們把上面的例子執(zhí)行一下,來看看效果:
2019-12-25 11:38:02.492 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統(tǒng) 2019-12-25 11:38:02.493 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統(tǒng) 2019-12-25 11:38:04.494 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統(tǒng) 2019-12-25 11:38:04.495 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統(tǒng) 2019-12-25 11:38:07.496 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統(tǒng) 2019-12-25 11:38:07.496 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統(tǒng) 2019-12-25 11:38:11.997 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 開始通知下游系統(tǒng) 2019-12-25 11:38:11.997 INFO 25664 --- [ main] c.f.l.service.impl.DoRetryServiceImpl : 通知下游系統(tǒng) java.lang.RuntimeException: 通知下游系統(tǒng)異常 ... ... ...
可以看到,三次之后拋出了RuntimeException的異常。
@Recover
當(dāng)重試耗盡時,RetryOperations可以將控制傳遞給另一個回調(diào),即RecoveryCallback。Spring-Retry還提供了@Recover注解,用于@Retryable重試失敗后處理方法,此方法里的異常一定要是@Retryable方法里拋出的異常,否則不會調(diào)用這個方法。
@Recover
public boolean doRecover(Throwable e, boolean isRetry) throws ArithmeticException { log.info("全部重試失敗,執(zhí)行doRecover"); return false; }
對于@Recover注解的方法,需要特別注意的是:
1、方法的返回值必須與@Retryable方法一致
2、方法的第一個參數(shù),必須是Throwable類型的,建議是與@Retryable配置的異常一致,其他的參數(shù),需要與@Retryable方法的參數(shù)一致
/** * Annotation for a method invocation that is a recovery handler. A suitable recovery * handler has a first parameter of type Throwable (or a subtype of Throwable) and a * return value of the same type as the <code>@Retryable</code> method to recover from. * The Throwable first argument is optional (but a method without it will only be called * if no others match). Subsequent arguments are populated from the argument list of the * failed method in order. */
@Recover不生效的問題
在測試過程中,發(fā)現(xiàn)@Recover無法生效,執(zhí)行時拋出異常信息:
org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.ArithmeticException: / by zero
at org.springframework.retry.annotation.RecoverAnnotationRecoveryHandler.recover(RecoverAnnotationRecoveryHandler.java:61)
at org.springframework.retry.interceptor.RetryOperationsInterceptor$ItemRecovererCallback.recover(RetryOperationsInterceptor.java:141)
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:512)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:351)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:180)
at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:115)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy157.doRetry(Unknown Source)
追蹤一下異常的信息,進(jìn)入到RecoverAnnotationRecoveryHandler中,找到報錯的方法public T recover(Object[] args, Throwable cause),看一下其實現(xiàn):
發(fā)現(xiàn)報錯處,是因為method為空而導(dǎo)致的,明明我已經(jīng)在需要執(zhí)行的方法上注解了@Recover,為什么還會找不到方法呢?很奇怪,再來深入追蹤一下:
打斷點到這,發(fā)現(xiàn)methods列表是空的,那么methods列表是什么時候初始化的呢?繼續(xù)追蹤:
發(fā)現(xiàn)了初始化methods列表的地方,這里會掃描注解了@Recover注解的方法,將其加入到methds列表中,那么為什么沒有掃描到我們注解了的方法呢?
很奇怪,為什么明明注解了@Recover,這里卻沒有掃描到呢?
我有點懷疑Spring掃描的部分,可能有什么問題了,回頭去看看@EnableRetry是怎么說的:
終于找到問題的所在了,對于@EnableRetry中的proxyTargetClass參數(shù),是控制是否對使用接口實現(xiàn)的bean開啟代理類,默認(rèn)的情況下,是不開啟的,問題原因就是這個,我們來實驗一下,把這個參數(shù)改成true:
@EnableRetry(proxyTargetClass = true)
再次運行,果然沒有問題了。
由此得出結(jié)論,當(dāng)使用接口實現(xiàn)的bean時,需要將EnableRetry的參數(shù)改為true,非接口的實現(xiàn),可以使用默認(rèn)配置,即false。
結(jié)語
本篇主要簡單介紹了Springboot中的Retryable的使用,主要的適用場景為在調(diào)用第三方接口或者使用MQ時。由于會出現(xiàn)網(wǎng)絡(luò)抖動,連接超時等網(wǎng)絡(luò)異常。
以上這篇SpringBoot @Retryable注解方式就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/wtopps/article/details/103698635