Interpretation of feign source code

Retry configurations for feign interface requests that fail can be achieved by following custom profiles (configuration is not generally recommended)

@Configuration
public class FeignConfig {
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default(100, SECONDS.toMillis(1), 5);
    }
}

Of course, you can also use the default retry configuration file, with the feign.Retryer source code below

// The full path of the class is feign.Retryer
    public Default() {
      // By default, the retry interval is 100 ms, the maximum retry interval is 1 second, and the maximum number of retries is 5
      this(100, SECONDS.toMillis(1), 5);
    }

    public Default(long period, long maxPeriod, int maxAttempts) {
      this.period = period;
      this.maxPeriod = maxPeriod;
      this.maxAttempts = maxAttempts;
      this.attempt = 1;
    }
    
    public void continueOrPropagate(RetryableException e) {
      // Throw an exception if the number of retries is greater than the maximum number of retries
      if (attempt++ >= maxAttempts) {
        throw e;
      }
      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        // If the retry interval is greater than the maximum interval, take the maximum interval
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        // If the retry interval is not specified, call nextMaxInterval to get
        interval = nextMaxInterval();
      }
      try {
        // sleep and try again after a certain time
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      // The sleptForMillis variable is the total retry interval
      sleptForMillis += interval;
    }

    /**
     * The interval between the next retries. Each retry increments 1.5 times until the maximum interval
     **/
    long nextMaxInterval() {
      long interval = (long) (period * Math.pow(1.5, attempt - 1));
      return interval > maxPeriod ? maxPeriod : interval;
    }

Feign in spring cloud integrates ribbon, but feign and ribbon both have retry capabilities. spring cloud unifies the behavior of both, setting feign's retry policy to never retry. If you want to use feign's retry function, you only need to set ribbon's retry configuration, so it is generally not recommended to configure feign's retry policy

feign defaults to not configuring retry policy will be retried

The ribbon default configuration is as follows

ribbon:
  # Maximum number of retries for the same instance, excluding the first call.Default value is 0
  MaxAutoRetries: 0
  # Maximum number of retries for other instances of the same microservice, excluding the first invoked instance.Default value is 1
  MaxAutoRetriesNextServer: 1
  # Whether retries are allowed for all operations (GET, POST, etc.).Default value is false
  OkToRetryOnAllOperations: false

By default, GET requests are retried whether they are connection or read exceptions
Non-GET request, retry only if connection exception

It seems like retrying is not possible if there is only one instance of the same microservice, but it is not.
After analyzing the source code, feign retries in the doExecute method in org.springframework.retry.support.RetryTemplate

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
    ......
    while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
    try {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Retry: count=" + context.getRetryCount());
        }
        // Reset the last exception, so if we are successful
        // the close interceptors will not think we failed...
        lastException = null;
        return retryCallback.doWithRetry(context);
    }
    ......
}

canRetry above is the key


Is the last line policy.canRetryNextServer able to select the next instance for retry


The lbContext.getRetryHandler().getMaxRetriesOnNextServer() is the variable retryNextServer


The retryNextServer value is derived from MaxAutoRetriesNextServer and defaults to 1, so canRetry returns true, so it is called twice
The solution is to configure the following

ribbon:
  # Maximum number of retries for the same instance, excluding the first call.Default value is 0
  MaxAutoRetries: 0
  # Maximum number of retries for other instances of the same microservice, excluding the first invoked instance.Default value is 1
  MaxAutoRetriesNextServer: 0
  # Whether retries are allowed for all operations (GET, POST, etc.).Default value is false
  OkToRetryOnAllOperations: false

Automatic Configuration Class for FeignRibbonClient


You can see that it uses the configuration of LoadBalancerFeignClient by default


Looking at its DEFAULT_OPTIONS shows that the default connection timeout is 10s and the read timeout is 6s


The default network request framework is HttpURLConnection


To change the corresponding network request framework, simply add the appropriate pom dependencies


See how load balancing works, see executeWithLoadBalancer


View its submit tasks


Its method selectServer is the key to load balancing

The Feign process is as follows

  1. Open feign with @EnableFeignClients
  2. Add @FeignClient based on the interface you want to call remotely
  3. Program scans FeignClient annotations under a specific package and injects them into the Ioc container
  4. When the feign interface is called, the corresponding RequestTemplate is generated through the jdk proxy
  5. Generate corresponding Request from RequestTemplate
  6. Request is given to the class Client to call, which can be HttpClient,Okhttp, or HttpUrlConnection
  7. Finally, the Client is encapsulated into the LoadBalanceClient, which combines ribbon for load balancing

Tags: Java Spring network JDK OkHttp

Posted on Mon, 16 Mar 2020 13:16:54 -0400 by darklexus2k9