Skip to content

Retryer

Filippo Squillace edited this page Aug 27, 2016 · 8 revisions

The following are wait strategies that can be used together with guava-retrying library. In particular, Guavaberry introduces exponential wait strategies that can be combined with jitter in order to guarantee a more reliable retry mechanism in case of calls from multiple competing clients towards a remote server.

ExponentialJitterWaitStrategy

This implementation of WaitStrategy is helpful whenever there are competing clients that are trying to call a remote server at the same time.

The main benefits of this strategy is that it avoids overwhelming the server by multiple requests by slowing clients down using the classic exponential backoff. Moreover, since the reason of an impaired server might be due to the wave of requests coming from the clients, this strategy introduce an element of randomness (aka jitter) that ensures the requests are spread out along a randomized interval that grows exponentially for each attempt. The retry is capped to a maximum timeout in order to avoid the interval to grow indefinitely.

The interval for the i-th retry is calculated in the following way:

  min_value = exponential_wait * (1 - randomization_factor)
  max_value = exponential_wait

Where exponential_wait is the exponential time calculated for the i-th attempt and randomization_factor is a double belonging to the interval [0.0, 1.0].

Choose the randomization factor wisely according to your use case. The more the randomization factor tends to zero the more the retry behavior will look like the classic exponential backoff. This can be useful whenever we know that the cause of an impaired server is not due to the competing clients. Conversely, the more the randomization factor tends to one the more the requests will be spread along the interval, allowing the server to digest the request more gradually and reducing contention between competing clients.

Example

The following creates a Callable that returns always null and print to standard output the last waiting time (in seconds):

    Callable<Boolean> callable = new Callable<Boolean>() {
        private long start = System.currentTimeMillis()/1000;
        public Boolean call() throws Exception {
            long end = System.currentTimeMillis()/1000;
            System.out.println("Last waiting time: " + (end - start) + " seconds");
            start = end;
            return null; // do something else useful here
        }
    };

Let's suppose we want to retry to the function call every time it returns null and with an exponential waiting strategy with randomization factor (jitter) to zero and maximum waiting time 15 seconds:

    Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
            .retryIfResult(Predicates.isNull())
            .withWaitStrategy(WaitStrategies.exponentialJitterWait(Duration.ofSeconds(15), 0.0D))
            .build();
    retryer.call(callable);

The result would be a perfect exponential backoff strategy with waiting time capped to 15 seconds:

Last waiting time: 0 seconds
Last waiting time: 1 seconds
Last waiting time: 2 seconds
Last waiting time: 4 seconds
Last waiting time: 8 seconds
Last waiting time: 15 seconds

Increasing the randomization factor by 0.5, allows to have a random interval between [exp/2, exp] for each retry attempt, where exp will be the actual exponential wait time:

...
.withWaitStrategy(WaitStrategies.exponentialJitterWait(Duration.ofSeconds(15), 0.5D))
...

The result will be:

Last waiting time: 0 seconds
Last waiting time: 1 seconds
Last waiting time: 2 seconds
Last waiting time: 2 seconds
Last waiting time: 7 seconds
Last waiting time: 13 seconds

ExponentialWaitStrategy

This is a refactor of the ExponentialWaitStrategy class from guava-retrying based on Duration instead of long for declaring the delays.

The main benefits of this strategy is that it avoids overwhelming the server by multiple requests by slowing clients down using the classic exponential backoff. The retry is capped to a maximum timeout in order to avoid the interval to grow indefinitely.

The timeout for the i-th attempt is calculated in the following way:

  exponential_wait = min(max_timeout, 2**(attempts-1))

Where attempt is the number of current attempts and max_timeout is the capped value for the calculated timeout.

CompositeJitterWaitStrategy

This implementation of WaitStrategy helps you combining an existing waiting strategy with a randomization factor.

The interval for the i-th retry is calculated in the following way:

  min_value = wait * (1 - randomization_factor)
  max_value = wait

Where wait is the wait time calculated for the i-th attempt by the existing waiting strategy and randomization_factor is a double belonging to the interval [0.0, 1.0].

Example

The following example creates a wait strategy combined with the FixedWaitStrategy in order to produce a waiting time between [5, 10]:

WaitStrategy ws = WaitStrategies.compositeJitterWait(
        com.github.rholder.retry.WaitStrategies.fixedWait(10, TimeUnit.SECONDS), 0.5D);