[Best Practices] how elegant retry

This article flavors: Iced Bayberry expected to read: 20 minutes

Explanation

Recently the company engaged in activities, need to rely on a third party interface, the testing phase and there is nothing abnormal situation, but sometimes rely interface because of an internal error is returned on the line system abnormalities found that although the probability is small, but always because of this alarm always bad, not to mention the dead letter queue messages troublesome operation and maintenance also need to be re-delivered, so adding retry mechanism is imperative.

Retry mechanism to protect network systems reduce the volatility of short-term impact dependent services are not available to bring, so that the system can be a protective mechanism more stable operation. Let you already solid as the dog's system is more stable on a steady increase.

1

For convenience, assume that the way we want to retry as follows:

@Slf4j
@Component
public class HelloService {

    private static AtomicLong helloTimes = new AtomicLong();

    public String hello(){
        long times = helloTimes.incrementAndGet();
        if (times % 4 != 0){
            log.warn("发生异常,time:{}", LocalTime.now() );
            throw new HelloRetryException("发生Hello异常");
        }
        return "hello";
    }
}
复制代码

Call at:

@Slf4j
@Service
public class HelloRetryService implements IHelloService{

    @Autowired
    private HelloService helloService;

    public String hello(){
        return helloService.hello();
    }
}
复制代码

In other words, every tune four times this interface will be successful once.

Manually retry

The first to use the most simple way, directly into the retry time of the call:

// 手动重试
public String hello(){
    int maxRetryTimes = 4;
    String s = "";
    for (int retry = 1; retry <= maxRetryTimes; retry++) {
        try {
            s = helloService.hello();
            log.info("helloService返回:{}", s);
            return s;
        } catch (HelloRetryException e) {
            log.info("helloService.hello() 调用失败,准备重试");
        }
    }
    throw new HelloRetryException("重试次数耗尽");
}
复制代码

Output is as follows:

发生异常,time:10:17:21.079413300
helloService.hello() 调用失败,准备重试
发生异常,time:10:17:21.085861800
helloService.hello() 调用失败,准备重试
发生异常,time:10:17:21.085861800
helloService.hello() 调用失败,准备重试
helloService返回:hello
service.helloRetry():hello
复制代码

4 a program retry in a very short time and then returned successfully.

Although this may seem to solve the problem, but in practice, because there is no retry interval, is likely to depend on the time of the service has not yet recovered from network anomalies, so most likely next several calls are a failure.

Furthermore, such a large amount of code that needs modifications intrusive, obviously, not elegant.

3.png

Proxy mode

The above approach due to the need for extensive changes to the business code, although to achieve the function, but too intrusive original code maintainability.

So little need to use a more elegant way, without directly modifying the business code, then how to do it?

In fact, very simple, direct business outside and then wrapped in a layer of code on the line, proxy mode comes into play here.

@Slf4j
public class HelloRetryProxyService implements IHelloService{
   
    @Autowired
    private HelloRetryService helloRetryService;
    
    @Override
    public String hello() {
        int maxRetryTimes = 4;
        String s = "";
        for (int retry = 1; retry <= maxRetryTimes; retry++) {
            try {
                s = helloRetryService.hello();
                log.info("helloRetryService 返回:{}", s);
                return s;
            } catch (HelloRetryException e) {
                log.info("helloRetryService.hello() 调用失败,准备重试");
            }
        }
        throw new HelloRetryException("重试次数耗尽");
    }
}
复制代码

In this way, retry logic will be done by proxy class, business class original logic do not need to modify, and later want to modify retry logic only need to modify this class on the line, clear division of labor. For example, now you want to add a delay between retries, only you need to do a little modification can be:

@Override
public String hello() {
    int maxRetryTimes = 4;
    String s = "";
    for (int retry = 1; retry <= maxRetryTimes; retry++) {
        try {
            s = helloRetryService.hello();
            log.info("helloRetryService 返回:{}", s);
            return s;
        } catch (HelloRetryException e) {
            log.info("helloRetryService.hello() 调用失败,准备重试");
        }
        // 延时一秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    throw new HelloRetryException("重试次数耗尽");
}
复制代码

Although the agency model to be more elegant, but if you rely on the service a lot of time to each service creates a proxy class is apparently too much trouble, and in fact, retry logic are similar, nothing more than the number of retries and the delay is not Like it. If each class to write such a long list of similar code, obviously, not elegant!

4.png

JDK dynamic proxies

At this time, the dynamic proxy on the debut. Just write a proxy processing class, you can start a dog, Kandao 99.

@Slf4j
public class RetryInvocationHandler implements InvocationHandler {

    private final Object subject;

    public RetryInvocationHandler(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int times = 0;

        while (times < RetryConstant.MAX_TIMES) {
            try {
                return method.invoke(subject, args);
            } catch (Exception e) {
                times++;
                log.info("times:{},time:{}", times, LocalTime.now());
                if (times >= RetryConstant.MAX_TIMES) {
                    throw new RuntimeException(e);
                }
            }

            // 延时一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 获取动态代理
     *
     * @param realSubject 代理对象
     */
    public static Object getProxy(Object realSubject) {
        InvocationHandler handler = new RetryInvocationHandler(realSubject);
        return Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);
    }

}
复制代码

Coming out of unit senses:

 @Test
public void helloDynamicProxy() {
    IHelloService realService = new HelloService();
    IHelloService proxyService = (IHelloService)RetryInvocationHandler.getProxy(realService);

    String hello = proxyService.hello();
    log.info("hello:{}", hello);
}
复制代码

Output is as follows:

hello times:1
发生异常,time:11:22:20.727586700
times:1,time:11:22:20.728083
hello times:2
发生异常,time:11:22:21.728858700
times:2,time:11:22:21.729343700
hello times:3
发生异常,time:11:22:22.729706600
times:3,time:11:22:22.729706600
hello times:4
hello:hello
复制代码

After retry four times the output Hello, in line with expectations.

Dynamic agent can retry logic are placed in an apparently direct than using the proxy class a lot easier, and more elegant.

But do not get too excited, because the agent HelloService here is a simple class that does not rely on other classes, so the direct creation is no problem, but if the agent class is dependent on other classes Spring container management, then this ways will throw an exception, because there is no instance is dependent agent is injected into the instance created.

In this case, it is more complex, the need to obtain from the Spring container has been assembled, the proxy instance is needed, and then create a proxy class instance, and to the Spring container to manage, so you do not always re create a new instance of the proxy class.

Man of few words said, is to roll up its sleeves and dry.

timg.jpg

A new class of tool for obtaining proxy instance:

@Component
public class RetryProxyHandler {

    @Autowired
    private ConfigurableApplicationContext context;

    public Object getProxy(Class clazz) {
        // 1. 从Bean中获取对象
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getAutowireCapableBeanFactory();
        Map<String, Object> beans = beanFactory.getBeansOfType(clazz);
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        if (entries.size() <= 0){
            throw new ProxyBeanNotFoundException();
        }
        // 如果有多个候选bean, 判断其中是否有代理bean
        Object bean = null;
        if (entries.size() > 1){
            for (Map.Entry<String, Object> entry : entries) {
                if (entry.getKey().contains(PROXY_BEAN_SUFFIX)){
                    bean = entry.getValue();
                }
            };
            if (bean != null){
                return bean;
            }
            throw new ProxyBeanNotSingleException();
        }

        Object source = beans.entrySet().iterator().next().getValue();
        Object source = beans.entrySet().iterator().next().getValue();

        // 2. 判断该对象的代理对象是否存在
        String proxyBeanName = clazz.getSimpleName() + PROXY_BEAN_SUFFIX;
        Boolean exist = beanFactory.containsBean(proxyBeanName);
        if (exist) {
            bean = beanFactory.getBean(proxyBeanName);
            return bean;
        }

        // 3. 不存在则生成代理对象
        bean = RetryInvocationHandler.getProxy(source);

        // 4. 将bean注入spring容器
        beanFactory.registerSingleton(proxyBeanName, bean);
        return bean;
    }
}
复制代码

Using JDK dynamic proxy:

@Slf4j
public class RetryInvocationHandler implements InvocationHandler {

    private final Object subject;

    public RetryInvocationHandler(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int times = 0;

        while (times < RetryConstant.MAX_TIMES) {
            try {
                return method.invoke(subject, args);
            } catch (Exception e) {
                times++;
                log.info("retry times:{},time:{}", times, LocalTime.now());
                if (times >= RetryConstant.MAX_TIMES) {
                    throw new RuntimeException(e);
                }
            }

            // 延时一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 获取动态代理
     *
     * @param realSubject 代理对象
     */
    public static Object getProxy(Object realSubject) {
        InvocationHandler handler = new RetryInvocationHandler(realSubject);
        return Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);
    }

}
复制代码

So far, the main code is complete, modify HelloService class, add a dependent:

@Slf4j
@Component
public class HelloService implements IHelloService{

    private static AtomicLong helloTimes = new AtomicLong();

    @Autowired
    private NameService nameService;

    public String hello(){
        long times = helloTimes.incrementAndGet();
        log.info("hello times:{}", times);
        if (times % 4 != 0){
            log.warn("发生异常,time:{}", LocalTime.now() );
            throw new HelloRetryException("发生Hello异常");
        }
        return "hello " + nameService.getName();
    }
}
复制代码

NameService actually very simple, the purpose is to create dependency injection only test whether the normal operation of Bean.

@Service
public class NameService {

    public String getName(){
        return "Frank";
    }
}
复制代码

Come out of the test:

@Test
public void helloJdkProxy() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
    IHelloService proxy = (IHelloService) retryProxyHandler.getProxy(HelloService.class);
    String hello = proxy.hello();
    log.info("hello:{}", hello);
}
复制代码
hello times:1
发生异常,time:14:40:27.540672200
retry times:1,time:14:40:27.541167400
hello times:2
发生异常,time:14:40:28.541584600
retry times:2,time:14:40:28.542033500
hello times:3
发生异常,time:14:40:29.542161500
retry times:3,time:14:40:29.542161500
hello times:4
hello:hello Frank
复制代码

Perfect, so do not worry about dependencies injection, because to get from the Spring container Bean objects are already configured injection. Of course, this takes into account only a single case of Bean's case, consider a little more perfect, to determine what type of container Bean is Singleton or Prototype, Singleton is as if it is above this operation, if it is every time a new proxy Prototype class object.

In addition, here are using JDK dynamic proxy, so there is a natural defect, if you want to be a proxy class does not implement any interfaces, then it can not create proxy object, this approach will not work.

EDaBTlbkyvbhmng.jpg

CGLib dynamic proxy

Now that you have when it comes to the JDK dynamic proxy, it must mention CGLib dynamic agents. Agents have to use JDK dynamic proxy class is required, not all classes can be proxied, but CGLib dynamic proxies is just to solve this problem.

CGLib create a dynamic proxy class:

@Slf4j
public class CGLibRetryProxyHandler implements MethodInterceptor {
    private Object target;//需要代理的目标对象

    //重写拦截方法
    @Override
    public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
        int times = 0;

        while (times < RetryConstant.MAX_TIMES) {
            try {
                return method.invoke(target, arr);
            } catch (Exception e) {
                times++;
                log.info("cglib retry :{},time:{}", times, LocalTime.now());
                if (times >= RetryConstant.MAX_TIMES) {
                    throw new RuntimeException(e);
                }
            }

            // 延时一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    //定义获取代理对象方法
    public Object getCglibProxy(Object objectTarget){
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(objectTarget.getClass());
        enhancer.setCallback(this);
        Object result = enhancer.create();
        return result;
    }
}
复制代码

Want to change the dynamic agent with CGLib, replace what these two lines of code:

// 3. 不存在则生成代理对象
//        bean = RetryInvocationHandler.getProxy(source);
CGLibRetryProxyHandler proxyHandler = new CGLibRetryProxyHandler();
bean = proxyHandler.getCglibProxy(source);
复制代码

start testing:

@Test
public void helloCGLibProxy() {
    IHelloService proxy = (IHelloService) retryProxyHandler.getProxy(HelloService.class);
    String hello = proxy.hello();
    log.info("hello:{}", hello);

    hello = proxy.hello();
    log.info("hello:{}", hello);
}
复制代码
hello times:1
发生异常,time:15:06:00.799679100
cglib retry :1,time:15:06:00.800175400
hello times:2
发生异常,time:15:06:01.800848600
cglib retry :2,time:15:06:01.801343100
hello times:3
发生异常,time:15:06:02.802180
cglib retry :3,time:15:06:02.802180
hello times:4
hello:hello Frank
hello times:5
发生异常,time:15:06:03.803933800
cglib retry :1,time:15:06:03.803933800
hello times:6
发生异常,time:15:06:04.804945400
cglib retry :2,time:15:06:04.805442
hello times:7
发生异常,time:15:06:05.806886500
cglib retry :3,time:15:06:05.807881300
hello times:8
hello:hello Frank
复制代码

This great, the perfect solution to defects caused by JDK dynamic proxy. Elegant index rose a lot.

But the program still exists a problem that needs to be intrusive modifications to the original logic, need to be adjusted in each instance where the agency is called, so the existing code will still bring more changes.

fuuTyTbkyvbhmsa.jpg

Spring AOP

Non-intrusive want to modify the original logic? Want to achieve a comment to retry? Not be able to achieve a perfect Spring AOP do? Use AOP to invoke section set as a target, you can add some additional logic before and after the target method invocation.

Create a comment:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    int retryTimes() default 3;
    int retryInterval() default 1;
}
复制代码

There are two parameters, retryTimes represents the maximum number of retries, retryInterval Representative retry interval.

Then on the method needs to be retried plus notes:

@Retryable(retryTimes = 4, retryInterval = 2)
public String hello(){
    long times = helloTimes.incrementAndGet();
    log.info("hello times:{}", times);
    if (times % 4 != 0){
        log.warn("发生异常,time:{}", LocalTime.now() );
        throw new HelloRetryException("发生Hello异常");
    }
    return "hello " + nameService.getName();
}
复制代码

Then, the final step, the preparation of AOP section:

@Slf4j
@Aspect
@Component
public class RetryAspect {

    @Pointcut("@annotation(com.mfrank.springboot.retry.demo.annotation.Retryable)")
    private void retryMethodCall(){}

    @Around("retryMethodCall()")
    public Object retry(ProceedingJoinPoint joinPoint) throws InterruptedException {
        // 获取重试次数和重试间隔
        Retryable retry = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(Retryable.class);
        int maxRetryTimes = retry.retryTimes();
        int retryInterval = retry.retryInterval();

        Throwable error = new RuntimeException();
        for (int retryTimes = 1; retryTimes <= maxRetryTimes; retryTimes++){
            try {
                Object result = joinPoint.proceed();
                return result;
            } catch (Throwable throwable) {
                error = throwable;
                log.warn("调用发生异常,开始重试,retryTimes:{}", retryTimes);
            }
            Thread.sleep(retryInterval * 1000);
        }
        throw new RetryExhaustedException("重试次数耗尽", error);
    }
}
复制代码

start testing:

@Autowired
private HelloService helloService;

@Test
public void helloAOP(){
    String hello = helloService.hello();
    log.info("hello:{}", hello);
}
复制代码

Output is as follows:

hello times:1
发生异常,time:16:49:30.224649800
调用发生异常,开始重试,retryTimes:1
hello times:2
发生异常,time:16:49:32.225230800
调用发生异常,开始重试,retryTimes:2
hello times:3
发生异常,time:16:49:34.225968900
调用发生异常,开始重试,retryTimes:3
hello times:4
hello:hello Frank
复制代码

This is quite elegant, and be able to get a comment retry, simply do better.

IStGDBbkyvbhmow.jpg

Spring retry comment

In fact there is a fairly complete in Spring retry mechanism, is more useful than the upper section, you do not need to re-create the wheel yourself.

So let's take a look at how good this wheel so.

Introduced first necessary retry jar package:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
复制代码

Then add or configure the startup class @EnableRetry class notes, then added to the method needs to be retried @Retryable comment (ah? Like custom annotations like me? Actually copied my notes! [Manual funny])

@Retryable
public String hello(){
    long times = helloTimes.incrementAndGet();
    log.info("hello times:{}", times);
    if (times % 4 != 0){
        log.warn("发生异常,time:{}", LocalTime.now() );
        throw new HelloRetryException("发生Hello异常");
    }
    return "hello " + nameService.getName();
}
复制代码

By default, it will retry three times, the retry interval is 1 second. Of course, we can also customize the number of retries and interval. I just realized this is a function of hair in front of the same.

But in Spring retry mechanism also supports many useful features, for example, you can specify only an exception to retry a particular type, so if thrown other types of anomalies will not be retried, you can retry control more granular. The default is empty, all exceptions will have to try again.

@Retryable{value = {HelloRetryException.class}}
public String hello(){2
    ...
}
复制代码

You may also be used to include and exclude specify which exceptions include or exclude retry.

MaxAttemps can specify the maximum number of retries, the default is 3 times.

Bean name can retry interceptor disposed in the interceptor.

It provided a unique label by the retry flag for the statistics export.

May be used to add exceptionExpression abnormal expression, performed after the exception is thrown, to determine whether a subsequent retry.

Further, the retry mechanism also supports Spring backoff compensation mechanism to set the retry, retry interval can be set, and supports multiple retry delay set.

for example:

@Retryable(value = {HelloRetryException.class}, maxAttempts = 5,
           backoff = @Backoff(delay = 1000, multiplier = 2))
public String hello(){
    ...
}
复制代码

This method call will be thrown after the retries HelloRetryException exception, the maximum number of retries is 5, the first retry interval 1s, then 2 times the size of the increment, a second retry interval 2s, third times for 4s, for the fourth time 8s.

Retry mechanism also supports the use @Recover annotations to carry out remedial work, when the specified number of retries, it will call this method, you can operate the log records in the process.

It is worth noting that you want @ Recover notes into force, it needs to be @Retryable labeled with the method in the same class, and is marked @Retryable method does not return a value, otherwise it will not work.

After a @Recover annotation and if so, the number of retries reaches the maximum number, if no exception is thrown in the method @Recover labeled, it will not throw the original exception.

@Recover
public boolean recover(Exception e) {
    log.error("达到最大重试次数",e);
    return false;
}
复制代码

In addition to using external annotations, Spring Retry also supports the use of code directly when you call to retry:

@Test
public void normalSpringRetry() {
    // 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试
    Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
    exceptionMap.put(HelloRetryException.class, true);

    // 构建重试模板实例
    RetryTemplate retryTemplate = new RetryTemplate();

    // 设置重试回退操作策略,主要设置重试间隔时间
    FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
    long fixedPeriodTime = 1000L;
    backOffPolicy.setBackOffPeriod(fixedPeriodTime);

    // 设置重试策略,主要设置重试次数
    int maxRetryTimes = 3;
    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);

    retryTemplate.setRetryPolicy(retryPolicy);
    retryTemplate.setBackOffPolicy(backOffPolicy);

    Boolean execute = retryTemplate.execute(
        //RetryCallback
        retryContext -> {
            String hello = helloService.hello();
            log.info("调用的结果:{}", hello);
            return true;
        },
        // RecoverCallBack
        retryContext -> {
            //RecoveryCallback
            log.info("已达到最大重试次数");
            return false;
        }
    );
}
复制代码

At this point only advantage is that you can set multiple retry strategy:

NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试

AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环

SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略

TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试

ExceptionClassifierRetryPolicy:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试

CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate

CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,
悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行
复制代码

As can be seen, retry mechanism in Spring is still quite perfect, than the above write their own AOP aspects more powerful.

Here still need to be reminded that, due to the Spring Retry uses Aspect enhanced, so there will inevitably pit using Aspect - Internal method calls, the caller method if @Retryable annotated and callee are in the same class, then retry will fail.

But still there are some deficiencies, retry mechanism Spring's support for exceptions only capture, but can not check the return value.

dtFxiMbkyvbhlzo.jpg

Guava Retry

Finally, introduce another retry weapon --Guava Retry.

Compared Spring Retry, Guava Retry having greater flexibility, may be determined whether to retry check value based on the return.

Let's look a little chestnut:

First introduced jar package:

<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>
复制代码

Then with a small Demo to feel:

@Test
public void guavaRetry() {
    Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
        .retryIfExceptionOfType(HelloRetryException.class)
        .retryIfResult(StringUtils::isEmpty)
        .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
        .withStopStrategy(StopStrategies.stopAfterAttempt(3))
        .build();

    try {
        retryer.call(() -> helloService.hello());
    } catch (Exception e){
        e.printStackTrace();
    }
}
复制代码

Create a Retryer instance, then use the example of the method needs to be retried in the call, you can set up a retry mechanism in many ways, such as using retryIfException to reset all abnormal retry using retryIfExceptionOfType method to set the specified anomalies test, to use retryIfResult do not meet the expected return results retry using retryIfRuntimeException approach to retry all RuntimeException.

There are five methods with at the beginning, used to retry strategy / tactics wait / Block Policy / single task execution time limit / Custom listeners set to achieve a more robust exception handling.

By combining with Spring AOP, you can achieve more powerful than Spring Retry retry function.

Under careful comparison, the characteristics of Guava Retry can provide are:

  1. You can set the time limit of a single execution of the task, if the timeout exception is thrown.
  2. Retry listener may be provided for performing additional processing.
  3. You can set the task blocking strategy, that can set the current retry is completed, the next retry period before beginning to do something.
  4. Use to set more flexible policies can be combined by stop and wait for retry strategy strategies, such as length and up to 10 calls waiting index, random length of time, and never stop, and so on.

GBvgTpbkyvbhlEB.jpg

to sum up

This article progressive approach to multiple retries posture was a 360 degree teaching, from the simplest manually retry, to use a static proxy, then JDK dynamic proxies and CGLib dynamic agent, to Spring AOP, are process hand-made wheels, and finally introduces two wheels currently relatively easy to use, is a Spring Retry, use simple and crude, and born with the Spring framework, an annotation to get all things, the other is the Guava Retry, does not depend on Spring framework, self-contained, use more flexible and powerful.

Personally I think that, in most scenarios, Spring Retry retry mechanism has been strong enough to provide the additional flexibility provided Guava Retry if you do not use Spring Retry on the great. Of course, the specific conditions, but the absence of necessary, do not encourage repeat-create the wheel, research clearly still need to re-think yourself first wheels others.

This article this came to an end, and another day to complete the finished article, the purpose of writing is to summarize and share, I believe that best practice can be summed up and accumulated, they are in most scenarios applicable, these best practices will gradually accumulate in the process, become more important than experience things. Do not forget summarized as experience, and summed up the content they will not be lost.

If you have a better idea for a retry, welcome to discuss the exchange, I also welcome the attention of the public number for message exchange.

1565529015677.png

Guess you like

Origin juejin.im/post/5d50b663e51d4561c75f27dc