Cabbage Java self study room covers core knowledge

Guava – Retrying profile

In our development, API interface call exceptions are often encountered, any interface will have different probability of exceptions, for reentrant interface, in order to avoid service unavailability caused by accidental exceptions, retry mechanism is very necessary. Guava-retryiny is a very flexible retry component with multiple retry policies that can be easily extended. Guava-retrying is an open source component library for Google that abstracts more thoroughly, not just HTTP requests, but can be retried for any important resource service, such as database connections, file resources, etc.

1. The Maven dependencies

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

2. Retryer is introduced

Retryer provides a constructor to create a Retryer of a specified rule: this method can specify how a request is retried by passing in a policy of retry time, stop retry, retry interval wait, retry block, reject, and so on.

Retryer(@Nonnull AttemptTimeLimiter<V> attemptTimeLimiter,
		@Nonnull StopStrategy stopStrategy,
		@Nonnull WaitStrategy waitStrategy,
		@Nonnull BlockStrategy blockStrategy,
		@Nonnull Predicate<Attempt<V>> rejectionPredicate,
		@Nonnull Collection<RetryListener> listeners)
Copy the code
  • AttemptTimeLimiter – maximum timeout per retry, exceeding which a TimeoutException is thrown;
  • StopStrategy — Stop retry strategy;
  • WaitStrategy — waitStrategy between retries;
  • BlockStrategy — thread blocking strategy between retries;
  • RejectionPredicate — refusing to try to predicate;
  • Listeners of retry;

2.1. AttemptTimeLimiter interface (Timeout duration control)

The main purpose of this interface is to control the timeout of each retry.

public interface AttemptTimeLimiter<V> {
 
	V call(Callable<V> var1) throws Exception;
 
}
Copy the code

Generally, we create an implementation of this interface through the AttemptTimeLimiters class.

AttemptTimeLimiters

AttemptTimeLimiters is a factory class that produces an implementation of AttemptTimeLimiter, with three main methods:

  • NoTimeLimit () — Unlimited time
public static <V> AttemptTimeLimiter<V> noTimeLimit()
Copy the code
  • FixedTimeLimit () — Limit the timeout
public static <V> AttemptTimeLimiter<V> fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit)
 
public static <V> AttemptTimeLimiter<V> fixedTimeLimit(long duration, @Nonnull TimeUnit timeUnit, @Nonnull ExecutorService executorService)
Copy the code

Both methods implement total timeout control over the calling interface through a combination of duration and timeUnit.

2.2. StopStrategy Interface (Termination Strategy)

There is only one method in this interface, which, as the name suggests, is used to determine whether retries should be stopped.

public interface StopStrategy {

    boolean shouldStop(Attempt var1);
    
}
Copy the code

He passes Attempt as an argument, from which we can get the return value of the method, the exception thrown, the number of retries, and so on.

Attempt

public interface Attempt<V> {

	V get() throws ExecutionException;
	boolean hasResult();
	boolean hasException();
	V getResult() throws IllegalStateException;
	Throwable getExceptionCause() throws IllegalStateException;
	long getAttemptNumber();
	long getDelaySinceFirstAttempt();
    
}
Copy the code

StopStrategys

We can also produce StopStrategy objects using StopStrategys, which provides three static methods:

  • NeverStop () — neverStop
public static StopStrategy neverStop()
Copy the code
  • StopAfterAttempt () — Stop after a certain number of times
public static StopStrategy stopAfterAttempt(int attemptNumber)
Copy the code
  • StopAfterDelay () — Stops after a certain timeout
public static StopStrategy stopAfterDelay(long duration, @Nonnull TimeUnit timeUnit)
Copy the code

2.3. WaitStrategy Interface (Interval Policy)

The WaitStrategy interface contains only one method, which, as the name implies, is the duration of the interval.

public interface WaitStrategy {
 
	long computeSleepTime(Attempt var1);
 
}
Copy the code

WaitStrategies

Similarly, we can produce WaitStrategy using the WaitStrategies class, which provides a very powerful and rich static method:

  • NoWait () — no interval
public static WaitStrategy noWait()
Copy the code
  • FixedWait () – Specifies the interval
public static WaitStrategy fixedWait(long sleepTime, @Nonnull TimeUnit timeUnit)
Copy the code
  • FrandomWait () — Random time interval
public static WaitStrategy randomWait(long maximumTime, @Nonnull TimeUnit timeUnit);
 
public static WaitStrategy randomWait(long minimumTime, @Nonnull TimeUnit minimumTimeUnit, long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
Copy the code
  • IncrementingWait () — Linearly incrementing intervals
public static WaitStrategy incrementingWait(long initialSleepTime, @Nonnull TimeUnit initialSleepTimeUnit, long increment, @Nonnull TimeUnit incrementTimeUnit)
Copy the code

This method sets the initial interval and increment step.

  • ExponentialWait () — exponentially increments the interval
public static WaitStrategy exponentialWait();
 
public static WaitStrategy exponentialWait(long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
 
public static WaitStrategy exponentialWait(long multiplier, long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
Copy the code
  • FibonacciWait () — The interval between increments of the Fibonacci sequence
public static WaitStrategy fibonacciWait();
 
public static WaitStrategy fibonacciWait(long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
 
public static WaitStrategy fibonacciWait(long multiplier, long maximumTime, @Nonnull TimeUnit maximumTimeUnit);
Copy the code
  • ExceptionWait () — Interval when an exception is thrown
public static <T extends Throwable> WaitStrategy exceptionWait(@Nonnull Class<T> exceptionClass, @Nonnull Function<T, Long> function)
Copy the code

This is one of the most complex WaitStrategies. Once the last attempt throws an exception from exceptionClass and its subclasses, the callback method function is called with its return value as the interval until the next attempt.

  • Join () — Merges multiple policies

BlockStrategies can facilitate the creation of BlockStrategies

It provides only a static method, once specified, the thread will sleep in the interval, otherwise it will not

public static WaitStrategy join(WaitStrategy... waitStrategies)
Copy the code

2.4. BlockStrategy Interface (Blocking Strategy)

This policy specifies how many milliseconds the thread will sleep after this attempt.

public interface BlockStrategy {
 
	void block(long var1) throws InterruptedException;
 
}
Copy the code

BlockStrategies

BlockStrategies can facilitate the creation of BlockStrategies.

It only provides a static method, once specified, the thread will sleep in the interval, otherwise it will not:

public static BlockStrategy threadSleepStrategy()
Copy the code

2.5. Predicate Interface

The most important method of the Predicate interface is the Apply method, which returns whether attempts need to be rejected. Similar to AttemptTimeLimiters, Predicate is often created with Predicates.

@FunctionalInterface @GwtCompatible public interface Predicate<T> extends java.util.function.Predicate<T> { @CanIgnoreReturnValue boolean apply(@Nullable T var1); boolean equals(@Nullable Object var1); default boolean test(@Nullable T input) { return this.apply(input); }}Copy the code

Predicates

Predicates provides a very rich collection of static methods that can implement a wide variety of assertions, even in combination with or without assertions.

public static <T> Predicate<T> alwaysFalse(); public static <T> Predicate<T> isNull(); public static <T> Predicate<T> notNull(); public static <T> Predicate<T> not(Predicate<T> predicate); public static <T> Predicate<T> and(Iterable<? extends Predicate<? super T>> components); public static <T> Predicate<T> and(Predicate... components); public static <T> Predicate<T> and(Predicate<? super T> first, Predicate<? super T> second); public static <T> Predicate<T> or(Iterable<? extends Predicate<? super T>> components); public static <T> Predicate<T> or(Predicate... components); public static <T> Predicate<T> or(Predicate<? super T> first, Predicate<? super T> second); public static <T> Predicate<T> equalTo(@NullableDecl T target); public static Predicate<Object> instanceOf(Class<? > clazz); public static Predicate<Class<? >> subtypeOf(Class<? > clazz); public static <T> Predicate<T> in(Collection<? extends T> target); public static <A, B> Predicate<A> compose(Predicate<B> predicate, Function<A, ? extends B> function); public static Predicate<CharSequence> containsPattern(String pattern); public static Predicate<CharSequence> contains(Pattern pattern);Copy the code

2.6.wrap () — Let threads in the thread pool also have retry capability

As mentioned above, Retryer has a call method. As long as the retry policy is set and the corresponding method is encapsulated as the implementation of the Callable interface, the call method of Retryer can be called according to the predetermined retry policy.

But what if instead of executing directly, our method needs to be put into a thread pool? Retryer provides the Wrap interface implementation to encapsulate the retry policy of a method into a Callable implementation, allowing us to call directly from the thread pool:

public Retryer.RetryerCallable<V> wrap(Callable<V> callable)
Copy the code

3. Retryer creates tools

3.1. RetryerBuilder class

Because the Retryer class constructor has many parameters and is more complex, using RetryerBuilder is more concise and more commonly used. As follows:

Retryer<Boolean> Retryer = RetryerBuilder.<Boolean>newBuilder() // If Callable is null, try again .retryifResult (Predicates.<Boolean>isNull()) // IOException, retry. RetryIfExceptionOfType (IOException RuntimeException, and continue to retry. RetryIfRuntimeException () / / specified retry strategy, try again after three stop. WithStopStrategy (StopStrategies. StopAfterAttempt (3))  .build();Copy the code

4. Guava – Retrying

The RetryerBuilder builds retry trigger exception conditions, retry wait policies, and retry stop policies. The retry business logic is self-encapsulated by implementing the Callable interface to support any custom requirements.

  1. RetryerGuavaTemplate class implementation:
Public class RetryerGuavaTemplate {/** ** HTTP request retry encapsulation ** @param callable execution method * @param retryExceptions retry when specified exceptions occur, Public <T> T retryHttpWrapper(Callable<T> Callable, List<Class<? extends Throwable>> retryExceptions) { RetryerBuilder<T> builder = RetryerBuilder.<T>newBuilder() // Retry when a ConnectException occurs. RetryIfExceptionOfType (connectException.class) // Retry wait policy The initial wait is 1s, and the increase is 1s each time. For example, 1s for the first time, 2s for the second time, 3s for the third time, and so on... .withWaitStrategy(WaitStrategies.incrementingWait(1, TimeUnit.SECONDS, 1, TimeUnit. SECONDS)) / / try again after 3 stop withStopStrategy (StopStrategies. StopAfterAttempt (3)); // Add if (retryExceptions! = null && retryExceptions.size() > 0) { for (Class<? extends Throwable> e : retryExceptions) { builder.retryIfExceptionOfType(e); } } Retryer<T> retryer = builder.build(); try { return retryer.call(callable); } catch (ExecutionException | RetryException e) { throw new RuntimeException(e); } } public static void main(String[] args) { RetryerGuavaTemplate retryerGuavaTemplate = new RetryerGuavaTemplate(); retryerGuavaTemplate.retryHttpWrapper(() -> { System.out.println(LocalTime.now()); System.out.println("execute the retryer call."); throw new RuntimeException("retryer test..." ); }, Lists.newArrayList(RuntimeException.class)); }}Copy the code
  1. Test results:

In the test code, the number of retries is 3. The initial wait time in the retry wait policy is 1s, and each increment is 1s. Through the test log printing, as expected.

5. Guava-retrying combined with AOP implementation

  1. Add POM file dependencies:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
Copy the code
  1. Create the @retryer tag class:
@target (ElementType.METHOD) @Retention(retentionPolicy.runtime) public @interface Retryer { default 0; Class[] retryThrowable() default {}; Long maxDelayMsec() default 0; Int maxAttempt() default 0; }Copy the code
  1. Create a new AOP aspect:
@Slf4j @Aspect @Component public class RetryerAspect { @Around(value = "@annotation(Retryer)") public Object monitorAround(ProceedingJoinPoint pjp) throws Throwable { Method method; if (pjp.getSignature() instanceof MethodSignature) { MethodSignature signature = (MethodSignature) pjp.getSignature(); method = signature.getMethod(); } else { log.error("Monitor Annotation not at a method {}", pjp); return null; } Retryer retryerAnnotation = method.getDeclaredAnnotation(Retryer.class); if (retryerAnnotation.maxDelayMsec() <= 0 && retryerAnnotation.maxAttempt() <= 1) { return pjp.proceed(); } RetryerBuilder retryer = RetryerBuilder.newBuilder(); if (retryerAnnotation.waitMsec() > 0) { retryer.withWaitStrategy(fixedWait(retryerAnnotation.waitMsec(), TimeUnit.MILLISECONDS)); } if (retryerAnnotation.retryThrowable().length > 0) { for (Class retryThrowable : retryerAnnotation.retryThrowable()) { if (retryThrowable ! = null && Throwable.class.isAssignableFrom(retryThrowable)) { retryer.retryIfExceptionOfType(retryThrowable); } } } if (retryerAnnotation.maxDelayMsec() > 0) { retryer.withStopStrategy(StopStrategies.stopAfterDelay(retryerAnnotation.maxDelayMsec(), TimeUnit.MILLISECONDS)); } if (retryerAnnotation.maxAttempt() > 0) { retryer.withStopStrategy(StopStrategies.stopAfterAttempt(retryerAnnotation.maxAttempt())); } String retrylog = pjp.getTarget().getClass().getCanonicalName() + "." + method.getName(); return retryer.build().call(() -> { try { log.info("<RETRYER>" + retrylog); return pjp.proceed(); } catch (Throwable throwable) { if (throwable instanceof Exception) { throw (Exception) throwable; } else { throw new Exception(throwable); }}}); }}Copy the code
  1. Automatic retry using our custom @retryer implementation interface:
    @Retryer(retryThrowable = Exception.class, maxAttempt = 3, waitMsec = 2000)
    public Object retryerTest(String hello) {
        Map<String, String> result = null;
        try {
            System.out.println(LocalDateTime.now());
            result.put("name", hello);
        } catch (Exception e) {
            throw e;
        }
        return "";
    }
Copy the code