Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

Xiao Hei encountered a problem in the development. The module I was in charge of needed to call a tripartite service interface to query information, and the query results directly affected the processing of subsequent business logic.

This interface occasionally times out due to network problems, so that my business logic can not continue to process;

How to solve this problem? , small black first thought is retry, if failed to call again.

The question is, what if it fails again? Try again. We loop, say five times, and if all fails, the task service becomes unavailable and the call ends.

What if I want to make 5 calls between calls? First one second, then three seconds, then five seconds?

Small black found that things are not so simple, if they are easy to BUG ah.

On second thought, this is common quite common, there should be wheels on the net, look. Accidentally let me find it, ha ha.

Guava Retryer

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.

With Guava Retryer you can customize the retries and also monitor the results and behavior of each retry. The most important retries based on Guava style are really convenient.

Introduction of depend on

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

Quick start

Callable<Boolean> callable = new Callable<Boolean>() { public Boolean call() throws Exception { return true; // do something useful here } }; Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfResult(Predicates.<Boolean>isNull()) // Retry when callable returns null. RetryIfExceptionOfType (ioException.class) // Callable throws IOException retry. RetryIfRuntimeException () // Callable thrown RuntimeException retry. WithStopStrategy (StopStrategies stopAfterAttempt (3)) / / try again after 3 stop. The build (); try { retryer.call(callable); } catch (RetryException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }Copy the code

Retry if Callable’s call() method returns null and throws IOException or RuntimeException; Will stop after trying three times and throw a RetryException containing information about the last failed attempt; If any other exceptions pop up in the call() method, it will be wrapped and re-invoked in ExecutionException.

Exponential Backoff

Exponential compensation, according to an Exponential backoff on Wiki, is an algorithm that, through feedback, slows down a process exponentially, gradually finding the right rate.

In Ethernet, this algorithm is usually used for scheduling retransmission after a conflict. Delay retransmission is determined based on the time slot and number of retransmission attempts.

After c collisions (such as request failures), a random value between 0 and 2^ C-1 is chosen as the number of time slots.

For the first collision, each sender will wait 0 or 1 time slot to send. After the second collision, the sender will wait between 0 and 3 timesslots (calculated by 2^ 2-1) to send. After the third collision, the sender will wait between 0 and 7 (calculated by 2^ 3-1) time slots to send. And so on…

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfExceptionOfType(IOException.class) .retryIfRuntimeException() .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, Timeunit.minutes)) // index back.withstopstrategy (stopstrategies.neverstop ()) // neverStop retry.build();Copy the code

Create an always-retry retry that increases exponentially after each retry failure, up to a maximum of 5 minutes. After 5 minutes, try again every 5 minutes from then on.

Fibonacci Backoff

The Fibonacci sequence refers to a sequence like this:

0,1,1,2,3,5,8,13,21,34,55,89…

The sequence starts with the third term, and each term is equal to the sum of the previous two terms.

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfExceptionOfType(IOException.class) .retryIfRuntimeException() .withWaitStrategy(WaitStrategies.fibonacciWait(100, 2, Timeunit.minutes)) // Fibonacci withStopStrategy(stopStrategies.neverStop ()).build();Copy the code

Create an always-retry retry and wait with an increased Fibonacci retreat interval after each retry failure until a maximum of 2 minutes. After 2 minutes, try again every 2 minutes from then on.

Similar to the exponential retreat strategy, the Fibonacci retreat strategy follows a pattern of waiting longer and longer after each failed attempt.

The Performance of the two strategies has been specially tested by the University of Leeds in the UK. Compared with the exponential retreat strategy, Fibonacci retreat strategy may have better performance and throughput.

Retry listener

If you need to do something extra when retries occur, such as sending email notifications, you can use RetryListener. Guava Retryer automatically calls back listeners after each retry and supports registering multiple listeners.

@Slf4j class DiyRetryListener<Boolean> implements RetryListener { @Override public <Boolean> void OnRetry (Attempt < Boolean > Attempt) {the info (" retries: {} ", Attempt getAttemptNumber ()); The info (" distance first retry delay: {} ", attempt. GetDelaySinceFirstAttempt ()); If (attempt. HasException ()) {log. The error (" abnormal reason: ", attempt. GetExceptionCause ()); }else {system.out.println (" {}" + attempt. GetResult ()); }}}Copy the code

Once you have defined your listeners, you need to register them in Retryer.

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfResult(Predicates.<Boolean>isNull()) // Retry when callable returns null. RetryIfExceptionOfType (ioException.class) // Callable throws IOException retry. RetryIfRuntimeException () // Callable thrown RuntimeException retry. WithStopStrategy (StopStrategies stopAfterAttempt (3)) / / try again after 3 stop. WithRetryListener (new DiyRetryListener<Boolean>()) // Register listener.build();Copy the code

summary

Guava Retryer not only supports multiple options for retry strategies, but also decouples the processing of business logic in Callable, separate from retry logic, which is much better than writing the loop itself. Guava is really powerful.