SpringRetry重試機制

前言

Spring實現了一套重試機制,功能簡單實用。Spring Retry是從Spring Batch 2.2.0版本中獨立出來,變成了Spring Retry模塊。,已經廣泛應用於Spring Batch,Spring Integration, Spring for Apache Hadoop等Spring項目。

使用Spring Retry

(一)Maven依賴

 <!-- 重試機制 -->
 <dependency>
     <groupId>org.springframework.retry</groupId>
     <artifactId>spring-retry</artifactId>
     <version>1.2.4.RELEASE</version>
 </dependency>
 
 <dependency>
     <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

(二)配置類添加註解 @EnableRetry

@EnableRetry
@Configuration
public class RetryConfiguration {

}

(三)Service方法編寫使用以下2個註解,@Retryable標記方法重試的相關信息和@Recover標記重試失敗的回調的方法

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    String interceptor() default "";

    Class<? extends Throwable>[] value() default {};

    Class<? extends Throwable>[] include() default {};

    Class<? extends Throwable>[] exclude() default {};

    String label() default "";

    boolean stateful() default false;

    int maxAttempts() default 3;

    String maxAttemptsExpression() default "";

    Backoff backoff() default @Backoff;

    String exceptionExpression() default "";

    String[] listeners() default {};
}

@Retryable註解,其中:
value : 指定發生的異常進行重試 
include : 同value , 默認"" , 當exclude也爲空時 , 所有異常都重試 
exclude : 排除不重試的異常 , 默認"" , 當include也爲空時 , 所有異常都重試
maxAttemps : 嘗試次數 , 默認3 . 注意該值包含第一次正常執行的次數 , 即失敗之後重新嘗試2次 , 一共執行3次
backoff : 重試補償機制 , 默認無 , 屬性配置如下
    delay : 延遲指定時間後(單位毫秒)重試
    multiplier : 指定延遲的倍數 , 每次執行間隔加倍延遲 . 如delay=5000L,multiplier=2 , 第一次重 試爲5S後 , 第二次爲第一次的10S後 , 第三次爲第二次的20S後

@Recover註解,其中:

當重試次數達到限定時 , 會執行@Recover註解的補償方法 , 只有在入參與發生異常匹配時纔會執行該補償方法,即發生的異常類型需要和@Recover註解的參數一致,@Retryable註解的方法不能有返回值,不然@Recover註解的方法無效

在SpringBoot中使用

@SpringBootApplication
@EnableRetry//啓用重試機制
public class RetryApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(RetryApplication.class, args);
    }
}


/** 出現指定異常時(RuntimeException) , 再重試3次 , 每次延遲5s , 之後每次延遲翻倍*/
    @Retryable(include = {RuntimeException.class}, maxAttempts = 4, backoff = @Backoff(delay = 2000L, multiplier = 2))
    public void work2() {
        System.out.println("執行方法 : " + LocalDateTime.now());
        throw new RuntimeException();//模擬異常
    }


  /** 當重試次數達到限定時 , 會執行@Recover註解的補償方法 , 只有在入參與發生異常匹配時纔會執行該補償方法 */
    @Recover
    public void recover(RuntimeException e) {
        System.out.println("執行補償方法 : " + LocalDateTime.now());
    }

運行結果:

執行方法:2019-12-31T20:17:48.566

執行方法:2019-12-31T20:17:49.567

執行方法:2019-12-31T20:17:50.568

執行補償方法:2019-12-31T20:17:50.568

常見問題

  • maxAttemps 參數爲嘗試次數 , 而非重試次數 . 即=3時 , 會重試2次 , =4時會重試3次 , 因爲它包括了首次正常執行的計數
  • retry利用了Spring AOP的原理 , 所以底層採用了代理的技術 , 所有同一個方法內調用會使代理失效(導入的要是代理對象而非this對象)
  • 有關非冪等的操作(如新增,修改等) , 不要使用重試 , 會影響數據一致性
     

Spring Retry的重試策略和退避策略

通過源碼可以看出,Spring Retry的重試策略主要在policy包中,常見的包括:

  • SimpleRetryPolicy 
    默認最多重試3次
  • TimeoutRetryPolicy 
    默認在1秒內失敗都會重試
  • ExpressionRetryPolicy 
    符合表達式就會重試
  • CircuitBreakerRetryPolicy 
    增加了熔斷的機制,如果不在熔斷狀態,則允許重試
  • CompositeRetryPolicy 
    可以組合多個重試策略
  • NeverRetryPolicy 
    從不重試(也是一種重試策略哈)
  • AlwaysRetryPolicy 
    總是重試

退避策略主要在backoff包裏,退避是指怎麼去做下一次的重試,在這裏其實就是等待多長時間,主要包括:

 

  • FixedBackOffPolicy 
    默認固定延遲1秒後執行下一次重試
  • ExponentialBackOffPolicy 
    指數遞增延遲執行重試,默認初始0.1秒,係數是2,那麼下次延遲0.2秒,再下次就是延遲0.4秒,如此類推,最大30秒。
  • ExponentialRandomBackOffPolicy 
    在上面那個策略上增加隨機性
  • UniformRandomBackOffPolicy 
    這個跟上面的區別就是,上面的延遲會不停遞增,這個只會在固定的區間隨機
  • StatelessBackOffPolicy 
    這個說明是無狀態的,所謂無狀態就是對上次的退避無感知,從它下面的子類也能看出來

 

Spring Retry的原理

代碼如何實現重試的,分析@EnableRetry

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(
    proxyTargetClass = false
)
@Import({RetryConfiguration.class})
@Documented
public @interface EnableRetry {
    boolean proxyTargetClass() default false;
}

我們可以看到

@EnableAspectJAutoProxy(proxyTargetClass = false)就是打開Spring AOP功能,@Import註解註冊RetryConfiguration這個bean,RetryConfigutation繼承AbstractPointcutAdvisor,它有一個pointcut和一個advice。我們知道,在IOC過程中會根據PointcutAdvisor類來對Bean進行Pointcut的過濾,然後生成對應的AOP代理類,用advice來加強處理。 

@Configuration
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware {
    private Advice advice;
    private Pointcut pointcut;
    @Autowired(
        required = false
    )
    private RetryContextCache retryContextCache;
    @Autowired(
        required = false
    )
    private List<RetryListener> retryListeners;
    @Autowired(
        required = false
    )
    private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
    @Autowired(
        required = false
    )
    private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
    @Autowired(
        required = false
    )
    private Sleeper sleeper;
    private BeanFactory beanFactory;

    public RetryConfiguration() {
    }

    @PostConstruct
    public void init() {
        Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet(1);
        retryableAnnotationTypes.add(Retryable.class);
        this.pointcut = this.buildPointcut(retryableAnnotationTypes);
        this.advice = this.buildAdvice();
        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware)this.advice).setBeanFactory(this.beanFactory);
        }

    }

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public ClassFilter getClassFilter() {
        return this.pointcut.getClassFilter();
    }

    public Class<?>[] getInterfaces() {
        return new Class[]{org.springframework.retry.interceptor.Retryable.class};
    }

    public void validateInterfaces() throws IllegalArgumentException {
    }

    public Advice getAdvice() {
        return this.advice;
    }

    public Pointcut getPointcut() {
        return this.pointcut;
    }
    //創建advice對象,即攔截器
    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;
    }

    protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
        ComposablePointcut result = null;
        Iterator var3 = retryAnnotationTypes.iterator();

        while(var3.hasNext()) {
            Class<? extends Annotation> retryAnnotationType = (Class)var3.next();
            Pointcut filter = new RetryConfiguration.AnnotationClassOrMethodPointcut(retryAnnotationType);
            if (result == null) {
                result = new ComposablePointcut(filter);
            } else {
                result.union(filter);
            }
        }

        return result;
    }

    private static class AnnotationMethodsResolver {
        private Class<? extends Annotation> annotationType;

        public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) {
            this.annotationType = annotationType;
        }

        public boolean hasAnnotatedMethods(Class<?> clazz) {
            final AtomicBoolean found = new AtomicBoolean(false);
            ReflectionUtils.doWithMethods(clazz, new MethodCallback() {
                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                    if (!found.get()) {
                        Annotation annotation = AnnotationUtils.findAnnotation(method, AnnotationMethodsResolver.this.annotationType);
                        if (annotation != null) {
                            found.set(true);
                        }

                    }
                }
            });
            return found.get();
        }
    }

    private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
        private final RetryConfiguration.AnnotationMethodsResolver methodResolver;

        AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
            super(annotationType, true);
            this.methodResolver = new RetryConfiguration.AnnotationMethodsResolver(annotationType);
        }

        public boolean matches(Class<?> clazz) {
            return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
        }
    }

    private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut {
        private final MethodMatcher methodResolver;

        AnnotationClassOrMethodPointcut(Class<? extends Annotation> annotationType) {
            this.methodResolver = new AnnotationMethodMatcher(annotationType);
            this.setClassFilter(RetryConfiguration.this.new AnnotationClassOrMethodFilter(annotationType));
        }

        public boolean matches(Method method, Class<?> targetClass) {
            return this.getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass);
        }

        public boolean equals(Object other) {
            if (this == other) {
                return true;
            } else if (!(other instanceof RetryConfiguration.AnnotationClassOrMethodPointcut)) {
                return false;
            } else {
                RetryConfiguration.AnnotationClassOrMethodPointcut otherAdvisor = (RetryConfiguration.AnnotationClassOrMethodPointcut)other;
                return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
            }
        }
    }
}

在初始化方法中,創建pointcut和advice的實例,其中AnnotationAwareRetryOperationsInterceptor是一個MethodInterceptor,在創建AOP代理過程中如果目標方法符合pointcut的規則,它就會加到interceptor列表中,然後做增強,我們看看invoke方法做了什麼增強。 

public Object invoke(MethodInvocation invocation) throws Throwable {
        MethodInterceptor delegate = this.getDelegate(invocation.getThis(), invocation.getMethod());
        return delegate != null ? delegate.invoke(invocation) : invocation.proceed();
    }

這裏用到了委託,主要是需要根據配置委託給具體“有狀態”的interceptor還是“無狀態”的interceptor

private MethodInterceptor getDelegate(Object target, Method method) {      
		if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {      
			synchronized (this.delegates) {      
				if (!this.delegates.containsKey(target)) {      
					this.delegates.put(target, new HashMap<Method, MethodInterceptor>());      
				}      
				Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);      
				if (!delegatesForTarget.containsKey(method)) {      
					Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);      
					if (retryable == null) {      
						retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);      
					}      
					if (retryable == null) {      
						retryable = findAnnotationOnTarget(target, method);      
					}      
					if (retryable == null) {      
						return delegatesForTarget.put(method, null);      
					}      
					MethodInterceptor delegate;      
					//支持自定義MethodInterceptor,而且優先級最高      
					if (StringUtils.hasText(retryable.interceptor())) {      
						delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);      
					}      
					else if (retryable.stateful()) {      
                    	//得到“有狀態”的interceptor      
						delegate = getStatefulInterceptor(target, method, retryable);      
					}      
					else {      
                    	//得到“無狀態”的interceptor      
						delegate = getStatelessInterceptor(target, method, retryable);      
					}      
					delegatesForTarget.put(method, delegate);      
				}      
			}      
		}      
		return this.delegates.get(target).get(method);      
	}   

getStatefulInterceptor和getStatelessInterceptor都是差不多,我們先看看比較簡單的getStatelessInterceptor:

private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {      
		//生成一個RetryTemplate      
		RetryTemplate template = createTemplate(retryable.listeners());      
		//生成retryPolicy      
		template.setRetryPolicy(getRetryPolicy(retryable));      
		//生成backoffPolicy      
		template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));      
		return RetryInterceptorBuilder.stateless()      
				.retryOperations(template)      
				.label(retryable.label())      
				.recoverer(getRecoverer(target, method))      
				.build();      
	} 

具體生成retryPolicy和backoffPolicy的規則,我們等下再來看。 RetryInterceptorBuilder其實就是爲了生成 RetryOperationsInterceptor。RetryOperationsInterceptor也是一個MethodInterceptor,我們來看看它的invoke方法。 

public Object invoke(final MethodInvocation invocation) throws Throwable {      
      
		String name;      
		if (StringUtils.hasText(label)) {      
			name = label;      
		} else {      
			name = invocation.getMethod().toGenericString();      
		}      
		final String label = name;      
      
		//定義了一個RetryCallback,其實看它的doWithRetry方法,調用了invocation的proceed()方法,是不是有點眼熟,這就是AOP的攔截鏈調用,如果沒有攔截鏈,那就是對原來方法的調用。      
		RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {      
      
			public Object doWithRetry(RetryContext context) throws Exception {      
				      
				context.setAttribute(RetryContext.NAME, label);      
      
				/*      
				 * If we don't copy the invocation carefully it won't keep a reference to      
				 * the other interceptors in the chain. We don't have a choice here but to      
				 * specialise to ReflectiveMethodInvocation (but how often would another      
				 * implementation come along?).      
				 */      
				if (invocation instanceof ProxyMethodInvocation) {      
					try {      
						return ((ProxyMethodInvocation) invocation).invocableClone().proceed();      
					}      
					catch (Exception e) {      
						throw e;      
					}      
					catch (Error e) {      
						throw e;      
					}      
					catch (Throwable e) {      
						throw new IllegalStateException(e);      
					}      
				}      
				else {      
					throw new IllegalStateException(      
							"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +      
									"so please raise an issue if you see this exception");      
				}      
			}      
      
		};      
      
		if (recoverer != null) {      
			ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(      
					invocation.getArguments(), recoverer);      
			return this.retryOperations.execute(retryCallback, recoveryCallback);      
		}      
		//最終還是進入到retryOperations的execute方法,這個retryOperations就是在之前的builder set進來的RetryTemplate。      
		return this.retryOperations.execute(retryCallback);      
      
	}      

無論是RetryOperationsInterceptor還是StatufulRetryOperationsInterceptor,最終的攔截處理邏輯還是調用RetryTemplate的execute方法,RetryTemplate作爲一個模板類,裏面包含了重試統一邏輯。

重試邏輯及策略實現

上面介紹了Spring Retry利用了AOP代理使重試機制對業務代碼進行“入侵”。繼續看看重試的邏輯做了什麼:
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 {      
      
			//如果有註冊RetryListener,則會調用它的open方法,給調用者一個通知。      
			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;      
					//doWithRetry方法,一般來說就是原方法      
					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;      
			//重試結束後如果有兜底Recovery方法則執行,否則拋異常      
			return handleRetryExhausted(recoveryCallback, context, state);      
      
		}      
		catch (Throwable e) {      
			throw RetryTemplate.<E>wrapIfNecessary(e);      
		}      
		finally {      
			//處理一些關閉邏輯      
			close(retryPolicy, context, state, lastException == null || exhausted);      
			//調用RetryListener的close方法      
			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");      
		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);      
		}      
	}      

至此,重試的主要原理以及邏輯大概就是這樣了。

RetryContext

每一個策略都有對應的Context。在Spring Retry裏,其實每一個策略都是單例來的。我剛開始直覺是對每一個需要重試的方法都會new一個策略,這樣重試策略之間纔不會產生衝突,但是一想就知道這樣就可能多出了很多策略對象出來,增加了使用者的負擔,這不是一個好的設計。Spring Retry採用了一個更加輕量級的做法,就是針對每一個需要重試的方法只new一個上下文Context對象,然後在重試時,把這個Context傳到策略裏,策略再根據這個Context做重試,而且Spring Retry還對這個Context做了cache。這樣就相當於對重試的上下文做了優化。

總結

Spring Retry通過AOP機制來實現對業務代碼的重試”入侵“,RetryTemplate中包含了核心的重試邏輯,還提供了豐富的重試策略和退避策略。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章