什么是重試
重試是指,當在一個程序運行過程中,突然遇到了例如網絡延遲,中斷等情況時,為了保證程序容錯性,可用性,一致性等的一個措施,目前主流的框架大多都有一套自己的重試機制,例如 dubbo,mq,Spring 等
概要
Spring 也自己實現了一套重試機制,Spring Retry 是從 Spring batch 中獨立出來的一個功能,主要功能點在于重試和熔斷,目前已經廣泛應用于 Spring Batch,Spring Integration, Spring for Apache Hadoop 等 Spring 項目。spring retry 提供了注解和編程 兩種支持,提供了 RetryTemplate 支持,類似 RestTemplate。整個流程如下:

使用介紹
Maven 依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- also need to add Spring AOP into our project-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
注解使用
開啟 Retry 功能,需在啟動類中使用 @EnableRetry 注解
@SpringBootApplication
@EnableRetry
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注解 @Retryable
需要在重試的代碼中加入重試注解 @Retryable
@Retryable(value = RuntimeException.class)
public void testRetry01() throws MyException {
System.out.println("測試-value屬性");
throw new RuntimeException("出現了異常");
}
默認情況下,會重試 3 次,間隔 1 秒
重試配置
通過 @Retryable 注解的屬性 可以實現重試配置
- Value()
- includevalue 與 include 含義相同,表示可重試的異常類型。默認為空,如果同時 exclude 也為空則會重試所有異常。但在使用時需要注意
@Retryable(value = RuntimeException.class)
public void testRetry01() throws MyException {
System.out.println("測試-value屬性");
throw new RuntimeException("出現了異常");
}
例:testRetry01 只會在程序拋出 RuntimeException 時,開啟重試
- exclude不可重試的異常類型。默認為空(如果 include 也為為空,將重試所有異常)。如果 include 為空但 exclude 不為空,則重試非 exclude 中的異常 @Retryable(exclude = RuntimeException.class)
public void testRetry02() throws MyException {
System.out.println("測試-value屬性");
throw new MyException("出現了異常");
}例:testRetry02 在程序拋出 MyException 時,不會開啟重試 - maxAttempts
最大重試次數,默認為 3
- maxAttemptsExpression
最大嘗試次數的表達式,表達式一旦設置了值,則會覆蓋 maxAttempts 的值,maxAttemptsExpression 可以讀取 application.yml 配置文件里的數據,也可以通過 SpEL 表達式計算對應的值
@Retryable(value = MyException.class, maxAttemptsExpression = "${maxAttempts}")
public void testRetry03() throws MyException {
System.out.println("測試-maxAttemptsExpression屬性");
throw new MyException("出現了異常");
}

例:testRetry03 會去讀 properties 配置文件獲取屬性名為 maxAttempts 的值
@Retryable(value = MyException.class, maxAttemptsExpression = "#{2+3}")
public void testRetry04() throws MyException {
System.out.println("測試-maxAttemptsExpression屬性");
throw new MyException("出現了異常");
}
例:testRetry04 會去通過 SqlEL 計算出對應的重試值
- exceptionExpression
異常處理表達式,ExpressionRetryPolicy 中使用,執行完父類的 canRetry 之后,需要校驗 exceptionExpression 的值,為 true 則可以重試
@Retryable(value = MyException.class, exceptionExpression = "#{@retryService.isRetry()}")
public void testRetry05() throws MyException {
System.out.println("測試-exceptionExpression");
throw new MyException("出現了異常");
}
例:這個表達式的意思就是,如果 testRetry05 方法出現異常 會調用 retryService.isRetry() 方法,根據返回結果判斷是否重試
- @Recover兜底方法
當 @Retryable 方法重試失敗之后,最后就會調用 @Recover 方法。用于 @Retryable 失敗時的“兜底”處理方法。 @Recover 的方法必須要與 @Retryable 注解的方法保持一致,第一入參為要重試的異常,其他參數與 @Retryable 保持一致,返回值也要一樣,否則無法執行!
@Retryable(value = MyException.class)
public void testRetry06() throws MyException {
System.out.println("測試兜底方法");
throw new MyException("出現了異常");
}
@Recover
public void recover06(MyException e) {
System.out.println("兜底方法開啟,異常信息:" + e.getMessage());
}
熔斷模式@CircuitBreaker
指在具體的重試機制下失敗后打開斷路器,過了一段時間,斷路器進入半開狀態,允許一個進入重試,若失敗再次進入斷路器,成功則關閉斷路器,注解為 @CircuitBreaker ,具體包括熔斷打開時間、重置過期時間
@CircuitBreaker(openTimeout = 1000, resetTimeout = 3000, value = MyException.class)
public void testRetry07() throws MyException {
System.out.println("測試CircuitBreaker注解");
throw new MyException("出現了異常");
}
例:openTimeout 時間范圍內失敗 maxAttempts 次數后,熔斷打開 resetTimeout 時長 這個方法的意思就是方法在一秒內失敗三次時,觸發熔斷,下次在有請求過來時,直接進入
重試策略
- SimpleRetryPolicy 默認最多重試 3 次
- TimeoutRetryPolicy 默認在 1 秒內失敗都會重試
- ExpressionRetryPolicy 符合表達式就會重試
- CircuitBreakerRetryPolicy 增加了熔斷的機制,如果不在熔斷狀態,則允許重試
- CompositeRetryPolicy 可以組合多個重試策略
- NeverRetryPolicy 從不重試(也是一種重試策略哈)
- AlwaysRetryPolicy 總是重試
退避策略
退避策略退避是指怎么去做下一次的重試,在這里其實就是等待多長時間。
通過 @Backoff 注解實現,那么我們首先看一下@Backoff 的參數
@Backoff 參數
- value
默認為 1000, 與 delay 作用相同,表示延遲的毫秒數。當 delay 非 0 時,此參數忽略。
- delay
默認為 0。在指數情況下用作初始值,在統一情況下用作*的最小值。當此元素的值為 0 時,將采用元素 value 的值,否則將采用此元素的值,并且將忽略 value。
- maxDelay
默認為 0。重試之間的最大等待時間(以毫秒為單位)。如果小于 delay,那么將應用默認值為 30000L
- multipler
默認為 0。如果為正,則用作乘法器以生成下一個退避延遲。返回一個乘法器,用于計算下一個退避延遲
- delayExpression
評估標準退避期的表達式。在指數情況下用作初始值*,在均勻情況下用作最小值。覆蓋 delay。
- maxDelayExpression
該表達式計算重試之間的最大等待時間(以毫秒為單位)。 如果小于 delay,那么將應用 30000L 為默認值。覆蓋 maxDelay。
- multiplierExpression
評估為用作乘數的值,以生成退避的下一個延遲。覆蓋 multiplier。 返回一個乘數表達式,用于計算下一個退避延遲
- random
默認為 false,在指數情況下 multiplier> 0 將此值設置為 true 可以使后退延遲隨機化,從而使最大延遲乘以前一延遲,并且兩個值之間的分布是均勻的。
@Retryable(value = MyException.class, maxAttempts = 4,
backoff = @Backoff(delay = 2000, multiplier = 2, maxDelay = 5000))
public void testRetry08() throws MyException {
System.out.println("測試-backoff屬性");
throw new MyException("出現了異常");
}
@Backoff 的參數會影響我們使用哪種退避策略
- FixedBackOffPolicy
默認退避策略,每 1 秒重試 1 次
- ExponentialBackOffPolicy
指數退避策略,當設置 multiplier 時使用,每次重試時間間隔為 當前延遲時間 * multiplier。
例如:默認初始 0.1 秒,系數是 2,那么下次延遲 0.2 秒,再下次就是延遲 0.4 秒,如此類推,最大 30 秒。
- ExponentialRandomBackOffPolicy
指數隨機退避策略。在指數退避策略的基礎上增加了隨機性。
- UniformRandomBackOffPolicy
均勻隨機策略,設置 maxDely 但沒有設置 multiplier 時使用,重試間隔會在 maxDelay 和 delay 間隨機
原理
切入點
@EnableRetry
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
* standard JAVA interface-based proxies. The default is {@code false}.
* @return whether to proxy or not to proxy the class
*/
boolean proxyTargetClass() default false;
}
@EnablRetry 中使用了兩個特殊的注解
- @EnableAspectJAutoProxy
這個注解的作用是開啟 aop 的功能,默認使用 jdk 的動態代理。如果 proxyTargetClass 參數為 true,則使用 cglib 的動態代理。
- @Import
Import 引入了 RetryConfiguration 的 bean 。我們重點看下這個 bean。
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Component
public class RetryConfiguration extends AbstractPointcutAdvisor
implements IntroductionAdvisor, BeanFactoryAware, InitializingBean {
private Advice advice;
private Pointcut pointcut;
我們可以看到 RetryConfiguration 繼承了 AbstractPointcutAdvisor,所以 RetryConfiguration 需要實現 getAdvice() 和 getPointcut() 接口,所以這個 bean 的作用就是為 @Retryable 注解注冊 pointcut 切點和 advice 增強。我們再來看他的 初始化方法
@Override
public void afterPropertiesSet() throws Exception {
this.retryContextCache = findBean(RetryContextCache.class);
this.methodArgumentsKeyGenerator = findBean(MethodArgumentsKeyGenerator.class);
this.newMethodArgumentsIdentifier = findBean(NewMethodArgumentsIdentifier.class);
this.retryListeners = findBeans(RetryListener.class);
this.sleeper = findBean(Sleeper.class);
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
retryableAnnotationTypes.add(Retryable.class);
this.pointcut = buildPointcut(retryableAnnotationTypes); //創建 pointcut
this.advice = buildAdvice(); //創建 advice
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
}
}
protected Advice buildAdvice() {
AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
if (this.retryContextCache != null) {
interceptor.setRetryContextCache(this.retryContextCache);
}
if (this.retryListeners != null) {
interceptor.setListeners(this.retryListeners);
}
if (this.methodArgumentsKeyGenerator != null) {
interceptor.setKeyGenerator(this.methodArgumentsKeyGenerator);
}
if (this.newMethodArgumentsIdentifier != null) {
interceptor.setNewItemIdentifier(this.newMethodArgumentsIdentifier);
}
if (this.sleeper != null) {
interceptor.setSleeper(this.sleeper);
}
return interceptor;
}
上面代碼用到了 AnnotationClassOrMethodPointcut,其實它最終還是用到了 AnnotationMethodMatcher 來根據注解進行切入點的過濾。這里就是 @Retryable 注解了
下面來看 AnnotationAwareRetryOperationsInterceptor 的 invoke() 方法
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//獲取真正的代理類
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
if (delegate != null) {
//代理類存在,則執行代理類的 invoke()方法
return delegate.invoke(invocation);
}
else {
//否則,直接執行目標方法
return invocation.proceed();
}
}
這里 getDelegate() 會處理 @Retryable 的相關參數以及決定使用哪種重試策略和退避策略。
private MethodInterceptor getDelegate(Object target, Method method) {
ConcurrentMap<Method, MethodInterceptor> cachedMethods = this.delegates.get(target);
if (cachedMethods == null) {
cachedMethods = new ConcurrentHashMap<Method, MethodInterceptor>();
}
MethodInterceptor delegate = cachedMethods.get(method);
if (delegate == null) {
//獲取方法上的 Retryable 注解
MethodInterceptor interceptor = NULL_INTERCEPTOR;
Retryable retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class);
if (retryable == null) {
//獲取類上的 Retryable 注解
retryable = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Retryable.class);
}
if (retryable == null) {
//獲取目標類或者方法上的 Retryable 注解
retryable = findAnnotationOnTarget(target, method, Retryable.class);
}
if (retryable != null) {
if (StringUtils.hasText(retryable.interceptor())) {
//是否實現了自定義攔截,優先級最高
interceptor = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
}
else if (retryable.stateful()) {
//有狀態的攔截
interceptor = getStatefulInterceptor(target, method, retryable);
}
else {
//無狀態的攔截
interceptor = getStatelessInterceptor(target, method, retryable);
}
}
cachedMethods.putIfAbsent(method, interceptor);
delegate = cachedMethods.get(method);
}
this.delegates.putIfAbsent(target, cachedMethods);
return delegate == NULL_INTERCEPTOR ? null : delegate;
}
該方法會返回 @Retryable 最終使用的處理類,我們重點看一下 getStatelessInterceptor 的處理,getStatefulInterceptor 中多了 @CircuitBreaker 熔斷相關的處理。
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
//生成 RetryTemplate,同時主持 listener
RetryTemplate template = createTemplate(retryable.listeners());
//設置重試策略
template.setRetryPolicy(getRetryPolicy(retryable));
//設置退避策略
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
//通過 StatelessRetryInterceptorBuilder 創建 RetryOperationsInterceptor 攔截,初始化重試模板等信息
return RetryInterceptorBuilder.stateless().retryOperations(template).label(retryable.label())
.recoverer(getRecoverer(target, method)).build();
}
在回頭看看 getStatefulInterceptor 方法
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryContextCache(this.retryContextCache);
//獲取方法上的 CircuitBreaker 注解
CircuitBreaker circuit = AnnotatedElementUtils.findMergedAnnotation(method, CircuitBreaker.class);
if (circuit == null) {
//如果熔斷參數不為空,則處理相關參數,返回響應的攔截處理方,如果為空 ,則處理非熔斷的有狀態重試
circuit = findAnnotationOnTarget(target, method, CircuitBreaker.class);
}
if (circuit != null) {
//處理 CircuitBreaker 注解中的 retryable 相關參數,獲得重試策略
RetryPolicy policy = getRetryPolicy(circuit);
CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
breaker.setOpenTimeout(getOpenTimeout(circuit));
breaker.setResetTimeout(getResetTimeout(circuit));
template.setRetryPolicy(breaker);
template.setBackOffPolicy(new NoBackOffPolicy());
String label = circuit.label();
if (!StringUtils.hasText(label)) {
label = method.toGenericString();
}
return RetryInterceptorBuilder.circuitBreaker().keyGenerator(new FixedKeyGenerator("circuit"))
.retryOperations(template).recoverer(getRecoverer(target, method)).label(label).build();
}
RetryPolicy policy = getRetryPolicy(retryable);
template.setRetryPolicy(policy);
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
String label = retryable.label();
return RetryInterceptorBuilder.stateful().keyGenerator(this.methodArgumentsKeyGenerator)
.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier).retryOperations(template).label(label)
.recoverer(getRecoverer(target, method)).build();
}
重試邏輯及策略實現
RetryTemplate 的 doExecute 方法。
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state)
throws E, ExhaustedRetryException {
// 獲得重試策略
RetryPolicy retryPolicy = this.retryPolicy;
// 退避策略
BackOffPolicy backOffPolicy = this.backOffPolicy;
//新建一個 RetryContext 來保存本輪重試的上下文,允許重試策略自行初始化
RetryContext context = open(retryPolicy, state);
if (this.logger.isTraceEnabled()) {
this.logger.trace("RetryContext retrieved: " + context);
}
// Make sure the context is available globally for clients who need
// it...
RetrySynchronizationManager.register(context);
Throwable lastException = null;
boolean exhausted = false;
try {
//給監聽器發送一條信息。
boolean running = doOpenInterceptors(retryCallback, context);
if (!running) {
throw new TerminatedRetryException(
"Retry terminated abnormally by interceptor before first attempt");
}
// Get or Start the backoff context...
BackOffContext backOffContext = null;
Object resource = context.getAttribute("backOffContext");
if (resource instanceof BackOffContext) {
backOffContext = (BackOffContext) resource;
}
if (backOffContext == null) {
backOffContext = backOffPolicy.start(context);
if (backOffContext != null) {
context.setAttribute("backOffContext", backOffContext);
}
}
//判斷能否重試,就是調用 RetryPolicy 的 canRetry 方法來判斷。
//這個循環會直到原方法不拋出異常,或不需要再重試
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
lastException = null;
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
//方法拋出了異常
lastException = e;
try {
//記錄異常信息
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable",
ex);
}
finally {
//調用 RetryListener 的 onError 方法
doOnErrorInterceptors(retryCallback, context, e);
}
//再次判斷能否重試
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
//如果可以重試則走退避策略
backOffPolicy.backOff(backOffContext);
}
catch (BackOffInterruptedException ex) {
lastException = e;
// back off was prevented by another thread - fail the retry
if (this.logger.isDebugEnabled()) {
this.logger
.debug("Abort retry because interrupted: count="
+ context.getRetryCount());
}
throw ex;
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Checking for rethrow: count=" + context.getRetryCount());
}
if (shouldRethrow(retryPolicy, context, state)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rethrow in retry for policy: count="
+ context.getRetryCount());
}
throw RetryTemplate.<E>wrapIfNecessary(e);
}
}
/*
* A stateful attempt that can retry may rethrow the exception before now,
* but if we get this far in a stateful retry there's a reason for it,
* like a circuit breaker or a rollback classifier.
*/
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
if (state == null && this.logger.isDebugEnabled()) {
this.logger.debug(
"Retry failed last attempt: count=" + context.getRetryCount());
}
exhausted = true;
//這里會查看是否有兜底方法,有就執行,沒有就拋出異常
return handleRetryExhausted(recoveryCallback, context, state);
}
catch (Throwable e) {
throw RetryTemplate.<E>wrapIfNecessary(e);
}
finally {
close(retryPolicy, context, state, lastException == null || exhausted);
//關閉 RetryListener
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
}
}
主要核心重試邏輯就是上面的代碼了,看上去還是挺簡單的。下面看 RetryPolicy 的 canRetry 方法和 BackOffPolicy 的 backOff 方法,以及這兩個 Policy 是怎么來的。我們回頭看看getStatelessInterceptor方法中的getRetryPolicy和getRetryPolicy方法。
private RetryPolicy getRetryPolicy(Annotation retryable) {
Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
//通過注解屬性判斷重試策略 這里判斷如果 value 注解內容為空才去獲取 include 注解的內容 可得出 value 的優先級大于 include
String exceptionExpression = (String) attrs.get("exceptionExpression");
boolean hasExpression = StringUtils.hasText(exceptionExpression);
if (includes.length == 0) {
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
includes = value;
}
@SuppressWarnings("unchecked")
Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
Integer maxAttempts = (Integer) attrs.get("maxAttempts");
String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
if (StringUtils.hasText(maxAttemptsExpression)) {
maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
.getValue(this.evaluationContext, Integer.class);
}
if (includes.length == 0 && excludes.length == 0) {
SimpleRetryPolicy simple = hasExpression ? new ExpressionRetryPolicy(resolve(exceptionExpression))
.withBeanFactory(this.beanFactory)
: new SimpleRetryPolicy();
simple.setMaxAttempts(maxAttempts);
return simple;
}
Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
for (Class<? extends Throwable> type : includes) {
policyMap.put(type, true);
}
for (Class<? extends Throwable> type : excludes) {
policyMap.put(type, false);
}
boolean retryNotExcluded = includes.length == 0;
if (hasExpression) {
return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression, retryNotExcluded)
.withBeanFactory(this.beanFactory);
}
else {
return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
}
}
總結一下:就是通過 @Retryable 注解中的參數,來判斷具體使用文章開頭說到的哪個重試策略,是 SimpleRetryPolicy 還是 ExpressionRetryPolicy 等。
private BackOffPolicy getBackoffPolicy(Backoff backoff) {
long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
if (StringUtils.hasText(backoff.delayExpression())) {
min = PARSER.parseExpression(resolve(backoff.delayExpression()), PARSER_CONTEXT)
.getValue(this.evaluationContext, Long.class);
}
long max = backoff.maxDelay();
if (StringUtils.hasText(backoff.maxDelayExpression())) {
max = PARSER.parseExpression(resolve(backoff.maxDelayExpression()), PARSER_CONTEXT)
.getValue(this.evaluationContext, Long.class);
}
double multiplier = backoff.multiplier();
if (StringUtils.hasText(backoff.multiplierExpression())) {
multiplier = PARSER.parseExpression(resolve(backoff.multiplierExpression()), PARSER_CONTEXT)
.getValue(this.evaluationContext, Double.class);
}
if (multiplier > 0) {
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
if (backoff.random()) {
policy = new ExponentialRandomBackOffPolicy();
}
policy.setInitialInterval(min);
policy.setMultiplier(multiplier);
policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
if (max > min) {
UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
policy.setMinBackOffPeriod(min);
policy.setMaxBackOffPeriod(max);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
FixedBackOffPolicy policy = new FixedBackOffPolicy();
policy.setBackOffPeriod(min);
if (this.sleeper != null) {
policy.setSleeper(this.sleeper);
}
return policy;
}
就是通過 @Backoff 注解中的參數,來判斷具體使用文章開頭說到的哪個退避策略,是 FixedBackOffPolicy 還是 UniformRandomBackOffPolicy 等。
那么每個 RetryPolicy 都會重寫 canRetry 方法,然后在 RetryTemplate 判斷是否需要重試。我們看看 SimpleRetryPolicy 的
@Override
public boolean canRetry(RetryContext context) {
Throwable t = context.getLastThrowable();
//判斷拋出的異常是否符合重試的異常
//還有,是否超過了重試的次數
return (t == null || retryForException(t)) && context.getRetryCount() < maxAttempts;
}
同樣,我們看看 FixedBackOffPolicy 的退避方法。
protected void doBackOff() throws BackOffInterruptedException {
try {
//就是 sleep 固定的時間
sleeper.sleep(backOffPeriod);
}
catch (InterruptedException e) {
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
}
}
至此,重試的主要原理以及邏輯大概就是這樣了。
作者:穆清
來源:微信公眾號:政采云技術
出處
:https://mp.weixin.qq.com/s/fCogDuNUz74EHd_WEYqfZA