Eureka highly available Client retry mechanism: retryableeureka httpclient

Posted by goldfiles on Tue, 15 Feb 2022 08:59:35 +0100

Eureka highly available Client retry mechanism: retryableeureka httpclient

Here are a few questions I asked myself when I read the source code. First put them forward. I hope people who read this article will read them with questions, and then preliminarily introduce the EurekaHttpClient system. Later, I will talk about RetryableEurekaHttpClient in detail

1. How does Eureka Client register with Eureka Server cluster? If the ServiceUrl of my Client side is configured with multiple Eureka Service addresses, will the Client initiate registration with each Server?

2. Eureka Server has replication behavior, that is, it copies its own instance information to other Eureka Server nodes. Since there is replication behavior, why not configure only one in the ServiceUrl of Eureka Client? I register on the Server and the Server copies my information to other Eureka Server nodes, Does it mean that only one ServiceUrl of Eureka Client is configured?

3. If the Eureka Client has multiple serviceurls configured, will the Client maintain communication with that Eureka Server (registration, renewal, heartbeat, etc.)? Is it the first or random?

defaultZone: http://server3:50220/eureka,http://server1:50100/eureka,http://server2:50300/eureka


RetryableEurekaHttpClient inherits from EurekaHttpClientDecorator, the decorator of EurekaHttpClient. It is not the HttpClient that actually initiates the http request. It will indirectly delegate the request to abstractjersey EurekaHttpClient, as shown in the following class diagram:

 

EurekaHttpClientDecorator adopts the template method pattern, abstracts the execute method from the register, cancel, sendHeartBeat and other behaviors, and allows subclasses to customize the execution behavior

//Anonymous interface, no concrete implementation class
public interface RequestExecutor<R> {
    EurekaHttpResponse<R> execute(EurekaHttpClient delegate);
    RequestType getRequestType();
}
 
//The template method is adopted to design the pattern, and the execute behavior is abstracted from the register, cancel, sendHeartBeat and other behaviors, so that the subclass can define the specific implementation by itself
protected abstract <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor);


Only the register method is listed below

@Override
public EurekaHttpResponse<Void> register(final InstanceInfo info) {
    //The implementation of anonymous interface calls the execute method of subclass
    return execute(new RequestExecutor<Void>() {
        @Override
        public EurekaHttpResponse<Void> execute(EurekaHttpClient delegate) {
            //Delegate step by step, and finally to AbstractJerseyEurekaHttpClient
            return delegate.register(info);
        }
 
        @Override
        public RequestType getRequestType() {
            return RequestType.Register;
        }
    });
}


This article mainly introduces RetryableEurekaHttpClient, which is quite important. I'll write several others later when I have time.

As the name suggests, there must be an implementation of the retry mechanism in this class. Let's take a look at its execute (requestexecutor < R > requestexecutor) method first. We can see that there is a for loop in this method, and an http request is initiated in the loop. The default retry times of this loop is 3 (this number can be configured if it is not configured).

In this loop, there is a getHostCandidates() method to obtain all available Eureka Server endpoints, and then traverse Eureka Server endpoints through endpointIdx + + to send http requests. If there are timeout and other exceptions in the request process, note that the catch code block does not throw exceptions, but records the log, Then add the timeout Eureka Server endpoint to the blacklist quarantineSet and continue the for loop.

Exception handling is an important part of the retry mechanism. If there is no try catch or an exception is thrown directly in this place, for example, there are three serviceurls, and an exception occurs when making a request to server3 at a certain time. Even if the latter two server1 and server2 are available, they will not be requested (throw an exception, and the subsequent for loop code will not be executed).

Since numberOfRetries is equal to 3, that is, it can be retried three times at most. If it fails, even if the fourth serviceUrl is available, it will not be tried.

@Override
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
    List<EurekaEndpoint> candidateHosts = null;
    //Subscript of candidate Eureka ServerList
    int endpointIdx = 0;
    //Retry 3 times by default, DEFAULT_NUMBER_OF_RETRIES = 3
    for (int retry = 0; retry < numberOfRetries; retry++) {
        EurekaHttpClient currentHttpClient = delegate.get();
        EurekaEndpoint currentEndpoint = null;
        if (currentHttpClient == null) {
            if (candidateHosts == null) {
                //Get the serviceUrlList of the candidate Eureka Server
                candidateHosts = getHostCandidates();
                if (candidateHosts.isEmpty()) {
                    //If this exception occurs, you can be sure that serviceUrl and remoteRegion are not configured
                    throw new TransportException("There is no known eureka server; cluster server list is empty");
                }
            }
            if (endpointIdx >= candidateHosts.size()) {
                // This exception is also very common. The loop in this method needs to be executed three times by default. When you have only one ServiceUrl,
                // If it is invalid, this exception will be thrown on the second retry
                throw new TransportException("Cannot execute request on any known server");
            }
            //Get serviceUrl information
            currentEndpoint = candidateHosts.get(endpointIdx++);
            //Build a new httpClient based on the new serviceUrl information
            currentHttpClient = clientFactory.newClient(currentEndpoint);
        }
 
        try {
            //Send a request to serviceUrl, including register, heartBeat, Cancel and statusUpdate.
            EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
            // serverStatusEvaluator is a status evaluator for each request type (Register, SendHeartBeat, Cancel, GetDelta, etc.)
            // Set an acceptable status code, for example, when the request type is Register and the response Getstatuscode () is 404, so it is acceptable at this time
            // Don't try the next ServiceURl again
            if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
                delegate.set(currentHttpClient);
                if (retry > 0) {
                    logger.info("Request execution succeeded on retry #{}", retry);
                }
                return response;
            }
            logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
        } catch (Exception e) {
            //If there are exceptions such as connection timeout during the request process, print the log, update currentHttpClient, replace the next serviceUrl and try again
            logger.warn("Request execution failed with message: {}", e.getMessage());  // just log message as the underlying client should log the stacktrace
        }
 
        // Connection error or 5xx from the server that must be retried on another server
        delegate.compareAndSet(currentHttpClient, null);
        if (currentEndpoint != null) {
            //http request failed. Put the currently attempted Eureka Server endpoint into the blacklist.
            quarantineSet.add(currentEndpoint);
        }
    }
    //If the request fails three times, the request will be abandoned. If four Eureka addresses are configured in the serviceUrl and the first three requests fail, the fourth serviceUrl will not be tried even if it is available
    throw new TransportException("Retry limit reached; giving up on completing the request");
}


Then we can draw a conclusion from the above code:

1. When Eureka Client sends registration, heartbeat and other requests, it will try one by one to the serviceUrlList of Eureka Server cluster node. If one request succeeds, it will directly return the response instead of requesting from other nodes. It will only retry three times at most, and throw an exception directly after three times.

2. If you configure the defaultZone as follows, the order of requests is Server3 - > Server1 - > server2

3. It is recommended to configure multiple URLs in the defaultZone, even if it is greater than 3, because some server s may be hacked by the client and will not be requested by the client, so the number of numberOfRetries will not be counted

4. If server3 is always available in the following configuration, the Client will always only send heartbeat and other events to this server

5. The defaultZone of Eureka Client should not be configured in the same order. It is better to disrupt the configuration. If all Eureka clients are configured according to the following, the pressure of this server3 is very high. It is not only responsible for receiving the heartbeat state changes of all clients, but also responsible for synchronizing information to other server cluster nodes

defaultZone: http://server3:50220/eureka,http://server1:50100/eureka,http://server2:50300/eureka


getHostCandidates() is used to obtain ServiceUrlList. It has a blacklist mechanism. If a request to an Eureka Server endpoint fails abnormally, it will put the Eureka Server endpoint into the quarantineset (isolation set), The next time you call the getHostCandidates() method (in the for loop above, this method will only be executed once), you will get quarantineset Size () is compared with a threshold. If it is less than this threshold, candidate hosts will be filtered.

If the default client is configured to wait for 30 seconds, the default client will be suspended for every 30 seconds. If the default client's request is not configured successfully, the default client will be suspended for every 30 seconds

private List<EurekaEndpoint> getHostCandidates() {
    //Get all Eureka Server cluster nodes
    List<EurekaEndpoint> candidateHosts = clusterResolver.getClusterEndpoints();
    //How many blacklists are there in the blacklist of candidates
    quarantineSet.retainAll(candidateHosts);
 
    // If enough hosts are bad, we have no choice but start over again
    //This percentage defaults to 0.66, which is about 2 / 3,
    //Take chestnuts for example. If candidahosts = 3, the threshold threshold is equal to 1 (3 * 0.66 = 1.98, which is equal to 1 in int strong transformation...)
    int threshold = (int) (candidateHosts.size() * transportConfig.getRetryableClientQuarantineRefreshPercentage());
    //Prevent threshold is too large
    if (threshold > candidateHosts.size()) {
        //Prevent the threshold from being too large. This percentage may be set to a value greater than 1 by mistake
        threshold = candidateHosts.size();
    }
    if (quarantineSet.isEmpty()) {
        //The blacklist is empty and will not be filtered
        // no-op
    } else if (quarantineSet.size() >= threshold) {
        //If the number of blacklists is greater than this threshold, clear the blacklist without filtering
        //The purpose of setting the threshold is to prevent all serverlist s from being unavailable and being hacked
        //So clear the blacklist and try again
        logger.debug("Clearing quarantined list of size {}", quarantineSet.size());
        quarantineSet.clear();
    } else {
        //If it is less than the threshold, the endpoints in the blacklist are filtered out
        List<EurekaEndpoint> remainingHosts = new ArrayList<>(candidateHosts.size());
        for (EurekaEndpoint endpoint : candidateHosts) {
            if (!quarantineSet.contains(endpoint)) {
                remainingHosts.add(endpoint);
            }
        }
        candidateHosts = remainingHosts;
    }
 
    return candidateHosts;
}


The retry mechanism of RetryableEurekaHttpClient is basically the same. If you want to debug by yourself, you can configure the defaultZone on the Client side as follows, and then start only one Eureka Server (server2), and then mark a breakpoint in the execute method of RetryableEurekaHttpClient class, Debug: start the Client (one of the register with Eureka and fetch registry attributes must be configured as true)

defaultZone: http://server3:50220/eureka,http://server1:50100/eureka,http://server2:50300/eureka

eureka highly available Client retry mechanism: retryableeureka httpclient_ King CSDN blog_ eureka retry mechanism
https://blog.csdn.net/qq_36960211/article/details/85273392/

Topics: Load Balance eureka