业务系统和业务系统之间、接口与接口之间的接口调用会有一些不稳定的因素存在,比如网络波动导致接口调用网络超时,接口调用出现异常等,导致业务请求依赖的接口失败而获取不到数据或者是让系统异常的情况,也没有重试和补偿的机制,这就让系统的稳定性(鲁棒性)大大折扣,那重试机制的使用场景是接口没有要求幂等性和强一致性、发送消息失败、争抢锁失败等,接口要求只能调用一次而且必须成功,这个几乎是很难做到的,所以接口的调用需要考虑是否考虑幂等性、数据一致性、分区容错性和出错时的补偿机制或重试机制,最终一致性还是强一致性,最终一致性就使用MQ等中间件产品,强一致性就使用分布式事务等中间件产品,今天分享的是Java的优雅重试机制spring-retry。
如果一次操作失败,可以进行多次重试,提高调用成功的可能性
private int maxTry = 3;
@Test
public void test1() {
for (int i = 1; i <= maxTry; i++) {
try {
Boolean result = this.operate1();
if (result) {
i = 4;
log.info(result.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Boolean operate1() {
log.info("operate1开始");
String result = HttpUtil.get("http://www.baidu.com", 5000);
log.info(result);
if (StringUtils.isNotEmpty(result)) {
return Boolean.TRUE;
}
log.info("operate1结束");
return Boolean.FALSE;
}
package com.example.javaretrydemo.service;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author zlf
* @description:
* @time: 2022/10/2
*/
public class ThreadPoolService {
private static final int DEFAULT_CORE_SIZE = 10;
private static final int MAX_QUEUE_SIZE = 20;
private static final int QUEUE_INIT_MAX_SIZE = 500;
private volatile static ThreadPoolExecutor executor;
private ThreadPoolService() {
}
// 获取单例的线程池对象
public static ThreadPoolExecutor getInstance() {
if (executor == null) {
synchronized (ThreadPoolService.class) {
if (executor == null) {
executor = new ThreadPoolExecutor(DEFAULT_CORE_SIZE,// 核心线程数
MAX_QUEUE_SIZE, // 最大线程数
Integer.MAX_VALUE, // 闲置线程存活时间
TimeUnit.MILLISECONDS,// 时间单位
new LinkedBlockingDeque<Runnable>(QUEUE_INIT_MAX_SIZE),// 线程队列
Executors.defaultThreadFactory()// 线程工厂
);
}
}
}
return executor;
}
public void execute(Runnable runnable) {
if (runnable == null) {
return;
}
executor.execute(runnable);
}
// 从线程队列中移除对象
public void cancel(Runnable runnable) {
if (executor != null) {
executor.getQueue().remove(runnable);
}
}
}
@Test
public void test2() {
ThreadPoolExecutor executor = ThreadPoolService.getInstance();
AtomicInteger num = new AtomicInteger(1);
CompletableFuture.runAsync(() -> {
while (num.get() <= 3) {
//睡眠30秒
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Boolean result = this.operate1();
if (result) {
num.set(4);
}
} catch (Exception e) {
e.printStackTrace();
num.getAndIncrement();
}
}
}, executor);
}
public Boolean operate1() {
log.info("operate1开始");
String result = HttpUtil.get("http://www.baidu.com", 5000);
log.info(result);
if (StringUtils.isNotEmpty(result)) {
return Boolean.TRUE;
}
log.info("operate1结束");
return Boolean.FALSE;
}
使用JDK动态代理和CGLib动态代理(对目标类进行增强处理)加上spring的AOP(统一的切入调用拦截)加上反射机制(让公共逻辑更加的通用),通过AOP对方法调用进行拦截然后通过动态代理来生成代理对象给目标类的目标方法的调用的前后进行增强处理同时在加上try/catch处理调用目标方法,这种就可以写成一个公共的实现思路模型。
使用该类库虽然灵活但是代码入侵性很强,需要写额外的代码,所以不推荐使用,有兴趣的可以去研究下。
调用出错,没有重试机制,直接获取到日志调用参数然后使用curl重新调用接口,人工手动处理
使用spring-retry
@Retryable(value = SocketTimeoutException.class, maxAttempts = 10, backoff = @Backoff(value = 3000L), listeners = {"myRetryListener"})
public Boolean operate2() {
log.info("operate3开始");
String result = HttpUtil.get("http://www.baidu.com", 5000);
log.info(result);
if (StringUtils.isNotEmpty(result)) {
return Boolean.TRUE;
}
log.info("operate3结束");
return Boolean.FALSE;
}
// 将该类中使用了@Retryable的方法的类注入到其它类中然后调用该方法
spring-retry官网: https://github.com/spring-projects/spring-retry
org.springframework.retry
spring-retry
org.springframework
spring-aspects
5.2.22.RELEASE
在主启动类上加入@EnableRetry注解
@EnableRetry
@SpringBootApplication
public class JavaRetryDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JavaRetryDemoApplication.class, args);
}
}
@Retryable
@Retryable(value = SocketTimeoutException.class, maxAttempts = 10, backoff = @Backoff(value = 3000L), listeners = {"myRetryListener"})
public Boolean operate2() {
log.info("operate3开始");
String result = HttpUtil.get("http://www.baidu.com", 5000);
log.info(result);
if (StringUtils.isNotEmpty(result)) {
return Boolean.TRUE;
}
log.info("operate3结束");
return Boolean.FALSE;
}
注意: 该demo使用的是注解式,使用注解方式简洁,没有代码入侵,没有使用编程式,也可以使用编程式,编写一个RetryTemplate模板,然后通过代码定义重试和回退的策略,使用注解方式,需要注意加了@Retryable注解的类需要是一个public修饰的,方法中不要加try/catch,否则方法调用异常后,异常没有抛出而是被你在方法中处理了,重试就无效了,方法使用public修饰是为了动态代理能调用到目标类的公共目标方法,就像是spring的事务失效的机制一样,制与方法上的修饰是否可以不是public,可以去自行验证,我只是类比了spring中的事务失效的情况的猜测,写成public更加稳妥一点,不然又出现一些莫名其妙的问题,@Retryable的方法不能在本类被调用,不然重试机制不会生效。也就是要标记为@Service,然后在其它类使用@Autowired注入或者@Bean去实例才能生效。
package com.example.javaretrydemo.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.listener.RetryListenerSupport;
import org.springframework.stereotype.Component;
/**
* @author zlf
* @description:
* @time: 2022/10/2
*/
@Component
@Slf4j
public class MyRetryListener extends RetryListenerSupport {
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("监听到重试过程关闭了");
log.info("=======================================================================");
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
log.info("监听到重试过程错误了");
}
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
log.info("=======================================================================");
log.info("监听到重试过程开启了");
return true;
}
}
能否重试。
当proxyTargetClass属性为true时,使用CGLIB代理。默认使用标准JAVA注解。
在spring Boot中此参数写在程序入口即可。
value:指定处理的异常类
include:指定处理的异常类和value一样,默认为空,当exclude也为空时,默认所有异常
exclude:指定异常不处理,默认空,当include也为空时,默认所有异常
maxAttempts:最大重试次数。默认3次
backoff: 重试等待策略。默认使用@Backoff注解
不设置参数时,默认使用FixedBackOffPolicy(指定等待时间),重试等待1000ms
设置delay,使用FixedBackOffPolicy(指定等待时间),重试等待填写的时间
设置delay和maxDealy时,重试等待在这两个值之间均态分布
设置delay、maxDealy、multiplier,使用 ExponentialBackOffPolicy(指数级重试间隔的实现 ),multiplier即指定延迟倍数,比如delay=5000l,multiplier=2,则第一次重试为5秒,第二次为10秒,第三次为20秒……
用于@Retryable重试失败后处理方法,此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理,要触发@Recover方法,那么在@Retryable方法上不能有返回值,只能是void才能生效
spring-retry 工具虽能优雅实现重试,但是存在两个不友好设计:
Throwable
子类,说明重试针对的是可捕捉的功能异常为设计前提的,但是我们希望依赖某个数据对象实体作为重试实体, 但 sping-retry框架必须强制转换为Throwable子类。Spring Retry 提倡以注解的方式对方法进行重试,重试逻辑是同步执行的,当抛出相关异常后执行重试, 如果你要以返回值的某个状态来判定是否需要重试,可能只能通过自己判断返回值然后显式抛出异常了。只读操作可以重试,幂等写操作可以重试,但是非幂等写操作不能重试,重试可能导致脏写,或产生重复数据。
@Recover
注解在使用时无法指定方法,如果一个类中多个重试方法,就会很麻烦。
NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试;
AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环;
SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略;
TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试;
CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行。
代码入口:是启动类的这个@EnableRetry注解,自动装配了一个RetryConfiguration.class的重试配置类
@SuppressWarnings("serial")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Component
public class RetryConfiguration extends AbstractPointcutAdvisor
implements IntroductionAdvisor, BeanFactoryAware, InitializingBean {
private Advice advice;
private Pointcut pointcut;
private RetryContextCache retryContextCache;
private List<RetryListener> retryListeners;
private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
private Sleeper sleeper;
private BeanFactory beanFactory;
//执行顺序是:@PostConstruct > afterPropertiesSet > initMethod
@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);
// 构建通知
this.advice = buildAdvice();
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;
}
/**
* Calculate a pointcut for the given retry annotation types, if any.
* @param retryAnnotationTypes the retry annotation types to introspect
* @return the applicable Pointcut object, or {@code null} if none
*/
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
ComposablePointcut result = null;
//注解类或方法解析生成切点
for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
if (result == null) {
result = new ComposablePointcut(filter);
}
else {
result.union(filter);
}
}
return result;
}
private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut {
private final MethodMatcher methodResolver;
AnnotationClassOrMethodPointcut(Class<? extends Annotation> annotationType) {
this.methodResolver = new AnnotationMethodMatcher(annotationType);
setClassFilter(new AnnotationClassOrMethodFilter(annotationType));
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationClassOrMethodPointcut)) {
return false;
}
AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other;
return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
}
}
//注解方法解析过滤
private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
private final AnnotationMethodsResolver methodResolver;
AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
super(annotationType, true);
this.methodResolver = new AnnotationMethodsResolver(annotationType);
}
@Override
public boolean matches(Class<?> clazz) {
return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
}
}
//注解方法解析
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() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (found.get()) {
return;
}
Annotation annotation = AnnotationUtils.findAnnotation(method,
AnnotationMethodsResolver.this.annotationType);
if (annotation != null) {
found.set(true);
}
}
});
return found.get();
}
}
}
AnnotationAwareRetryOperationsInterceptor( //注解解析重试操作拦截器)类是方法调用的关键类
关键代码如下:
// 基于AOP和springBoot2.x默认使用CGLib的动态代理对目标方法进行拦截,这个invoke方法就是会在方法调用的时候会被触发到
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//生成一个代理对象MethodInterceptor
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
if (delegate != null) {
return delegate.invoke(invocation);
}
else {
return invocation.proceed();
}
}
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) {
MethodInterceptor interceptor = NULL_INTERCEPTOR;
Retryable retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method, Retryable.class);
}
if (retryable != null) {
// 从spring容器中获取代理对象
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;
}
// 无状态代理对象
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
// 创建重试模板(模板方法模式)
RetryTemplate template = createTemplate(retryable.listeners());
// 设置重试策略
template.setRetryPolicy(getRetryPolicy(retryable));
// 设置补偿策略
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
return RetryInterceptorBuilder
// 构建无状态代理对象
.stateless().
retryOperations(template).label(retryable.label())
// 匹配回退策略
.recoverer(getRecoverer(target, method)).build();
}
// 有状态代理对象
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryContextCache(this.retryContextCache);
CircuitBreaker circuit = AnnotatedElementUtils.findMergedAnnotation(method, CircuitBreaker.class);
if (circuit == null) {
circuit = findAnnotationOnTarget(target, method, CircuitBreaker.class);
}
if (circuit != null) {
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();
}
RetryOperationsInterceptor类中实际操作的对象是retryTemplate,关键代码如下:
public class RetryOperationsInterceptor implements MethodInterceptor {
private RetryOperations retryOperations = new RetryTemplate();
private MethodInvocationRecoverer<?> recoverer;
private String label;
public void setLabel(String label) {
this.label = label;
}
public void setRetryOperations(RetryOperations retryTemplate) {
Assert.notNull(retryTemplate, "'retryOperations' cannot be null.");
this.retryOperations = retryTemplate;
}
// 代理对象会执行到xxxxInterceptorBuilder中构建的RetryOperationsInterceptor的invoke方法如下
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
String name;
if (StringUtils.hasText(this.label)) {
name = this.label;
}
else {
name = invocation.getMethod().toGenericString();
}
final String label = name;
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(
invocation, label) {
@Override
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute(RetryContext.NAME, this.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 (this.invocation instanceof ProxyMethodInvocation) {
context.setAttribute("___proxy___", ((ProxyMethodInvocation) this.invocation).getProxy());
try {
// 执行目标类的目标方法
return ((ProxyMethodInvocation) this.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 (this.recoverer != null) {
ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(invocation.getArguments(),
this.recoverer);
try {
Object recovered = this.retryOperations.execute(retryCallback, recoveryCallback);
return recovered;
}
finally {
RetryContext context = RetrySynchronizationManager.getContext();
if (context != null) {
context.removeAttribute("__proxy__");
}
}
}
return this.retryOperations.execute(retryCallback);
}
}
上面的invoke方法最终会调用RetryTemplate类中的几个execute()的重载方法如下:
最后会调用到doExecute()方法,这个方法就是spring-retry的核心方法:
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;
// Allow the retry policy to initialise itself...
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...
//重试同步上下文管理器,里面有一个private static final ThreadLocal context = new ThreadLocal();属性,使用ThreadLocal对各种重试策略的上下文做一个多线程隔了
RetrySynchronizationManager.register(context);
Throwable lastException = null;
boolean exhausted = false;
try {
// Give clients a chance to enhance the context...
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);
}
}
/*
* We allow the whole loop to be skipped if the policy or context already
* forbid the first try. This is used in the case of external retry to allow a
* recovery in handleRetryExhausted without the callback processing (which
* would throw an exception).
*/
// 执行对上下文的应策略的canRetry方法判断是否可以是否满足重试条件
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// Reset the last exception, so if we are successful
// the close interceptors will not think we failed...
lastException = null;
// 调用目标类的目标方法 目标方法执行成功则返回,执行失败则进入catch中捕获异常,进入重试调用
return retryCallback.doWithRetry(context);
}
//
catch (Throwable e) {
lastException = e;
try {
// 在上下文中注册一个异常,然后上面的while条件会一直的去执行canRetry是否可以重试,然后哦一直调用目标类的目标方法成功返回,然后不成功直到不满足重试条件则调用退出,不可能一直的while(true)死循环的调用,肯定要有一个出口的
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable", ex);
}
finally {
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);
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
}
}
/*
* 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);
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
}
}
由于之前做了一个停车缴费的项目,平台应用要和终端设备通过网络调用终端的先关接口,由于终端设备的网络是免费的软件搭建的网络(没有使用专线,使用专线就需要额外的费用,所以不划算)一直会出现掉线导致调用终端接口因为网络掉线而出现SocketTimeoutException异常,多以就尝试使用异步重试,多试几次的方式,后面就使用了这个spring-retry的类库做了一个优雅的重试,但是最终还是没有解决这种网络问题导致重试n多次都还是不行,但是这种重试机制在网络基本可靠一些其它适合spring-retry的场景还是很ok的,后面又花了点时间看了下spring-retry的源码对这个类库有了很好的掌握和了解,看源码其实是一种比较好的学习探索的方式,看大佬们是如何写出优雅漂亮、令人赏心悦目的代码的,从中可以学习大佬的设计思想和各种设计模式的运用;最后是使用了Tio的一个半开源的websocket类库写了一个clien的终端你程序跟平台应用建立websocket心跳连接来保持在线的,同时平台端可以给指定的client端发送websocket消息,client端收到消息后就可以在本地调用终端软件的接口来完成和终端的交互了,到此就是使用spring-retry的心得和总结。