This is the 15th day of my participation in Gwen Challenge


In daily development, we often encounter scenarios where we need to invoke external services and interfaces. External services are generally unreliable for callers, especially in the case of poor network environment. Network jitter can easily lead to request timeout and other exceptions. In this case, you need to use the failed retry policy to re-invoke the API interface to obtain external services. Retry strategies are also widely used in service governance to check whether a service is alive or not through periodic checks.

Spring Exception Retry framework Spring Retry

Spring Retry supports integration into Spring or Spring Boot projects, and it supports AOP’s aspect injection writing, so the AspectJweaver. Jar package must be introduced.

1. Introduce Maven dependencies

<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> < version > 1.1.2. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Aspectj < / groupId > < artifactId > aspectjweaver < / artifactId > < version > 1.8.6 < / version > < / dependency >Copy the code

2. Add the @retryable and @recover annotations

@retryable annotations are retried when an exception occurs over the annotated method

  • Value: specifies the exception to be retried
  • Include: Like value, is null by default. When exclude is also null, all exceptions are retried
  • Exclude: specifies that exceptions are not retried. The default value is null. If include is also empty, all exceptions are retried
  • Maxattem: Number of retries, default is 3
  • Backoff: the retry compensation mechanism is unavailable by default

@ Backoff annotations

  • Delay: Retry after the specified delay
  • Multiplier: Specifies the delay multiplier. For example, if delay= 5000L, Multiplier =2, the first retry is 5 seconds later, the second retry is 10 seconds, and the third retry is 20 seconds

@recover Annotation: When a specified number of retries are reached, the annotated method is called back and log processing can take place in the method. It is important to note that the callback occurs only when the exception is of the same type as the input parameter.

@Service public class RemoteService { @Retryable(value = {Exception.class}, maxAttempts = 5, backoff = @Backoff(delay = 5000L, multiplier = 1)) public void call() { System.out.println(LocalDateTime.now() + ": do something..." ); Throw new RuntimeException(localDatetime.now () + ": run call exception "); } @Recover public void recover(Exception e) { System.out.println(e.getMessage()); }Copy the code

3. Enable retry

Add the @enableretry annotation on the startup class to EnableRetry, or on services that use retry, or on the Configuration class.

It is recommended that all Enable configurations be added to the startup class so that the functions used can be clearly and uniformly managed.

@SpringBootApplication @EnableRetry public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println("Start app success."); RemoteService bean = context.getBean(RemoteService.class); bean.call(); }}Copy the code

4. Start the service and run the test

By calling the service in the launch class Context, we see the following print:

The T15:2019-03-09 and. 781: do something... The 2019-03-09 T15: therefore. 808: do something... The T15:2019-03-09 and. 835: do something... The 2019-03-09 T15: her. 861: do something... The 2019-03-09 T15:22:32. 887: do something... 2019-03-09T15:22:32.887: Operation call exceptionCopy the code

Guava-based retry component Guava-retryer

Look directly at the component author’s description of this component: This is a small extension to Google’s Guava Library, which allows you to work with different retrying strategies Arbitrary function call, such as something that talks to a remote service with flaky uptime. (This is a small extension of Google’s Guava library, Allows you to create configurable retry policies for arbitrary function calls, such as those for conversations with remote services with unstable runtime.

Step 1 Introduce maven coordinates:

< the dependency > < groupId > com. Making. Rholder < / groupId > < artifactId > guava - retrying < / artifactId > < version > 2.0.0 < / version > </dependency>Copy the code

1. Main interfaces and strategies

  • Attempt to do STH.
  • AttemptTimeLimiter: Time limit for a single task (if a single task times out, the current task is terminated)
  • BlockStrategies: Task blocking strategies (generally speaking, what to do when the current task is completed and the next task is not started…) , the default strategy is: blockstrategies. THREAD_SLEEP_STRATEGY that is to call thread. sleep(sleepTime);
  • RetryException: RetryException.
  • RetryListener: a custom RetryListener that can be used to log errors asynchronously.
  • StopStrategy: stop retry policy, which can be:
  • 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: never stopping, used when rotating until the desired result is returned;
  • StopAfterAttemptStrategy: Sets the maximum number of retries. If the maximum number of retries is exceeded, the retries are stopped and a retry exception is returned.
  • WaitStrategy: wait time policy (control time interval), and the result is the next execution time:
  • FixedWaitStrategy: Fixed waiting time strategy;
  • RandomWaitStrategy: random waiting time strategy (can provide a minimum and maximum waiting time, the waiting time is their interval random value)
  • IncrementingWaitStrategy: Incrementing wait time strategy (provide an initial value and step size, wait time increases with retry count)
  • ExponentialWaitStrategy: ExponentialWaitStrategy;
  • FibonacciWaitStrategy: Fibonacci wait duration strategy.
  • ExceptionWaitStrategy: Wait policy for abnormal duration.
  • CompositeWaitStrategy: CompositeWaitStrategy;

2. Determine whether to retry based on the result

Usage scenario: If the return value determines whether to retry.

Retry interface:

private static Callable<String> callableWithResult() { return new Callable<String>() { int counter = 0; public String call() throws Exception { counter++; System.out.println(LocalDateTime.now() + ": do something... " + counter); if (counter < 5) { return "james"; } return "kobe"; }}; }Copy the code

Testing:

public static void main(String[] args) { Retryer<String> retry = RetryerBuilder.<String>newBuilder() .retryIfResult(result -> ! result.contains("kobe")).build(); retry.call(callableWithResult()); }Copy the code

Output:

The 2019-03-09 T15: it is. 706: do something... 1 the T15 2019-03-09: it is. 710: do something... 2 the 2019-03-09 T15: it is. 711: do something... 3 the 2019-03-09 T15: it is. 711: do something... 4 the T15 2019-03-09: it is. 711: do something... 5Copy the code

3. Determine whether to retry based on the exception

Usage scenario: Determine whether to retry based on the type of exceptions thrown. Retry interface:

private static Callable<String> callableWithResult() { return new Callable<String>() { int counter = 0; public String call() throws Exception { counter++; System.out.println(LocalDateTime.now() + ": do something... " + counter); if (counter < 5) { throw new RuntimeException("Run exception"); } return "kobe"; }}; }Copy the code

Testing:

public static void main(String[] args) throws ExecutionException, RetryException{
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.neverStop())
            .build();
    retry.call(callableWithResult());
}
Copy the code

Output:

The 2019-03-09 T15:53:27. 682: do something... 1 the T15:2019-03-09 53:27. 686: do something... 2 the T15:2019-03-09 53:27. 686: do something... 3 the T15:2019-03-09 53:27. 687: do something... 4 the T15:2019-03-09 53:27. 687: do something... 5Copy the code

4. Retry policy — Set unlimited retry

Usage scenario: In the case of exceptions, infinite retry (default execution policy) until a valid result is returned.

 Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.neverStop())
            .build();
    retry.call(callableWithResult());
Copy the code

Retry policy – Sets the maximum number of retries

Usage scenario: If exceptions exist, the maximum number of retries is allowed. If the number of retries exceeds the threshold, an exception is thrown.

private static Callable<String> callableWithResult() { return new Callable<String>() { int counter = 0; public String call() throws Exception { counter++; System.out.println(LocalDateTime.now() + ": do something... " + counter); throw new RuntimeException("Run exception"); }}; }Copy the code

Testing:

public static void main(String[] args) throws ExecutionException, RetryException{
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .build();
    retry.call(callableWithResult());
}
Copy the code

Output:

The 2019-03-09 T16:02:29. 471: do something... 1 the T16 2019-03-09:02:29. 477: do something... 2 the T16 2019-03-09:02:29. 478: do something... 3 the T16 2019-03-09:02:29. 478: do something... 4 Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 4 attempts.Copy the code

6. Wait policy: Set a fixed retry wait period policy

Application scenario: Set the waiting interval for each retry to 10s.

 public static void main(String[] args) throws ExecutionException, RetryExceptio{
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
            .build();
    retry.call(callableWithResult());
}
Copy the code

Testing the output, we can see that the call interval is 10S:

The 2019-03-09 T16:06:34. 457: do something... 1 the T16 2019-03-09:06:44. 660: do something... 2 the T16 2019-03-09:06:54. 923: do something... 3 the T16 2019-03-09:07:05. 187: do something... 4 Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 4 attempts.Copy the code

7. Wait policy – Set a fixed growth policy for retry wait time

Scenario: Set the initial waiting time value and set a fixed growth step, but not set the maximum waiting time;

public static void main(String[] args) throws ExecutionException, RetryException {
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .withWaitStrategy(WaitStrategies.incrementingWait(1, SECONDS, 1, SECONDS))
            .build();

    retry.call(callableWithResult());
}
Copy the code

Test the output, it can be seen that the call interval is increased by 1 second:

The 2019-03-09 T18: now. 256: do something... 1 the 2019-03-09 T18:46:31. 260: do something... 2 the 2019-03-09 T18:46:33. 260: do something... 3 the 2019-03-09 T18:46:36. 260: do something... 4 Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 4 attempts.Copy the code

8. Wait policy – Set a strategy to increase the retry wait time exponentially

Usage scenario: Increase the waiting time exponentially according to the Multiplier value and set the maximum waiting time;

 public static void main(String[] args) throws ExecutionException, RetryExceptio{
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .withWaitStrategy(WaitStrategies.exponentialWait(1000, 10,SECONDS))
            .build();
    retry.call(callableWithResult());
}
Copy the code

This retry policy and entry is not very understand, well, check the source code:

@Immutable private static final class ExponentialWaitStrategy implements WaitStrategy { private final long multiplier; private final long maximumWait; public ExponentialWaitStrategy(long multiplier, long maximumWait) { Preconditions.checkArgument(multiplier > 0L, "multiplier must be > 0 but is %d", new Object[]{Long.valueOf(multiplier)}); Preconditions.checkArgument(maximumWait >= 0L, "maximumWait must be >= 0 but is %d", new Object[]{Long.valueOf(maximumWait)}); Preconditions.checkArgument(multiplier < maximumWait, "multiplier must be < maximumWait but is %d", new Object[]{Long.valueOf(multiplier)}); this.multiplier = multiplier; this.maximumWait = maximumWait; } public long computeSleepTime(Attempt failed datetempt) {double exp = Math. Pow (2.0d, (double)failedAttempt.getAttemptNumber()); long result = Math.round((double)this.multiplier * exp); if(result > this.maximumWait) { result = this.maximumWait; } return result >= 0L? result:0L; }}Copy the code

ExponentialWaitStrategy is an immutable internal class through the source code, the constructor checks into the parameter, the most important delay time calculation method computeSleepTime(), we can see the delay time calculation

  1. Calculate the number of failures as an exponent base 2
  2. The value constructor for the first step multiplies the first input arguments and then rounds them to get the delay time in milliseconds

Based on the above analysis, it can be seen that the interval of 1000 input parameters should be 2, 4, and 8s

Test output, it can be seen that the call interval is 2×1000, 4×1000, 8×1000:

The T19:2019-03-09 and. 905: do something... Miriam: 1 2019-03-09 T19. 908: do something... 2 the 2019-03-09 T19:11:29. 908: do something... 3 the 2019-03-09 T19: now. 909: do something... 4 Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 4 attempts.Copy the code

9. Wait policy – Set the retry wait time according to Fibonacci number policy

Usage scenario: Increase the wait time according to the Fibonacci sequence based on the multiplier value and set the maximum wait time. The Fibonacci sequence is 1, 1, 2, 3, 5, 8, 13, 21, 34…

public static void main(String[] args) throws ExecutionException, RetryException {
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .withWaitStrategy(WaitStrategies.fibonacciWait(1000, 10, SECONDS))
            .build();

    retry.call(callableWithResult());
}
Copy the code

The delay time is the product of the Fibonacci sequence and the first input parameter (milliseconds).

public long computeSleepTime(Attempt failedAttempt) { long fib = this.fib(failedAttempt.getAttemptNumber()); long result = this.multiplier * fib; if(result > this.maximumWait || result < 0L) { result = this.maximumWait; } return result >= 0L? result:0L; }Copy the code

Test output, it can be seen that the interval call is 1×1000, 1×1000, 2×1000:

The 2019-03-09 T19: alien. 903: do something... 1 the 2019-03-09 T19:28:44. 909: do something... 2 the 2019-03-09 T19: lend. 928: do something... 3 the 2019-03-09 T19:28:47. 928: do something... 4 Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 4 attempts.Copy the code

10. Wait policy – Combined retry wait duration policy

Application scenario: If existing policies do not meet application scenarios, multiple policies can be combined.

public static void main(String[] args) throws ExecutionException,     RetryException {
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(10))
            .withWaitStrategy(WaitStrategies.join(WaitStrategies.exponentialWait(1000, 100, SECONDS)
                    , WaitStrategies.fixedWait(2, SECONDS)))
            .build();

    retry.call(callableWithResult());
}
Copy the code

Again, look at the source code to understand what composition policies mean:

public long computeSleepTime(Attempt failedAttempt) {
    long waitTime = 0L;

    WaitStrategy waitStrategy;
    for(Iterator i$ = this.waitStrategies.iterator(); i$.hasNext(); waitTime += waitStrategy.computeSleepTime(failedAttempt)) {
        waitStrategy = (WaitStrategy)i$.next();
    }
    return waitTime;
}
Copy the code

It can be seen that the delay time of the combination policy is obtained by adding the delay time of multiple policies. ExponentialWait delay is 2,4,8,16,32… , fixedWait delay is 2,2,2,2,2… , so the total delay time is 4,6,10,18,34…

Test output:

The 2019-03-09 T19:46:45. 854: do something... 1 the 2019-03-09 T19:46:49. 859: do something... 2 the 2019-03-09 T19:46:55. 859: do something... 3 the 2019-03-09 T19:47:05. 859: do something... 4 the 2019-03-09 T19:47:23. 859: do something... 5 the 2019-03-09 T19:47:57. 860: do something... 6 the 2019-03-09 T19:49:03. 861: do something... 7 the 2019-03-09 T19:50:45. 862: do something... 8Copy the code

11. The RetryListener implements details of the retry process

Usage scenarios: Custom listeners that print details of the retry process separately, more for asynchronous logging, or special processing in the future.

public class MyRetryListener implements RetryListener { @Override public <V> void onRetry(Attempt<V> attempt) { System.out.println(("retry times=" + attempt.getAttemptNumber())); / / distance retry delay System. For the first time out. The println (" delay = "+ attempt. GetDelaySinceFirstAttempt ()); System.out.println("hasException=" + attempt. HasException ()); System.out.println("hasResult=" + attempt.hasResult()); / / what causes abnormal if (attempt. HasException ()) {System. Out. Println (" causeBy = "+ attempt. GetExceptionCause ()); } else {system.out.println ("result=" + attempt. GetResult ()); } // Add additional exception handling code try {Object result = attempt. Get (); System.out.println("rude get=" + result); } catch (ExecutionException e) { System.out.println("this attempt produce exception." + e.getCause()); }}Copy the code

Testing:

    public static void main(String[] args) throws ExecutionException, RetryException {
    Retryer<String> retry = RetryerBuilder.<String>newBuilder()
            .retryIfRuntimeException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(2))
            .withRetryListener(new MyRetryListener())
            .build();
    retry.call(callableWithResult());
}
Copy the code

Output:

The 2019-03-09 T16: slide. 097: do something... 1 retry times=1 delay=128 hasException=true hasResult=false causeBy=java.lang.RuntimeException: Run the exception this attempt produce exception. Java. Lang. RuntimeException: Run the exception T16 2019-03-09: slide. 102: do something... 2 retry times=2 delay=129 hasException=true hasResult=false causeBy=java.lang.RuntimeException: Run exception this attempt produce exception.java.lang.RuntimeException: Run exception Exception in thread "main" com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 2 attempts.Copy the code

conclusion

Both approaches are elegant retry strategies. Spring-retry configuration is simpler and functions are relatively simple. Guava is a boutique Java library from Google, and guava-Retry is very powerful. Compared with spring-retry, it is more selective in determining whether to Retry and can be used as a supplement to spring-retry.