opnefeign

usage

declarative

The interface is declared FeignClient by annotating @FeignClient

@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback.class)
public interface UserSecurityServiceFeign {

    /** * Verify the token and return the corresponding permission information **@paramAuthenticateTokenCmd Token Authentication command *@returnLogin * /
    @PostMapping("/auth/token/authentication")
    ResultDTO<UserPermissionInfoCO> authenticateToken(AuthenticateTokenCmd authenticateTokenCmd);

}
Copy the code

Inheriting type

Feigin allows inheritance, through which we can override some configurations to customize FeignClient. For example, UserSecurityServiceFeign in the API Module, and then in other modules we can reference the API Module and define a FeignClient to inherit it and then customize it.

@FeignClient(value = "${user.app.name}", fallback = UserSecurityServiceFeignFallback2.class)
public interface SelfUserSecurityServiceFeign  extends  UserSecurityServiceFeign{}Copy the code

The principle of analysis

The entrance

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   // Packets to be scanned
   String[] value() default {};
   String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
   // Default configurations such as feign.codec.Decoder, feign.codec.Encoder, feign.contract, etc.Class<? >[] defaultConfiguration()default{}; Class<? >[] clients()default {};
}
Copy the code

Start with @enableFeignClients, which imports FeignClientsRegistrar with the @import annotation. FeignClientsRegistrar implements ImportBeanDefinitionRegistrar means in spring BeanFactoryPostProcessor configure class, The registerBeanDefinitions method is executed to register @feignClient’s BeanDefintion.

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware.EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   // Register the default Feign configuration class
   registerDefaultConfiguration(metadata, registry);
   / / register FeignClients
   registerFeignClients(metadata, registry);
}
Copy the code

Register the default Feign configuration class

Can see depending on the defaultConfiguration @ EnableFeignClients annotations, constitute a FeignClientSpecificationBeanDefinition then register into the container

private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
   Map<String, Object> defaultAttrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

   if(defaultAttrs ! =null && defaultAttrs.containsKey("defaultConfiguration")) {
      String name;
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
      registerClientConfiguration(registry, name,
            defaultAttrs.get("defaultConfiguration")); }}private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}
Copy the code

Registered FeignClients

The core step is to register BeanDefintion with the Spring container so we can dependency inject FeignClient. Scanning the corresponding package according to the annotation configuration, if the class is annotated by the @FeignClient annotation then it is a candidate BeanDefinition, It then iterates through the candidate BeanDefinition and reads its @FeignClient configuration metadata attributes to generate one feignClientFactoryBean-beanDefinition registered to the container.

// Configuration information read by attributes @feignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   // key!! The BeanDefinition type is FeignClientFactoryBean
   BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
   validate(attributes);
   definition.addPropertyValue("url", getUrl(attributes));
   definition.addPropertyValue("path", getPath(attributes));
   String name = getName(attributes);
   definition.addPropertyValue("name", name);
   String contextId = getContextId(attributes);
   definition.addPropertyValue("contextId", contextId);
   //type Sets the type returned by the FactoryBean
   definition.addPropertyValue("type", className);
   definition.addPropertyValue("decode404", attributes.get("decode404"));
   definition.addPropertyValue("fallback", attributes.get("fallback"));
   definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

   String alias = contextId + "FeignClient";
   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

   // has a default, won't be null
   boolean primary = (Boolean) attributes.get("primary");

   beanDefinition.setPrimary(primary);

   String qualifier = getQualifier(attributes);
   if (StringUtils.hasText(qualifier)) {
      alias = qualifier;
   }
   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         new String[] { alias });
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code

The creation of FeignClient

The BeanDefinition registered by FeignClient is FeignClientFactoryBean, a FactoryBean implementation. That is, the object returned by its getObject method is injected during dependency injection. FeignClient is a singleton bean. Feign is created by Feign.Builder, and Feign(ReflectiveFeign) is used to create a JDK dynamic proxy object, namely FeignClient instance

@Override
public Object getObject(a) throws Exception {
   return getTarget();
}
<T> T getTarget(a) {
   // Get feign.builder
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   //2. If the URL is not configured, load balancing is required for the service name
   if(! StringUtils.hasText(url)) {/ /... Omitted URL concatenation
      // Use load balancing client
      return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
   }
   / /... Omitted URL concatenation
  
   Client client = getOptional(context, Client.class);
   // If you have a complete URL, you need to remove the load balancing function
   if(client ! =null) {
      if (client instanceof LoadBalancerFeignClient) {
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   The target method returns feign.target tartet is an Interface whose type is @feignClient
   return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));
}
@Override
public boolean isSingleton(a) {
    / / feignClient is a singleton
	return true;
}
// The target method of feign.builder creates a dynamic proxy object
public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

// Load balancing
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
    // Get Client from container
	Client client = getOptional(context, Client.class);
	if(client ! =null) {
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

Copy the code

Feign.Builder

Feign’s builder, which is at the heart of Feign and determines how Feigin is built (and the overall logic flow), allows extensions such as Hystrix and Sentinel to integrate their logic by exposing a Feign.builderBean. You can see that Feign.Builder is indeed fetched from the container (FeignContext is NamedContextFactory).

protected Feign.Builder feign(FeignContext context) {
   FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
   Logger logger = loggerFactory.create(type);

   // Get builder from container
   Feign.Builder builder = get(context, Feign.Builder.class)
         // required values
         .logger(logger)
         .encoder(get(context, Encoder.class))
         .decoder(get(context, Decoder.class))
         .contract(get(context, Contract.class));
   // @formatter:on

   configureFeign(context, builder);

   return builder;
}
  // Feign.builder reserved extension
  public static class Builder {

    private final List<RequestInterceptor> requestInterceptors =
        new ArrayList<RequestInterceptor>();
    private Logger.Level logLevel = Logger.Level.NONE;
    // annotation parsing on the method
    private Contract contract = new Contract.Default();
    //client Client with different functions, such as load balancing
    private Client client = new Client.Default(null.null);
    private Retryer retryer = new Retryer.Default();
    private Logger logger = new NoOpLogger();
    // Decode and encode
    private Encoder encoder = new Encoder.Default();
    private Decoder decoder = new Decoder.Default();
    private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    // Exception handling
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    // core: JDKdongd when generating the interceptor FeignClient for the proxy object
    private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    private boolean forceDecoding = false;
    private List<Capability> capabilities = new ArrayList<>();
      
     public Feign build(a) {
      // Other code omitted
      / / generated Feign
      return newReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }}Copy the code

A dynamic proxy

ReflectiveFeign creates the JDK proxy object, creates a MethodHandler based on the annotation information on the Method (Contract resolution), and executes the corresponding logic in the InvocationHandler based on the Method’s MethodHandler.

/ / target
public <T> T newInstance(Target<T> target) {
  // Annotations on parse methods create corresponding MethodHandler
  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
  for (Method method : target.type().getMethods()) {
     // method ignored in Oject
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if (Util.isDefault(method)) {
       // Default method processing
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {
      // Other methods that require proxiesmethodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); }}/ / InvocationHandler InvocationHandlerFactory feign
  InvocationHandler handler = factory.create(target, methodToHandler);
  //JDK dynamic proxy
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}
Copy the code

InvocationHandlerFactory

InvocationHandler factory, which needs to be overridden by the invocationHandlerFactory in feign. Bulider.

public interface InvocationHandlerFactory {
  //dispatch finds the corresponding MethodHandler based on Method
  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);

  interface MethodHandler {

    Object invoke(Object[] argv) throws Throwable;
  }

  static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return newReflectiveFeign.FeignInvocationHandler(target, dispatch); }}}Copy the code

FeignContext

FeignContext inherits the NamedContextFactory, which means that it creates an ApplicationContext for each client and gets the required beans from this ApplicationContext. It implements the ApplicationContextAware Bean lifecycle interface, so it can take the parent container and form a parent container.

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
      implements DisposableBean.ApplicationContextAware {	
      // name indicates the name of the customer service
      protected AnnotationConfigApplicationContext getContext(String name) {
         // Double check+synchronized ensures that each client's ApplicationContext has only one reference singleton
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
                    // If not, create a map and add it to the map
					this.contexts.put(name, createContext(name)); }}}return this.contexts.get(name);
        
	}
    // omit others
   	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
        // Get the parent container
		this.parent = parent; }}Copy the code

Create SpringApplicationContext

Create a new AnnotationConfigApplicationContext, will get the autowire FeignClientSpecification registered into new ApplicationContext, The setParent then forms the parent-child ApplicationContext and finally launches the context.refresh() container.

protected AnnotationConfigApplicationContext createContext(String name) {
   // Create a new ApplicationContext
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   // FeignClientSpecification to be automatically configured
   if (this.configurations.containsKey(name)) {
      for(Class<? > configuration :this.configurations.get(name) .getConfiguration()) { context.register(configuration); }}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
         for(Class<? > configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);
   context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
         this.propertySourceName,
         Collections.<String, Object>singletonMap(this.propertyName, name)));
   // Key components of parent-child level context
   if (this.parent ! =null) {
      // Uses Environment from parent as well as beans
      context.setParent(this.parent);
      // jdk11 issue
      // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
      context.setClassLoader(this.parent.getClassLoader());
   }
   context.setDisplayName(generateDisplayName(name));
   // Start the container
   context.refresh();
   return context;
}
Copy the code

extension

If we need to customize Feign, we just need to configure a custom Feign.Builder rebuild method. For other extensions, see the properties in Feign.Builder.

Current limiting fuse extension

Sentinel, Hystrix support to feign, mainly covering the invocationHandlerFactory USES its own SentinelInvocationHandler in feign proxy object is added in the execution of the Sentinel at the core of the code. Here is Sentinel’s support for Feign.

public final class SentinelFeign {
   public static Builder builder(a) {
      return new Builder();
   }
   // Customize feign.builder
   public static final class Builder extends Feign.Builder implements ApplicationContextAware {
      // Override the build method
      @Override
      public Feign build(a) {
         // Set up a custom invocationHandlerFactory
         super.invocationHandlerFactory(new InvocationHandlerFactory() {
            @Override
            public InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {
               // Omit other configuration read code
               return new SentinelInvocationHandler(target, dispatch);
            }
            // Other invocationHandlerFactory proxy code
         });

         super.contract(new SentinelContractHolder(contract));
         return super.build();
      }
	// omit others
}
/ / SentinelInvocationHandler proxy mode intercept method execution join sentinel core logic
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)throws Throwable {
		
	String resourceName = methodMetadata.template().method().toUpperCase()
						+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
	Entry entry = null;
	try {
        // Sentinel template code
		ContextUtil.enter(resourceName);
		entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
        // Execute logic
		result = methodHandler.invoke(args);
	}catch (Throwable ex) {
	 // Exception handling
	}
	finally {
		if(entry ! =null) {
			entry.exit(1, args);
		}
		ContextUtil.exit();
	}
   // Other code omitted
	return result;
}   
Copy the code

Load Balancing expansion

By default, Feign integrates the Ribbon to perform load balancing. The principle is to perform load balancing when FeignClientFactoryBean creates FeignClient if it is configured as a service name. Get the ClientBean object from the container and set it in the Builder. Client contains load balancing logic.

  // Check whether load balancing is required
  if(! StringUtils.hasText(url)) {if(! name.startsWith("http")) {
         url = "http://" + name;
      }
      else {
         url = name;
      }
      url += cleanPath();
      // Create feignClient for load balancing
      return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));
   }
// Load balancing
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
    //
	Client client = getOptional(context, Client.class);
	if(client ! =null) {
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
Copy the code

LoadBalancer Load balancing

FeignLoadBalancerAutoConfiguration

Feign load balancing is automatically assembled to decorate different feIGN load balancing clients according to the current FEIGN Client type.

@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}Copy the code

OkHttp, for example when feign. OkHttp. Enabled = true, the first okHttpClient decoration for okHttpClient in adornment to FeignBlockingLoadBalancerClient (including load balancing client).


@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancerConfiguration {

   @Bean
   @ConditionalOnMissingBean
   @Conditional(OnRetryNotEnabledCondition.class)
   public Client feignClient(okhttp3.OkHttpClient okHttpClient,BlockingLoadBalancerClient loadBalancerClient) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      / / decoration for FeignBlockingLoadBalancerClient with load balancing function
      return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
   }

   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
   @ConditionalOnBean(LoadBalancedRetryFactory.class)
   @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true", matchIfMissing = true)
   public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient, List
       
         loadBalancedRetryFactories)
        {
      AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
            loadBalancedRetryFactories.get(0)); }}Copy the code
BlockingLoadBalancerClientAutoConfiguration

Automatic assembly BlockingLoadBalancerClient

Ribbon Load Balancing

Feign Default load balancer

FeignRibbonClientAutoConfiguration

Fegin’s Ribbon load balancer automates assembly

// If you are using Feign and need to use the Ribbon as a load balancer, automatic configuration is enabled
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
// The Ribbon is used by default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Do different decorations for different Http request clients
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
  // Omit other configurations
}
Copy the code

In the case of OkHttp, feign.okhttp.enabled=true triggers the following auto-assembly configuration class – delegating okHttp3. OkHttpClient to LoadBalancerFeignClient (decorator mode) for load balancing.

SpringClientFactory is a factory for creating clients, load balancers, and client configuration instances.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {

   @Bean
   @ConditionalOnMissingBean(Client.class)
   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
      OkHttpClient delegate = new OkHttpClient(okHttpClient);
      return newLoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }}Copy the code
Configuring the Ribbon

The easiest way to configure the Ribbon is through a configuration file. Of course, we can also configure it in code.

To configure a custom load policy in code, you need to create a configuration class to initialize the custom policy, as shown in the code below.

@Configurationpublic class BeanConfiguration {   
    @Bean   
    public MyRule rule(a) {       
        return newMyRule(); }}Copy the code

Create a configuration class for the Ribbon client, associate it with BeanConfiguration, and use name to specify the name of the service to be invoked, as shown below.

@RibbonClient(name = "ribbon-config-demo", configuration = BeanConfiguration.class)
public class RibbonClientConfig {}Copy the code
Configure the Ribbon in configuration file mode

In addition to configuring the Ribbon using code, you can also configure the Ribbon using configuration files:

Format: < clientName >. < nameSpace >. < propertyName > = < value >

<clientName>.ribbon.NFLoadBalancerClassName: Should Implement ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should Implement IRule load balancing algorithm
<clientName>.ribbon.NFLoadBalancerPingClassName: Should Implement IPing(Service Availability check)
<clientName>.ribbon.NIWSServerListClassName: Should Implement ServerList(Service List fetching)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerList-filter
Copy the code

ClientName is the service name in Feign, and ribbon is the namespace

Configuration Reading Principle -IClientConfig

In the default implementation of IClientConfig (DefaultClientConfigImpl), the properties of the restClientName prefix are read based on the restClientName

@Override
public void loadProperties(String restClientName){
       enableDynamicProperties = true;
       setClientName(restClientName);
        // Load the default configuration
       loadDefaultValues();
       // Read the property whose prefix is restClientName
       Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
       for(Iterator<String> keys = props.getKeys(); keys.hasNext(); ) { String key = keys.next(); String prop = key;try {
               if (prop.startsWith(getNameSpace())){
                   prop = prop.substring(getNameSpace().length() + 1);
               }
               // Set to IClientConfig
               setPropertyInternal(prop, getStringValue(props, key));
           } catch (Exception ex) {
               throw new RuntimeException(String.format("Property %s is invalid", prop)); }}}Copy the code

For a specific key, see the IClientConfigKey implementation class CommonClientConfigKey for example:

//LoadBalancer Related
public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer") {};public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName") {};public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName") {};public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName") {};public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval") {};public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime") {};Copy the code
Default spring-cloud-netfix-ribbon configuration

In DefaultClientConfigImpl. LoadDefaultValues, for example

public void loadDefaultValues(a) {
    // omit other configurations and post only load balancers.
    //ZoneAwareLoadBalancer
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerClassName, getDefaultNfloadbalancerClassname());
    / / the default rules of load AvailabilityFilteringRule but RibbonClientConfiguration override this default
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, getDefaultNfloadbalancerRuleClassname());
    putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerPingClassName, getDefaultNfloadbalancerPingClassname());
   
}
Copy the code
ZoneAwareLoadBalancer

Default load balancer

@Override 
public Server chooseServer(Object key) {
        / / if sh
        if(! ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <=1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold".0.2 d);
            }

            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage".0.99999 d);
            }
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if(availableZones ! =null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if(zone ! =null) { BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone); server = zoneLoadBalancer.chooseServer(key); }}}catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if(server ! =null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key); }}Copy the code

The Ribbon provides seven load balancing rules:

  • Com.net flix. Loadbalancer. RoundRobinRule – polling
  • Com.net flix. Loadbalancer. RandomRule – random
  • Com.net flix. Loadbalancer. RetryRule – try again, according to the first RoundRobinRule poll, if failure will retry within a specified time
  • Com.net flix. Loadbalancer. WeightedResponseTimeRule – weight, the faster response speed, the greater the weight, the easier it is selected.
  • Com.net flix. Loadbalancer. BestAvailableRule – to filter out, not with the first turn in the circuit breaker tripped state service, the minimum amount and then select a concurrent service
  • Com.net flix. Loadbalancer. AvailabilityFilteringRule – to filter out the first example, choose the instance of concurrency value smaller again
  • Com.net flix. Loadbalancer. ZoneAvoidanceRule – Spring – the cloud – netflix – ribbon default rules composite judge the performance of the server area and server service availability of choice.
ZoneAvoidanceRule

Spring – the cloud – netflix – ribbon the default load balancing strategy, if there is no corresponding configuration file in RibbonClientConfiguration load balance rule configuration is created a ZoneAvoidanceRule * * * *, The load balancing algorithm it uses is polling

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
	if (this.propertiesFactory.isSet(IRule.class, name)) {
		return this.propertiesFactory.get(IRule.class, config, name);
	}
	ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
	rule.initWithNiwsConfig(config);
	return rule;
}
// Default implementation
public Server choose(Object key) {
    ILoadBalancer lb = getLoadBalancer();
    // Filter after polling
    Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    if (server.isPresent()) {
       return server.get();
    } else {
       return null; }}public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
     // Get the qualified server
     List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
     if (eligible.size() == 0) {
          return Optional.absent();
     }
     //incrementAndGetModulo The qualified server polls that cas gets the next subscript value
     return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
 }
// incrementing and modulo and cas replace until successful to get the post-poll subscript
 private int incrementAndGetModulo(int modulo) {
 	for (;;) {
       int current = nextIndex.get();
       int next = (current + 1) % modulo;
       if (nextIndex.compareAndSet(current, next) && current < modulo)
             returncurrent; }}Copy the code

conclusion

Spring Cloud OpenFeign is a Feign-based Http request framework that integrates with Sentinel or Hystrix for fuses and Ribbon(default)/ Loadbalancer for load balancing. Feign supports OkHttp and HttpClient (the default) to initiate Http requests.

2, Spring-cloud-netfix-ribbon default load balancing algorithm is polling.

Spring Cloud OpenFeign creates a Spring ApplicationContext() for each service (client) and a parent-child relationship with the current application ApplicationContext, from which the required beans are fetched.

4. Load balancers, like openfeign, create a Spring ApplicationContext for each client and set parent to the current ApplicationContext. Look specifically at a subclass of NamedContextFactory

Ps: the above ApplicationContext AnnotationConfigApplicationContext * * * *

The problem

1. Why create an ApplicationContext for every load balancing client and FeIGN client?

The parent context shares a common configuration, and then different child contexts have different configurations. As for why I don’t put it directly in the corresponding Client instance, when the configuration center changes the configuration, it may need to create a new Client (@refreshcope support), it cannot create a new Client in the Client, and the configuration should be isolated from the Client instance. Hope universal net friend answer

At the end

I wrote my first article for the Nuggets and also recently for openFeign. If there is an error (and there will be an error QAQ) please leave it in the comments section.