Elegant retry - Guava Retrying parsing

Posted by noimad1 on Sun, 22 Dec 2019 10:49:40 +0100

1, Retry scenario

The remote call encounters current restriction or timeout, but the idempotent of the request needs to be considered in the case of concurrency, which is not the problem to be discussed here

1. When to retry -- when there is an exception or Success is false

Retry policy: exception verification and result verification

2. When does it end? 3 retries

Termination policy

3. How long to wait? 200ms

Wait policy

4. Monitor retry process

2, Basic usage

Retryer<Boolean> retryer = RetryerBuilder
            .<Boolean>newBuilder()
            // If a runtime exception or checked exception is thrown, it will be retried, but if an error is thrown, it will not be retried.
            .retryIfException()
            // Custom specified return value also needs to be retried: return false also needs to be retried
            .retryIfResult(Predicates.equalTo(false))
            // Retry interval
            .withWaitStrategy(WaitStrategies.fixedWait(200, TimeUnit.MILLISECONDS))
            // Number of attempts
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .build();

retryer.call(() -> {});

1. Retryer is created by retryer builder. Retryer builder provides various methods to customize retrying strategy.

2. The content body of the retry must be a Callable implementation. The retry is based on the retry policy customized by the execution status (exception, return value) of the call method.

.retryIfResult(Predicates.containsPattern("_error$"))
.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),
                                        Predicates.instanceOf(IllegalStateException.class)))

3. Termination strategy

.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.withStopStrategy(StopStrategies.stopAfterDelay(30,TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.neverStop())

4. Waiting strategy

.withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))
.withWaitStrategy(WaitStrategies.incrementingWait(3, TimeUnit.SECONDS,1,TimeUnit.SECONDS))
.withWaitStrategy(WaitStrategies.fibonacciWait())

5. Retry listening

After each retry, the registered listener will be called back and executed in sequence. Listener must implement method of RetryListener.onRetry

6. Thread pool and time limit

.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))

7. Execution exception

RetryException: execution terminated or thread interrupt ed

ExecutionException: Callable execution exception was not retried

3, Code parsing

1. RetryerBuilder

    private AttemptTimeLimiter<V> attemptTimeLimiter;
    private StopStrategy stopStrategy;
    private WaitStrategy waitStrategy;
    private BlockStrategy blockStrategy;
    private Predicate<Attempt<V>> rejectionPredicate = Predicates.alwaysFalse();
    private List<RetryListener> listeners = new ArrayList<RetryListener>();
/**
     * Builds the retryer.
     *
     * @return the built retryer.
     */
    public Retryer<V> build() {
        AttemptTimeLimiter<V> theAttemptTimeLimiter = attemptTimeLimiter == null ? AttemptTimeLimiters.<V>noTimeLimit() : attemptTimeLimiter;
        StopStrategy theStopStrategy = stopStrategy == null ? StopStrategies.neverStop() : stopStrategy;
        WaitStrategy theWaitStrategy = waitStrategy == null ? WaitStrategies.noWait() : waitStrategy;
        BlockStrategy theBlockStrategy = blockStrategy == null ? BlockStrategies.threadSleepStrategy() : blockStrategy;

        return new Retryer<V>(theAttemptTimeLimiter, theStopStrategy, theWaitStrategy, theBlockStrategy, rejectionPredicate, listeners);
    }

2. AttemptTimeLimiter

Direct execution

    private static final class NoAttemptTimeLimit<V> implements AttemptTimeLimiter<V> {
        @Override
        public V call(Callable<V> callable) throws Exception {
            return callable.call();
        }
    }

Thread pool execution, execution time limit

private static final class FixedAttemptTimeLimit<V> implements AttemptTimeLimiter<V> {

        private final TimeLimiter timeLimiter;
        private final long duration;
        private final TimeUnit timeUnit;


        @Override
        public V call(Callable<V> callable) throws Exception {
            return timeLimiter.callWithTimeout(callable, duration, timeUnit, true);
        }
    }

SimpleTimeLimiter : ExecutorService

@Override
  public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration,
      TimeUnit timeoutUnit, boolean amInterruptible) throws Exception {
    checkNotNull(callable);
    checkNotNull(timeoutUnit);
    checkArgument(timeoutDuration > 0, "timeout must be positive: %s",
        timeoutDuration);
    Future<T> future = executor.submit(callable);
    try {
      if (amInterruptible) {
        try {
          return future.get(timeoutDuration, timeoutUnit);
        } catch (InterruptedException e) {
          future.cancel(true);
          throw e;
        }
      } else {
        return Uninterruptibles.getUninterruptibly(future, 
            timeoutDuration, timeoutUnit);
      }
    } catch (ExecutionException e) {
      throw throwCause(e, true);
    } catch (TimeoutException e) {
      future.cancel(true);
      throw new UncheckedTimeoutException(e);
    }
  }

3. Retryer

    /**
     * Executes the given callable. If the rejection predicate
     * accepts the attempt, the stop strategy is used to decide if a new attempt
     * must be made. Then the wait strategy is used to decide how much time to sleep
     * and a new attempt is made.
     *
     * @param callable the callable task to be executed
     * @return the computed result of the given callable
     * @throws ExecutionException if the given callable throws an exception, and the
     *                            rejection predicate considers the attempt as successful. The original exception
     *                            is wrapped into an ExecutionException.
     * @throws RetryException     if all the attempts failed before the stop strategy decided
     *                            to abort, or the thread was interrupted. Note that if the thread is interrupted,
     *                            this exception is thrown and the thread's interrupt status is set.
     */
    public V call(Callable<V> callable) throws ExecutionException, RetryException {
        long startTime = System.nanoTime();
        for (int attemptNumber = 1; ; attemptNumber++) {
            Attempt<V> attempt;
            try {
                V result = attemptTimeLimiter.call(callable);
                attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
            } catch (Throwable t) {
                attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
            }

            for (RetryListener listener : listeners) {
                listener.onRetry(attempt);
            }

            if (!rejectionPredicate.apply(attempt)) {
                return attempt.get();
            }
            if (stopStrategy.shouldStop(attempt)) {
                throw new RetryException(attemptNumber, attempt);
            } else {
                long sleepTime = waitStrategy.computeSleepTime(attempt);
                try {
                    blockStrategy.block(sleepTime);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RetryException(attemptNumber, attempt);
                }
            }
        }
    }

Attempt to perform a task at a time

ResultAttempt Result execution result, current execution times, current execution time
ExceptionAttempt Throwable execution exception, current execution times, current execution time

Predict judgment conditions

AndPredicate  OrPredicate

Topics: Programming