Series of instructions

A step-by-step implementation mechanism for Java Retry.

Java-retry source address

Scene import

Simple requirements

Product manager: to achieve a conditional, query user information service.

Xiaoming: Ok. no problem

code

  • UserService.java
public interface UserService {

    /** * Query user information based on conditions *@paramCondition conditions *@return* / User information
    User queryUser(QueryUserCondition condition);

}
Copy the code
  • UserServiceImpl.java
public class UserServiceImpl implements UserService {

    private OutService outService;

    public UserServiceImpl(OutService outService) {
        this.outService = outService;
    }

    @Override
    public User queryUser(QueryUserCondition condition) {
        outService.remoteCall();
        return newUser(); }}Copy the code

The conversation

Project manager: This service sometimes fails. Take a look.

Xiaoming: OutService is an external RPC service, but sometimes unstable.

Project Manager: If the call fails, you can retry the call several times. You take a look at retries

retry

Retry role

Retry is not suitable for all scenarios, such as invalid parameter verification and write operation (considering whether the write is idempotent or not).

Remote call timeout, sudden network interruption can be retried. Microservice governance frameworks usually have their own retry and timeout configurations. For example, dubbo can set retries=1 and timeout=500 to retry only once after a call fails. If the call fails after 500ms, the call fails.

For example, if an external RPC call or data entry fails, you can retry the call multiple times to improve the likelihood of success.

V1.0 supports retry versions

thinking

Xiaoming: I have other tasks at hand. That’s easy too. Five minutes to take him down.

implementation

  • UserServiceRetryImpl.java
public class UserServiceRetryImpl implements UserService {

    @Override
    public User queryUser(QueryUserCondition condition) {
        int times = 0;
        OutService outService = new AlwaysFailOutServiceImpl();

        while (times < RetryConstant.MAX_TIMES) {
            try {
                outService.remoteCall();
                return new User();
            } catch (Exception e) {
                times++;

                if(times >= RetryConstant.MAX_TIMES) {
                    throw newRuntimeException(e); }}}return null; }}Copy the code

V1.1 Proxy mode version

Easy to maintain

Project manager: I have seen your code, although the function has been implemented, but try to write a little easier to maintain.

Xiaoming: Ok. (Thinking, do you mean to write some notes or something?)

The proxy pattern

Provide a proxy for other objects to control access to that object.

In some cases, an object is inappropriate or cannot directly reference another object, and a proxy object can act as an intermediary between a client and a target object.

It is characterized by the fact that the proxy and delegate classes have the same interface.

implementation

Xiaoming thought of the proxy model that he had seen before, and thought that in this way, the original code would change less momentum, and it would be more convenient to change it later.

  • UserServiceProxyImpl.java
public class UserServiceProxyImpl implements UserService {

    private UserService userService = new UserServiceImpl();

    @Override
    public User queryUser(QueryUserCondition condition) {
        int times = 0;

        while (times < RetryConstant.MAX_TIMES) {
            try {
                return userService.queryUser(condition);
            } catch (Exception e) {
                times++;

                if(times >= RetryConstant.MAX_TIMES) {
                    throw newRuntimeException(e); }}}return null; }}Copy the code

V1.2 Dynamic proxy mode

Convenient to expand

Project Manager: Xiao Ming, here’s another method with the same problem. You can also add retry.

Xiaoming: Ok.

Xiao Ming thought, I am writing an agent, but then calmed down, what if there is another service to try again?

  • RoleService.java
public interface RoleService {

    /** * query *@paramUser User information *@returnWhether you have permission */
    boolean hasPrivilege(User user);

}
Copy the code

Code implementation

  • DynamicProxy.java
public class DynamicProxy implements InvocationHandler {

    private final Object subject;

    public DynamicProxy(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 {
                // When a proxy object calls a method of the real object, it automatically jumps to the invoke method of the handler object associated with the proxy object
                return method.invoke(subject, args);
            } catch (Exception e) {
                times++;

                if (times >= RetryConstant.MAX_TIMES) {
                    throw newRuntimeException(e); }}}return null;
    }

    /** * get the dynamic proxy **@paramRealSubject Proxy object */
    public static Object getProxy(Object realSubject) {
        // The real object we want to delegate is passed in, and its methods are finally called through the real object
        InvocationHandler handler = new DynamicProxy(realSubject);
        returnProxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler); }}Copy the code
  • The test code
@Test
public void failUserServiceTest(a) {
        UserService realService = new UserServiceImpl();
        UserService proxyService = (UserService) DynamicProxy.getProxy(realService);

        User user = proxyService.queryUser(new QueryUserCondition());
        LOGGER.info("failUserServiceTest: " + user);
}


@Test
public void roleServiceTest(a) {
        RoleService realService = new RoleServiceImpl();
        RoleService proxyService = (RoleService) DynamicProxy.getProxy(realService);

        boolean hasPrivilege = proxyService.hasPrivilege(new User());
        LOGGER.info("roleServiceTest: " + hasPrivilege);
}
Copy the code

V1.3 Dynamic proxy mode enhanced

dialogue

Project manager: Xiao Ming, your dynamic proxy approach is lazy, but some of our classes don’t have interfaces. This is a problem you need to solve.

Xiaoming: Ok. (who? Write service does not define interface.

  • ResourceServiceImpl.java
public class ResourceServiceImpl {

    /** * Verify resource information *@paramThe user into * refs@returnCheck whether */ passes the verification
    public boolean checkResource(User user) {
        OutService outService = new AlwaysFailOutServiceImpl();
        outService.remoteCall();
        return true; }}Copy the code

Bytecode technology

Xiaoming looked at the information on the net, the solution is still some.

  • CGLIB

CGLIB is a powerful, high-performance, and high-quality code generation library for extending JAVA classes and implementing interfaces at run time.

  • javassist

Javassist (Java Programming Assistant) makes Java bytecode manipulation easy.

It is a Java class library for editing bytecodes; It allows Java programs to define new classes at run time and modify class files when the JVM loads them.

Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level.

If users use source-level apis, they can edit class files without having to understand Java bytecode specifications.

The entire API is designed using only the Java language vocabulary. You can even specify the inserted bytecode as source text; Javassist compiles it dynamically.

Bytecode level apis, on the other hand, allow users to edit class files directly as other editors.

  • ASM

ASM is a general-purpose Java bytecode manipulation and analysis framework.

It can be used to modify existing classes or generate classes dynamically, directly in binary form.

ASM provides some generic bytecode conversion and analysis algorithms from which you can build custom complex conversion and code analysis tools.

ASM provides similar functionality to other Java bytecode frameworks, but focuses on performance.

Because it is designed and implemented as small and fast as possible, it is ideal for use in dynamic systems (and, of course, statically, for example, in compilers).

implementation

Xiao Ming took a look and chose to use CGLIB.

  • CglibProxy.java
public class CglibProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        int times = 0;

        while (times < RetryConstant.MAX_TIMES) {
            try {
                // Call the methods of the parent class through the proxy subclass
                return methodProxy.invokeSuper(o, objects);
            } catch (Exception e) {
                times++;

                if (times >= RetryConstant.MAX_TIMES) {
                    throw newRuntimeException(e); }}}return null;
    }

    /** * get the proxy class *@paramClazz class information *@returnProxy class result */
    public Object getProxy(Class clazz){
        Enhancer enhancer = new Enhancer();
        // Target object class
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // Use bytecode technology to create a subclass instance of the target object class as a proxy
        returnenhancer.create(); }}Copy the code
  • test
@Test
public void failUserServiceTest(a) {
   UserService proxyService = (UserService) new CglibProxy().getProxy(UserServiceImpl.class);

   User user = proxyService.queryUser(new QueryUserCondition());
   LOGGER.info("failUserServiceTest: " + user);
}

@Test
public void resourceServiceTest(a) {
   ResourceServiceImpl proxyService = (ResourceServiceImpl) new CglibProxy().getProxy(ResourceServiceImpl.class);
   boolean result = proxyService.checkResource(new User());
   LOGGER.info("resourceServiceTest: " + result);
}
Copy the code

V2.0 AOP implementation

dialogue

Project Manager: Xiao Ming, I’ve been thinking about a problem recently. The number of retries should be different for different services. Because services have different requirements for stability.

Xiaoming: Ok. (Thinking, it’s Friday after a week of retries.)

Xiao Ming was thinking about this problem until he got off work. It’s the weekend, so take some time to write a retry widget.

Design ideas

  • Technical support

spring

Java annotations

  • Annotations to define

Annotations can be used on methods to define the number of retries required

  • Annotations parsing

Interception specifies the method that needs to be retried, resolves the corresponding number of retries, and then performs the corresponding number of retries.

implementation

  • Retryable.java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

    /**
     * Exception type that are retryable.
     * @return exception type to retry
     */
    Class<? extends Throwable> value() default RuntimeException.class;

    /** * contains the first failure *@return the maximum number of attempts (including the first failure), defaults to 3
     */
    int maxAttempts(a) default 3;

}
Copy the code
  • RetryAspect.java
@Aspect
@Component
public class RetryAspect {

    @Pointcut("execution(public * com.github.houbb.retry.aop.. *. * (..) ) &&. "" +
                      "@annotation(com.github.houbb.retry.aop.annotation.Retryable)")
    public void myPointcut(a) {}@Around("myPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Method method = getCurrentMethod(point);
        Retryable retryable = method.getAnnotation(Retryable.class);

        //1
        int maxAttempts = retryable.maxAttempts();
        if (maxAttempts <= 1) {
            return point.proceed();
        }

        //2. Exception handling
        int times = 0;
        final Class<? extends Throwable> exceptionClass = retryable.value();
        while (times < maxAttempts) {
            try {
                return point.proceed();
            } catch (Throwable e) {
                times++;

                // The number of retries exceeds the maximum or is not an exception
                if(times >= maxAttempts || ! e.getClass().isAssignableFrom(exceptionClass)) {throw newThrowable(e); }}}return null;
    }

    private Method getCurrentMethod(ProceedingJoinPoint point) {
        try {
            Signature sig = point.getSignature();
            MethodSignature msig = (MethodSignature) sig;
            Object target = point.getTarget();
            return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw newRuntimeException(e); }}}Copy the code

Use of methods

  • fiveTimes()

The current method is retried five times. Retry condition: The service throws AopRuntimeExption

@Override
@Retryable(maxAttempts = 5, value = AopRuntimeExption.class)
public void fiveTimes() {
    LOGGER.info("fiveTimes called!");
    throw new AopRuntimeExption();
}
Copy the code
  • The test log
The 2018-08-08 15:49:33. 814 INFO [main] com. Making. Houbb. Retry. Aop) service. Impl. UserServiceImpl: 66 - fiveTimes called! The 2018-08-08 15:49:33. 815 INFO [main] com. Making. Houbb. Retry. Aop) service. Impl. UserServiceImpl: 66 - fiveTimes called! The 2018-08-08 15:49:33. 815 INFO [main] com. Making. Houbb. Retry. Aop) service. Impl. UserServiceImpl: 66 - fiveTimes called! The 2018-08-08 15:49:33. 815 INFO [main] com. Making. Houbb. Retry. Aop) service. Impl. UserServiceImpl: 66 - fiveTimes called! The 2018-08-08 15:49:33. 815 INFO [main] com. Making. Houbb. Retry. Aop) service. Impl. UserServiceImpl: 66 - fiveTimes called! java.lang.reflect.UndeclaredThrowableException ...Copy the code

V3.0 spring – retry version

dialogue

Coming to the company on Monday, the project manager talked with Xiao Ming again.

Project manager: The number of retries is sufficient, but retries should be strategic. For example, if the external call fails for the first time, it can wait 5 seconds for the next call. If it fails again, it can wait 10 seconds for the call.

Xiaoming: I see.

thinking

But it’s Monday, and I have a lot of other things to do.

Xiao Ming is thinking, no time to write this ah. See if you can find one online.

spring-retry

Spring Retry provides declarative Retry support for Spring applications. It is used for Spring for Spring batch processing, Spring integration, Apache Hadoop(and so on).

In a distributed system, to ensure the consistency of distributed data transactions, a retry operation is performed when the network jitter request may time out when the RPC interface is invoked or MQ is sent. MQ is the most popular way to retry, but if MQ is not introduced in your project, it is inconvenient.

Alternatively, developers can write their own retry mechanisms, but most are less elegant.

Annotated use

  • RemoteService.java

Retry condition: RuntimeException encountered

Retry times: 3

Retry policy: Wait 5 seconds after the retry, and the subsequent times will be two times of the original time.

Circuit breaker: If all retries fail, the recover() method is called.

@Service
public class RemoteService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);

    /** * calls the method */
    @Retryable(value = RuntimeException.class,
               maxAttempts = 3,
               backoff = @Backoff(delay = 5000L, multiplier = 2))
    public void call(a) {
        LOGGER.info("Call something...");
        throw new RuntimeException("RPC call exception");
    }

    /** * recover mechanism *@paramE * /
    @Recover
    public void recover(RuntimeException e) {
        LOGGER.info("Start do recover things....");
        LOGGER.warn("We meet ex: ", e); }}Copy the code
  • test
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class RemoteServiceTest {

    @Autowired
    private RemoteService remoteService;

    @Test
    public void test(a) { remoteService.call(); }}Copy the code
  • The log
The 2018-08-08 16:03:26. 1433-409 the INFO [main] C.G.H.R.S pring. Service. RemoteService: Call something... The 2018-08-08 16:03:31. 1433-414 the INFO [main] C.G.H.R.S pring. Service. RemoteService: Call something... The 2018-08-08 16:03:41. 1433-416 the INFO [main] C.G.H.R.S pring. Service. RemoteService: Call something... The 2018-08-08 16:03:41. 1433-418 the INFO [main] C.G.H.R.S pring. Service. RemoteService: Startdorecover things.... The 2018-08-08 16:03:41. 1433-425 WARN [main] C.G.H.R.S pring. Service. RemoteService: We meet the ex: java.lang.RuntimeException: RPC calls anomalies at com. Making. Houbb. Retry. Spring. Service. RemoteService. Call (RemoteService. Java: 38) ~ / classes / : na...Copy the code

Time points of the three calls:

2018-08-08 16:03:26.409 
2018-08-08 16:03:31.414
2018-08-08 16:03:41.416
Copy the code

defects

The Spring-Retry tool, while elegant, has two unfriendly designs:

One is that the retry entity is restricted to a Throwable subclass, indicating that retries are designed for catching functional exceptions, but we want to rely on a data object entity as a retry entity, but the Sping-Retry framework must cast it to a Throwable subclass.

Another is that the assertion object at the retry root uses a doWithRetry Exception instance, which does not conform to the return design of normal internal assertions.

Spring Retry advocates annotated retries of methods. The Retry logic is executed synchronously, and the “failure” of retries is Throwable. If you are trying to determine whether a Retry is needed based on the state of the returned value, you may have to judge the returned value yourself and then explicitly throw an exception.

The @Recover annotation cannot specify methods when used, which can be troublesome if there are multiple retry methods in a class.

Annotation is introduced

@EnableRetry

Indicates whether to retry.

The serial number attribute type The default value instructions
1 proxyTargetClass boolean false Indicates whether to create a subclass-based (CGLIB) proxy instead of a standard Java interface-based proxy.

@Retryable

The method that marks this annotation will retry if an exception occurs

The serial number attribute type The default value instructions
1 interceptor String “” Apply the interceptor bean name to the retryable()
2 value Class[] {} The exception type that can be retried.
3 label String “” Unique label for statistical reports. If it is not provided, the caller can choose to ignore it or provide a default value.
4 maxAttempts int 3 Maximum number of attempts (including the first failure). Default is 3.
5 backoff @Backoff @Backoff() Specifies the backoff property to retry this operation. The default is empty

@Backoff

The serial number attribute type The default value instructions
1 delay long 0 If not set, 1000 milliseconds is used by default
2 maxDelay long 0 Maximum retry wait time
3 multiplier long 0 Multiplier used to calculate the next delay delay (greater than 0 works)
4 random boolean false Random retry wait time

@Recover

Annotations for method calls to restore handlers. An appropriate recovery handler has a first parameter of type throwable (or a throwable subtype) and returns a value of the same type as the @retryable method. The first argument that can be thrown is optional (but methods without it will only be called). The subsequent parameters are populated sequentially from the argument list of the failed method.

Methodological use

Annotation just makes it easier to use, but more flexible. You can use the various methods provided.

  • SimpleDemo.java
public class SimpleDemo {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDemo.class);

    public static void main(String[] args) throws Exception {
        RetryTemplate template = new RetryTemplate();

        / / strategy
        SimpleRetryPolicy policy = new SimpleRetryPolicy();
        policy.setMaxAttempts(2);
        template.setRetryPolicy(policy);

        String result = template.execute(
                new RetryCallback<String, Exception>() {
                    @Override
                    public String doWithRetry(RetryContext arg0) {
                        throw newNullPointerException(); }},new RecoveryCallback<String>() {
                    @Override
                    public String recover(RetryContext context) {
                        return "recovery callback"; }}); LOGGER.info("result: {}", result); }}Copy the code
  • Perform log
16:30:52. 578. [the main] the DEBUG org. Springframework. Retry. Support. RetryTemplate - retry: Count = 0 16:30:52. [the main] 591 DEBUG org. Springframework. Retry. Support. RetryTemplate - CheckingforRethrow: count = 1 16:30:52. 591. [the main] the DEBUG org. Springframework. Retry. Support. RetryTemplate - retry: Count = 1 16:30:52. [the main] 591 DEBUG org. Springframework. Retry. Support. RetryTemplate - Checkingfor16:30:52 rethrow: count = 2. 591. [the main] the DEBUG org. Springframework. Retry. Support. RetryTemplate - retry failed last attempt: Count = 2 16:30:52. 592 [main] INFO com.github.houbb.retry.spring.com monway SimpleDemo - result: recovery callbackCopy the code

Spring – retry structure

An overview of

  • RetryCallback: Encapsulates the business logic you need to retry (doSth above)

  • RecoverCallback: Encapsulates the business logic you need to execute after multiple retries have failed (doSthWhenStillFail above)

  • RetryContext: A Retry context that can be used to pass parameters or status between multiple Retry or Retry and Recover (multiple doSth or doSth with doSthWhenStillFail)

  • RetryOperations: Defines the basic framework (template) for “retry”, which requires RetryCallback to be passed in, optionally with RecoveryCallback;

  • RetryListener: A typical “listener” that notifies “listeners” at different stages of retry (e.g. doSth, wait, etc.)

  • RetryPolicy: A policy or condition for retries, which can be as simple as multiple retries, or retries with a specified timeout (someCondition above).

  • BackOffPolicy: rollback policy for retry when an exception occurs in the service logic execution. If we need to retry, we may have to wait a certain amount of time (the server may be too busy, or it may collapse if the retries continue without interval), but this time can be zero, fixed, or random (see the rollback strategy in TCP’s congestion control algorithm). The rollback strategy is wait().

  • RetryTemplate: a concrete implementation of RetryOperations that combines RetryListener[], BackOffPolicy, and RetryPolicy.

Retry strategy

  • NeverRetryPolicy: RetryCallback is allowed to be called only once. Retry is not allowed

  • AlwaysRetryPolicy: Allows unlimited retries until success. Improper logic in this approach leads to an infinite loop

  • SimpleRetryPolicy: specifies a fixed retry policy. The maximum number of retries is three by default. RetryTemplate Specifies the default policy

  • TimeoutRetryPolicy: Timeout Period Retry policy. The default timeout is 1 second. Retries are allowed within the specified timeout period

  • ExceptionClassifierRetryPolicy: set different abnormal retry strategy, similar to retry strategy, the difference is that here only to distinguish the various abnormal retry

  • CircuitBreakerRetryPolicy: fusing features of retry strategy, it is necessary to set up three parameters openTimeout, resetTimeout and delegate

  • A CompositeRetryPolicy is composed of two retry policies. An optimistic retry policy is implemented as long as one policy allows retry, and a pessimistic retry policy is implemented as long as one policy does not allow retry. Regardless of the two retry policies, each policy in the CompositeRetryPolicy executes

Retry the rollback policy

The retry rollback policy specifies whether to retry immediately or wait a period of time for each retry.

By default, immediate retry is used. If you need to wait for a period of time to retry, you need to specify the BackoffRetryPolicy.

  • NoBackOffPolicy: no backout algorithm policy that retries immediately after each retry

  • FixedBackOffPolicy: A fixed-time backoff policy for sleeper and backOffPeriod, sleeper specifies the wait policy. By default, thread. sleep (the Thread sleeps) and backOffPeriod specifies the sleep duration (1 second by default)

  • UniformRandomBackOffPolicy: Random time retreat strategy, need to set the sleeper, minBackOffPeriod and maxBackOffPeriod, this strategy in [minBackOffPeriod, take a random between maxBackOffPeriod sleep time, MinBackOffPeriod defaults to 500 milliseconds and maxBackOffPeriod to 1500 milliseconds

  • ExponentialBackOffPolicy: Sleeper, initialInterval, maxInterval, and Multiplier parameters need to be set. InitialInterval specifies the initial sleep time (default: 100 milliseconds). MaxInterval specifies the maximum sleep time (default: 30 seconds). Multiplier Specifies the multiplier, that is, the next sleep time is the current sleep time x multiplier

  • Retreat ExponentialRandomBackOffPolicy: random index strategy, the introduction of random multiplier can realize random multiplier back

guava-retrying

The conversation

Xiao Hua: Our system also uses retry

Project Manager: Xiaoming used Spring-retry some time ago. It would be good to share it

Xiaoming: Spring-Retry has all the basic functions, but it must be controlled based on exceptions. If you are trying to determine whether a retry is needed based on the status of a returned value, you might have to determine the returned value yourself and explicitly throw an exception.

Xiao Hua: In our project, we want to retry according to the properties of the object. You can look at Guava-retry. I used it a long time ago and it looks good.

Xiaoming: Ok.

guava-retrying

The Guava-Retrying module provides a generic way to retry arbitrary Java code using guava predicate matching enhanced specific stop, retry, and exception-handling capabilities.

  • advantage

The Guava Retryer tool is similar to Spring-Retry in that it packages logical retries by defining the retries role. However, the Guava Retryer has a better policy definition. In addition to controlling the number of retries and retry frequency, the Guava Retryer tool is compatible with retry source definitions that support multiple exceptions or user-defined entity objects. Give the retry feature more flexibility.

Guava Retryer is thread-safe, entrance call logic USES Java. Util. Concurrent. Callable call () method

The code example

An introduction to case

When an exception is encountered, retry three times to stop

  • HelloDemo.java
public static void main(String[] args) {
    Callable<Boolean> callable = new Callable<Boolean>() {
        @Override
        public Boolean call(a) throws Exception {
            // do something useful here
            LOGGER.info("call...");
            throw newRuntimeException(); }}; Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfResult(Predicates.isNull()) .retryIfExceptionOfType(IOException.class) .retryIfRuntimeException() .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();
    try {
        retryer.call(callable);
    } catch(RetryException | ExecutionException e) { e.printStackTrace(); }}Copy the code
  • The log
The 2018-08-08 17:21:12. 442 INFO [main] com. Making. Houbb. Retry. Guava. HelloDemo: 41 - call... com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts. 2018-08-08 17:21:12.443 INFO [main] com.github.houbb.retry.guava.HelloDemo:41 - call... The 2018-08-08 17:21:12. 444 INFO [main] com. Making. Houbb. Retry. Guava. HelloDemo: 41 - call... at com.github.rholder.retry.Retryer.call(Retryer.java:174) at com.github.houbb.retry.guava.HelloDemo.main(HelloDemo.java:53) Caused by: java.lang.RuntimeException at com.github.houbb.retry.guava.HelloDemoThe $1.call(HelloDemo.java:42)
	at com.github.houbb.retry.guava.HelloDemoThe $1.call(HelloDemo.java:37)
	at com.github.rholder.retry.AttemptTimeLimiters$NoAttemptTimeLimit.call(AttemptTimeLimiters.java:78)
	at com.github.rholder.retry.Retryer.call(Retryer.java:160)
	... 1 more
Copy the code

Retry strategy

  • ExponentialBackoff.java

Retry times: 3

Retry policy: wait for 3S

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                .retryIfResult(Predicates.isNull())
                .retryIfExceptionOfType(IOException.class)
                .retryIfRuntimeException()
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            retryer.call(callable);
        } catch (RetryException | ExecutionException e) {
            e.printStackTrace();
        }

Copy the code
  • The log
The 2018-08-08 17:20:41. 653 INFO [main] com. Making. Houbb. Retry. Guava. ExponentialBackoff: 43 - call... The 2018-08-08 17:20:44. 659 INFO [main] com. Making. Houbb. Retry. Guava. ExponentialBackoff: 43 - call... The 2018-08-08 17:20:47. 664 INFO [main] com. Making. Houbb. Retry. Guava. ExponentialBackoff: 43 - call... com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts. at com.github.rholder.retry.Retryer.call(Retryer.java:174) at  com.github.houbb.retry.guava.ExponentialBackoff.main(ExponentialBackoff.java:56) Caused by: java.lang.RuntimeException at com.github.houbb.retry.guava.ExponentialBackoffThe $1.call(ExponentialBackoff.java:44)
	at com.github.houbb.retry.guava.ExponentialBackoffThe $1.call(ExponentialBackoff.java:39)
	at com.github.rholder.retry.AttemptTimeLimiters$NoAttemptTimeLimit.call(AttemptTimeLimiters.java:78)
	at com.github.rholder.retry.Retryer.call(Retryer.java:160)
	... 1 more
Copy the code

Guava – retrying profile

RetryerBuilder

RetryerBuilder is a factory creator. You can customize the retry source and support multiple retry sources. You can configure the retry times or retry timeout, and you can configure the waiting interval to create Retryer instances.

The retry source of RetryerBuilder supports Exception and custom assertion objects, which are set by retryIfException and retryIfResult. Multiple and compatible objects are supported simultaneously.

  • retryIfException

RetryIfException: Runtime and Checked exceptions will be retried, but error will not be retried.

  • retryIfRuntimeException

RetryIfRuntimeException retries only when runtime exceptions are thrown. Checked and Error are not retried.

  • retryIfExceptionOfType

RetryIfExceptionOfType allows us to retry only when certain exceptions occur, such as NullPointerException and IllegalStateException are Runtime exceptions, including custom errors.

Such as:

retryIfExceptionOfType(Error.class)// Retry only when error is thrown
Copy the code

Of course, we can retry only when specified exceptions occur, such as:

.retryIfExceptionOfType(IllegalStateException.class)
.retryIfExceptionOfType(NullPointerException.class)  
Copy the code

Or Predicate

.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),
Predicates.instanceOf(IllegalStateException.class))) 
Copy the code
  • retryIfResult

RetryIfResult can specify that your Callable method retries when it returns a value, for example

// Return false and retry
.retryIfResult(Predicates.equalTo(false))   

// Retry with _error
.retryIfResult(Predicates.containsPattern("_error$"))  
Copy the code
  • RetryListener

If we need to do some extra processing after a retry, such as logging exceptions, we can use RetryListener.

After each retry, Guava-Retrying automatically calls back the listeners we registered.

Multiple RetryListeners can be registered and are called in the order in which they were registered.

.withRetryListener(new RetryListener {      
 @Override    
   public <T> void onRetry(Attempt<T> attempt) {  
               logger.error("The [{}] call failed", attempt.getAttemptNumber()); }})Copy the code

The main interface

The serial number interface describe note
1 Attempt One mission
2 AttemptTimeLimiter Time limit for executing a single task If a single task times out, the current task is terminated
3 BlockStrategies Task blocking strategy What to do when the current task is executed and the next task is not started. The default policy is:BlockStrategies.THREAD_SLEEP_STRATEGY
4 RetryException Retry abnormal
5 RetryListener Custom retry listener Can be used to log errors asynchronously
6 StopStrategy Stopping a Retry Policy
7 WaitStrategy Waiting time strategy (Control interval), the result is the next execution time
8 Attempt One mission
9 Attempt One mission

StopStrategy

Three are offered:

  • StopAfterDelayStrategy

Set a maximum allowed execution time; For example, the maximum retry time is set to 10 seconds. If the retry time exceeds the maximum retry time, the task is terminated and RetryException is returned.

  • NeverStopStrategy

Do not stop, used for the need to keep rotating until the return of the desired results;

  • StopAfterAttemptStrategy

Set the maximum number of retries. If the number exceeds the maximum number of retries, the system stops retries and returns a retry exception.

WaitStrategy

  • FixedWaitStrategy

Fixed waiting time strategy;

  • RandomWaitStrategy

Random waiting time strategy (can provide a minimum and maximum waiting time, waiting time is its interval random value)

  • IncrementingWaitStrategy

Incremental wait time policy (provide an initial value and step size, wait time increases with retry count)

  • ExponentialWaitStrategy

Exponential waiting time strategy;

  • FibonacciWaitStrategy

Fibonacci waiting period strategy;

  • ExceptionWaitStrategy

Abnormal waiting strategy;

  • CompositeWaitStrategy

Compound waiting time strategy;

conclusion

The commonality and principle of elegant retry

Normal and retry are elegantly decoupled, and retry assertion condition instances or logical exception instances are the medium through which the two communicate.

Specify the retry interval, configure different retry policies, and set the retry timeout period to further ensure the retry validity and stability of the retry process.

Both use the command design mode, through the delegate retry object to complete the corresponding logical operation, at the same time internal encapsulation to achieve the retry logic.

Spring-retry and Guava-Retry tools are thread-safe retries that support logical retry in concurrent service scenarios.

Graceful retry applies to scenarios

Unstable dependency scenarios exist in the functional logic. Retry is required to obtain the expected result or re-execution of the logic does not end immediately. Such as remote interface access, data load access, data upload verification and so on.

For exception scenarios, retry is required and normal logic and retry logic are decoupled.

Retry schemes can also be considered for scenarios that require data-media-based interactions and wish to test execution logic through retry polling.

The conversation

Project Manager: I think Guava-Retry is good, but not convenient enough. Xiao Ming, why don’t you wrap one based on annotations?

Xiao Ming:…

Better implementation

Java Retry framework – Sisyphus