SpringBoot technology practice - SpringRetry retry framework

1, Environment construction

  1. Add spring retry dependency. Spring retry uses AOP implementation, so you also need to add AOP package
<!-- SpringRetry -->
<dependency>
	  <groupId>org.springframework.retry</groupId>
	  <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-aspects</artifactId>
</dependency>
  1. Official documents

2, RetryTemplate

2.1 RetryTemplate

  1. RetryTemplate encapsulates the basic operations of Retry
    • org.springframework.retry.support.RetryTemplate
  2. Listening, fallback policy and retry policy can be specified in RetryTemplate
  3. Just use the normal new RetryTemplate()

2.2 RetryListener

  1. RetryListener specifies the callback when an error occurs during execution
    • org.springframework.retry.RetryListener
package org.springframework.retry;

public interface RetryListener {

	/**
	 * Called at the beginning of task execution, only once
	 */
	<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);

	/**
	 * Called at the end of task execution (including retry), only once
	 */
	<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);

	/**
	 * Callback on error
	 */
	<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
}
  1. Specify in RetryTemplate after configuration

2.3 fallback strategy

2.3.1 FixedBackOffPolicy

  1. How long is the delay to continue the call when an error occurs
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(1000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
  1. Specify in RetryTemplate after configuration

2.3.2 ExponentialBackOffPolicy

  1. When an error occurs, it is delayed by the specified delay time for the first time and then by the index
// Exponential fallback (seconds), the first fallback is 1s, the second fallback is 2s, the third is 4 seconds, and the fourth is 8 seconds
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(1000L);
exponentialBackOffPolicy.setMultiplier(2);
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
  1. Specify in RetryTemplate after configuration

2.4 retry strategy

  1. The retry policy mainly specifies the number of retries when an error occurs
// Retry policy
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);
  1. Specify in RetryTemplate after configuration

2.5 RetryCallback

  1. RetryCallback is retrytemplate Callback executed when execute
    • public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E

2.6 core usage

  1. RetryTemplate can be used for simple use
  2. Configure retryTemplate
    • Specifies that the fallback policy is ExponentialBackOffPolicy
    • Specify the retry policy as SimpleRetryPolicy
    • Specifies the listener RetryListener
import com.codecoord.util.PrintUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

@Configuration
public class RetryTemplateConfig {

    /**
     * Inject retryTemplate
     */
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        ///Fallback fixed time (seconds)
       /* FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(1000L);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);*/

        // Exponential fallback (seconds), 1s for the first time and 2s for the second time
        ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
        exponentialBackOffPolicy.setInitialInterval(1000L);
        exponentialBackOffPolicy.setMultiplier(2);
        retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);

        // Retry policy
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(5);
        retryTemplate.setRetryPolicy(retryPolicy);

        // Set the listener, and open and close are executed once at startup and end respectively
        RetryListener[] listeners = {
                new RetryListener() {
                    @Override
                    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                        PrintUtil.print("open");
                        return true;
                    }
                    @Override
                    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                            Throwable throwable) {
                        PrintUtil.print("close");
                    }
                    @Override
                    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                            Throwable throwable) {
                        PrintUtil.print("onError");
                    }
                }
        };
        retryTemplate.setListeners(listeners);

        return retryTemplate;
    }
}

  1. Inject RetryTemplate into controller or service
@RestController
public class SpringRetryController {
    @Resource
    private RetryTemplate retryTemplate;
    private static int count = 0;

    @RequestMapping("/retry")
    public Object retry() {
        try {
            count = 0;
            retryTemplate.execute((RetryCallback<Void, RuntimeException>) context -> {
                // Business code
                // ....
                // Simulate throwing an exception
                ++count;
                throw new RuntimeException("Throw exception");
            });
        } catch (RuntimeException e) {
            System.out.println("Exception");
        }

        return "retry = " + count;
    }
}
  1. Access the retry interface and observe the log output
18:27:20.648 - http-nio-8888-exec-1 - open
18:27:20.649 - http-nio-8888-exec-1 - retryTemplate.execute implement
18:27:20.649 - http-nio-8888-exec-1 - onError
18:27:21.658 - http-nio-8888-exec-1 - retryTemplate.execute implement
18:27:21.658 - http-nio-8888-exec-1 - onError
18:27:23.670 - http-nio-8888-exec-1 - retryTemplate.execute implement
18:27:23.670 - http-nio-8888-exec-1 - onError
18:27:27.679 - http-nio-8888-exec-1 - retryTemplate.execute implement
18:27:27.679 - http-nio-8888-exec-1 - onError
18:27:35.681 - http-nio-8888-exec-1 - retryTemplate.execute implement
18:27:35.681 - http-nio-8888-exec-1 - onError
18:27:35.681 - http-nio-8888-exec-1 - close

3, EnableRetry

  1. @EnableRetry enables retry. When specified on the class, the method will execute by default and retry three times
  2. Define the service, open the @ EnableRetry annotation and specify @ Retryable to retry. Refer to the following section
import org.springframework.retry.annotation.Retryable;

public interface RetryService {

    /**
     * Retry method call
     */
    @Retryable
    void retryServiceCall();
}

import org.springframework.retry.annotation.EnableRetry;
import org.springframework.stereotype.Service;

@EnableRetry
@Service
public class RetryServiceImpl implements RetryService {

    @Override
    public void retryServiceCall() {
        PrintUtil.print("Method call..");
        throw new RuntimeException("Manual exception");
    }
}

  1. Inject service into controller
@RequestMapping("/retryAnnotation")
public Object retryAnnotation() {
    retryService.retryServiceCall();
    return "retryAnnotation";
}
  1. Will retry by default
18:46:48.721 - http-nio-8888-exec-1 - Method call..
18:46:49.724 - http-nio-8888-exec-1 - Method call..
18:46:50.730 - http-nio-8888-exec-1 - Method call..
java.lang.RuntimeException: Manual exception

4, Retryable

  1. Annotation on the method that needs to be retried
  2. There are several properties
    • Retryable annotation parameter
      • value: specify the exception to retry
      • include: like value, it is empty by default. When exclude is also empty, all exceptions will be retried
      • exclude: Specifies that the exception will not be retried. It is empty by default. When include is also empty, all exceptions will be retried
      • Maxattempts: number of retries. The default is 3
      • backoff: retry compensation mechanism, not available by default
    • @Backoff annotation retry compensation strategy
      • When the parameter is not set, the FixedBackOffPolicy (specify the waiting time) is used by default, and the retry wait is 1000ms
      • Set delay and use FixedBackOffPolicy (specify that the retry wait is evenly distributed between the two values when delay and maxDealy are set)
      • Set delay, maxDealy and multiplier, and use ExponentialBackOffPolicy (implementation of exponential retry interval). Multiplier specifies the delay multiple. For example, delay=5000L, multiplier=2, then the first retry is 5 seconds, the second is 10 seconds, and the third is 20 seconds
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

	/**
	 * Retry interceptor bean name to be applied for retryable method. Is mutually
	 * exclusive with other attributes.
	 * @return the retry interceptor bean name
	 */
	String interceptor() default "";

	/**
	 * Exception types that are retryable. Synonym for includes(). Defaults to empty (and
	 * if excludes is also empty all exceptions are retried).
	 * @return exception types to retry
	 */
	Class<? extends Throwable>[] value() default {};

	/**
	 * Exception types that are retryable. Defaults to empty (and if excludes is also
	 * empty all exceptions are retried).
	 * @return exception types to retry
	 */
	Class<? extends Throwable>[] include() default {};

	/**
	 * Exception types that are not retryable. Defaults to empty (and if includes is also
	 * empty all exceptions are retried).
	 * If includes is empty but excludes is not, all not excluded exceptions are retried
	 * @return exception types not to retry
	 */
	Class<? extends Throwable>[] exclude() default {};

	/**
	 * A unique label for statistics reporting. If not provided the caller may choose to
	 * ignore it, or provide a default.
	 *
	 * @return the label for the statistics
	 */
	String label() default "";

	/**
	 * Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
	 * retry policy is applied with the same policy to subsequent invocations with the
	 * same arguments. If false then retryable exceptions are not re-thrown.
	 * @return true if retry is stateful, default false
	 */
	boolean stateful() default false;

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

	/**
	 * @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
	 * Overrides {@link #maxAttempts()}.
	 * @date 1.2
	 */
	String maxAttemptsExpression() default "";

	/**
	 * Specify the backoff properties for retrying this operation. The default is a
	 * simple {@link Backoff} specification with no properties - see it's documentation
	 * for defaults.
	 * @return a backoff specification
	 */
	Backoff backoff() default @Backoff();

	/**
	 * Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
	 * returns true - can be used to conditionally suppress the retry. Only invoked after
	 * an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
	 * Other beans in the context can be referenced.
	 * For example:
	 * <pre class=code>
	 *  {@code "message.contains('you can retry this')"}.
	 * </pre>
	 * and
	 * <pre class=code>
	 *  {@code "@someBean.shouldRetry(#root)"}.
	 * </pre>
	 * @return the expression.
	 * @date 1.2
	 */
	String exceptionExpression() default "";

	/**
	 * Bean names of retry listeners to use instead of default ones defined in Spring context
	 * @return retry listeners bean names
	 */
	String[] listeners() default {};

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Backoff {

	/**
	 * Synonym for {@link #delay()}.
	 *
	 * @return the delay in milliseconds (default 1000)
	 */
	long value() default 1000;

	/**
	 * A canonical backoff period. Used as an initial value in the exponential case, and
	 * as a minimum value in the uniform case.
	 * @return the initial or canonical backoff period in milliseconds (default 1000)
	 */
	long delay() default 0;

	/**
	 * The maximimum wait (in milliseconds) between retries. If less than the
	 * {@link #delay()} then the default of
	 * {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
	 * is applied.
	 *
	 * @return the maximum delay between retries (default 0 = ignored)
	 */
	long maxDelay() default 0;

	/**
	 * If positive, then used as a multiplier for generating the next delay for backoff.
	 *
	 * @return a multiplier to use to calculate the next backoff delay (default 0 =
	 * ignored)
	 */
	double multiplier() default 0;

	/**
	 * An expression evaluating to the canonical backoff period. Used as an initial value
	 * in the exponential case, and as a minimum value in the uniform case. Overrides
	 * {@link #delay()}.
	 * @return the initial or canonical backoff period in milliseconds.
	 * @date 1.2
	 */
	String delayExpression() default "";

	/**
	 * An expression evaluating to the maximimum wait (in milliseconds) between retries.
	 * If less than the {@link #delay()} then the default of
	 * {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
	 * is applied. Overrides {@link #maxDelay()}
	 *
	 * @return the maximum delay between retries (default 0 = ignored)
	 * @date 1.2
	 */
	String maxDelayExpression() default "";

	/**
	 * Evaluates to a vaule used as a multiplier for generating the next delay for
	 * backoff. Overrides {@link #multiplier()}.
	 *
	 * @return a multiplier expression to use to calculate the next backoff delay (default
	 * 0 = ignored)
	 * @date 1.2
	 */
	String multiplierExpression() default "";

	/**
	 * In the exponential case ({@link #multiplier()} &gt; 0) set this to true to have the
	 * backoff delays randomized, so that the maximum delay is multiplier times the
	 * previous delay and the distribution is uniform between the two values.
	 *
	 * @return the flag to signal randomization is required (default false)
	 */
	boolean random() default false;

}
  1. On the method to be retried, configure the corresponding retry times, the exception type of the retry exception, set the fallback delay time, the retry policy, and the method listening name
@Component
public class PlatformClassService {
    @Retryable(
        // Retry the exception type of the exception
        value = {Exception.class},
        // max retries 
        maxAttempts = 5,
        // Set fallback delay time
        backoff = @Backoff(delay = 500),
        // Configure callback method name
        listeners = "retryListener"
    )
    public void call() {
        System.out.println("call...");
        throw new RuntimeException("Manual exception");
    }
}
// The initial delay is 2 seconds, and then the acceptance is 1.5 times delayed for retry. The total number of retries is 4
@Retryable(value = {Exception.class}, maxAttempts = 4, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
  1. The listening method is configured in the configuration class
/**
  * Annotation call
  */
@Bean
public RetryListener retryListener() {
    return new RetryListener() {
        @Override
        public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
            System.out.println("open context = " + context + ", callback = " + callback);
            // The subsequent call returns true to continue execution
            return true;
        }

        @Override
        public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                                                   Throwable throwable) {
            System.out.println("close context = " + context + ", callback = " + callback);
        }
        @Override
        public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                                                     Throwable throwable) {
            System.out.println("onError context = " + context + ", callback = " + callback);
        }
    };
}
  1. Call service
@RestController
public class SpringRetryController {
    @Resource
    private PlatformClassService platformClassService;
    
    @RequestMapping("/retryPlatformCall")
    public Object retryPlatformCall() {
        try {
            platformClassService.call();
        } catch (Exception e) {
            return "Attempt to call failed";
        }
        return "retryPlatformCall";
    }
}
  1. Call result

Keywords: Spring Boot

Added by silent on Sat, 15 Jan 2022 07:40:47 +0200