Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities.

About this article

Today we retry to understand this thing, we should contact a lot in the process of daily work, such as docking a third party, it is difficult to ensure that each call can be normal response, as we all know external services are generally not reliable for the caller, especially under the condition of the network environment more bad, actually this is not just multilateral interaction, this system also is very normal, For example, service A calls service B, and service B suddenly drops. This is normal. However, we should take into account that a series of unknown problems may occur behind this normal situation. For example, I went to the third party to notify the purchase of a member, but the notification failed after the user paid the money. So this kind of serious and predictable problem, we should avoid in time, colloquially speaking, there is a word called “compensation mechanism”.

I think retry, mainly consider the following two scenarios:

First: is the occurrence of error, such as null pointer, request timeout, HTTP status exception and so on

Second: business errors, such as failure to modify a state, setting a certain data does not reach the desired value.

Frame natural support

As we mentioned above, retry scenarios are common, so there must be some framework that does this for us right out of the box.

  • Spring’s retry

  • Guava-Retry

Today we will focus on the second Guava retry mechanism because it is much more flexible in certain scenarios than the first. We’ll write a comparison later on why it’s flexible.

Guava Retrying is a flexible and convenient retry component that includes multiple retry strategies and is easy to extend. Let’s see how the author describes it:

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-Retrying you can customize the retries and also monitor the results and behavior of each retry. The most important retries based on Guava-style retries are really convenient.

The code shown

1. Pom files are imported

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

2. Define Callable to make retry calls

   / * * *@descSync members * We use Boolean variables to determine *@author yn
    * @dateBetter 2021/10/09 * /
   private static Callable<Boolean> syncMember = new Callable<Boolean>() {
   
       @Override
       public Boolean call(a) throws Exception {
       
           // Business implementation, call third party member interface
           String url = ConfigureUtil.get(Memberstants.MEMBER_URL);
           String result = HttpMethod.post(url, new ArrayList<BasicNameValuePair>());
           if(StringUtils.isEmpty(result)){
              throw new RemoteException("Failed to synchronize member. Please try again.");
           }
           
           List<Member> member = JSON.parseArray(result, Member.class);
           if(CollectionUtils.isNotEmpty(member)){
               // The synchronization succeeded
               return true;
           }
           // Synchronization failed
           return false; }};Copy the code

I’m going to say one more thing about why it’s called callable, but let’s look at Retryer at the bottom. Because of this call, the method to retry must be of type Callable.

3. Define Retry objects and set related policies

Retryer<Boolean> retryer = RetryerBuilder
                .<Boolean>newBuilder()
                // Runtime and Checked exceptions will be retried, but error will not be retried.
                .retryIfException()
                // Return false and retry
                .retryIfResult(Predicates.equalTo(false))
                // Reset the policy
                .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
                // Number of attempts
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                // Monitor mechanism
                .withRetryListener(new MyRetryListener<>())
                .build();
 
        try {
            retryer.call(updateReimAgentsCall);
        } catch (ExecutionException e) {
            logger.error("Failed to synchronize members, need to send a reminder email");
        } catch (RetryException e) {
            logger.error("Failed to synchronize members, need to send a reminder email");
        }
Copy the code

OK, three simple steps to the retry mechanism, and you can use it in your field.

Let’s see what the parameters of each step mean.

  • RetryerBuilder is a factory creator. You can customize the retry source and support multiple retry sources. You can configure the retry times or retry timeout, and you can configure the waiting interval to create Retryer instances.

  • The retry source of RetryerBuilder supports Exception and custom assertion objects, which are set by retryIfException and retryIfResult. Multiple and compatible objects are supported simultaneously.

  • RetryIfException: Runtime and Checked exceptions will be retried, but error will not be retried.

  • RetryIfRuntimeException retries only when runtime exceptions are thrown. Checked and Error are not retried.

  • RetryIfExceptionOfType allows us to retry only when certain exceptions occur, such as NullPointerException and IllegalStateException, which are Runtime exceptions.

Error can also be customized:

.retryIfExceptionOfType(Error.class)// Retry only when error is thrown
Copy the code

We can also specify the Callable method to retry at a specific return value, such as

// Return false and retry
.retryIfResult(Predicates.equalTo(false)) 
// Retry with _error
.retryIfResult(Predicates.containsPattern("_error$"))
Copy the code

There is also a problem. If we need to do some extra processing after a retry, such as sending an alarm email, we can use RetryListener. After each retry, Guava-Retrying automatically calls back the listeners we registered. Multiple RetryListeners can be registered and are called in the order in which they were registered. Beautiful. These questions are considered in every aspect:


public class MyRetryListener<Boolean> implements RetryListener {  
  
    @Override  
    public <Boolean> void onRetry(Attempt<Boolean> attempt) {  
  
        // The number of retries,(note: the first retry is actually the first call)
        System.out.print("[retry]time=" + attempt.getAttemptNumber());  
  
        // The delay from the first retry
        System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());  
  
        // Retry result: Abnormal termination or normal return
        System.out.print(",hasException=" + attempt.hasException());  
        System.out.print(",hasResult=" + attempt.hasResult());  
  
        // What causes the exception
        if (attempt.hasException()) {  
            System.out.print(",causeBy=" + attempt.getExceptionCause().toString());  
        } else {  
            // The result of a normal return
            System.out.print(",result=" + attempt.getResult());  
        }  
  
        // Bad practice: Added extra exception handling code
        try {  
            Boolean result = attempt.get();  
            System.out.print(",rude get=" + result);  
        } catch (ExecutionException e) {  
            System.err.println("this attempt produce exception."+ e.getCause().toString()); } System.out.println(); }}Copy the code

The listener event is set in this method:

/ / loading
.withRetryListener(new MyRetryListener<>())
Copy the code

OK, that’s all for today’s retry mechanism.

overtones

Thank you for reading, if you feel that you have learned something, please like, follow. Also welcome to have a question we comment below exchange

Come on! See you next time!

To share with you a few I wrote in front of a few SAO operation

Talk about different strategy patterns (Bookmarks)

Copy object, this operation is a little SAO!