Ribbon Load Balancing

1. About load balancing

Load balancing is generally divided into server load balancing and client load balancing

The so-called server-side load balancer, such as Nginx and F5, routes the request to the target server according to certain algorithms after it arrives at the server.

Client load balancing, for example, takes the Ribbon as an example. The service consumer client has a list of server addresses. Before making a request, the caller selects a server to access using a load balancing algorithm.

Ribbon is a load balancer released by Netflix. Eureka works with the Ribbon. The Ribbon reads service information from Eureka and loads the service provided by the service provider based on certain algorithms

2. Ribbon Advanced applications

Let’s take a look at the Eureka client project lagou-service-AutoDeliver. This project does not need to introduce additional Jar coordinates because we introduced Eureka-client in the service consumer. It introduces Ribbon related jars (see Maven reference dependencies)

(1) Reform resume micro service

Next, we need to transform our resume micro-service project, now there are two resume micro-services respectively 8080 and 8081

After running the project, you can observe that both services have been registered with Eureka, so that we can start testing load-balancing related businesses

To make it easier to distinguish which service is accessed during load balancing, we can modify two resume microservices to return IP port numbers

@RestController
@RequestMapping("/resume")
public class ResumeController {
 
    @Value("${server.port}")
    private Integer port;

    //http://localhost:8081/resume/openstate/1545133
    @GetMapping("/openstate/{userId}")
    public Integer findDefaultResumeState(@PathVariable Long userId){
        returnport; }}Copy the code

(2) Transformation of resume delivery service [micro service user]

To enable the Ribbon on the Eureka client, we simply add a @loadBalanced annotation when registering the RestTemplate to use the Ribbon

@Bean
// Ribbon load balancing
@LoadBalanced
public RestTemplate getRestTemplate(a) {
	return new RestTemplate();
}
Copy the code

Now that @loadBalanced is added to RestTemplate, you need to change the way the RestTemplate is called

Prior to the introduction of the Ribbon, you need to directly interact with Eureka Server to obtain relevant service instance information, and then select one to call

  @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/old2/checkState/{userId}")
    public Integer findResumeOpenState_old2(@PathVariable Long userId){

        Obtain service instance information from Eureka Server
        List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-resume");

        //2. If there are multiple instances, select one to use
        ServiceInstance serviceInstance = instances.get(0);

        //3. Obtain the host port from the metadata
        String host = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        String url="http://"+host+":"+port+"/resume/openstate/" + userId;

        System.out.println("Eureka Service information >>>>>>>>>>>>>>>"+url);


        Integer forObject = restTemplate.getForObject(url ,Integer.class);

        return forObject;
    }
Copy the code

With the introduction of the Ribbon, clients only need to know the service name, not the IP address and port number. This advantage is that the physical IP address of the server can be flexibly changed. Server migration has no impact on services, because clients only recognize the service name, and the service name is fixed. Most importantly, the Ribbon encapsulates Ip ports and other details. You can use the service name to complete calls. The Ribbon encapsulates load balancing policies, Ip assembly and other services

@GetMapping("/checkState/{userId}")
public Integer findResumeOpenState(@PathVariable Long userId){

    String url="http://lagou-service-resume/resume/openstate/" + userId;

    Integer forObject = restTemplate.getForObject(url ,Integer.class);

    return forObject;
}
Copy the code

(3) Call the resume delivery service

After invoking the original resume delivery service, port numbers 8080 and 8081 will be switched

Ribbon load balancing example code

3. Ribbon Load balancing policies

The Ribbon has a variety of built-in load balancing policies. The top interfaces for complex balancing arecom.netflix.loadbalancer.IRuleThe class tree is as follows

Load Balancing Policy describe
RoundRobinRule: indicates a polling policy By default, the server obtained for more than 10 times is unavailable and an empty server is returned
RandomRule: a RandomRule strategy If a random server is null or unavailable, the server is selected in a while loop
RetryRule: Retry policy Retry within a specified period of time. By default, it inherits RoundRobinRule and also supports custom injection. RetryRule will check whether the selected server is null or alive after each selection, and will continue to check within 500ms. While RoundRobinRule fails more than 10 times, RandomRule does not have a lapse time as long as the serverList does not hang.
BestAvailableRule: Minimum connection number policy Iterate through the serverList and select the server with the smallest number of connections available. The algorithm has a LoadBalancerStats member variable that stores the health and connection count of all servers. If the selected server is null, RoundRobinRule is called to re-select the server. 1 (1) 2 (1) 3 (1)
AvailabilityFilteringRule: available filtering strategy If the polling policy is extended, the system selects a server through the default polling, and then checks whether the server is available due to timeout and whether the number of current connections exceeds the upper limit.
ZoneAvoidanceRule: zone tradeoff policy (default policy) Extended polling strategy, inheriting 2 filters: ZoneAvoidancePredicate and AvailabilityPredicate, in addition to filtering out servers that time out and have too many links, filters out all nodes within a zone zone that doesn’t meet the requirements. AWS — Zone polls among service instances within a zone/room

How do I modify a load balancing policy

Open the configuration file application.yml to modify it

# for the microservice name of the called party, if not added, it takes effect globally
lagou-service-resume:
  ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule Load policy adjustment

# global effect
#ribbon:
# # NFLoadBalancerRuleClassName: com.net flix. Loadbalancer. RandomRule load strategy adjustment
Copy the code

After the modification, I executed the resume delivery service and found that the breakpoint had entered RandomRule

4. Ribbon core source code analysis

How Ribbon works

Important: The Ribbon adds an interceptor to the restTemplate

Consider: What the Ribbon is doing:

When we go to http://lagou-service-resume/resume/openstate, The ribbon should retrieve the service instance list based on the service name lagou-service-resume, obtain an instance Server from the instance list based on certain load balancing policies, and finally request access through the RestTemplate

The Ribbon details the structure of the Ribbon (involving the description of the underlying components/classes)

The core is the LoadBalancer, surrounded by IRule, IPing, and so on

  • IRule: is the load balancing policy object at instance selection time
  • IPing: sends heartbeat checks to the service to determine whether the service is available
  • ServerListFilter: Filters the list of incoming service instances according to some rules
  • ServerListUpdater: Defines a set of operations to update a list of services

4.1 @loadBalanced source code analysis

We can implement load balancing by adding an @loadBalanced annotation to the RestTemplate instance. Let’s look at the operations behind this annotation (load balancing process).

Check out the @loadBalanced annotation. Where is this annotation recognized? In our experience reading source code, the typical entry will have some @import annotation, but there is no other information here, just a single commentNormal RestTemplate objects can be handled with LoadBanlancerClient using the @loadBalanced annotation

The idea seems to be broken, so now we can only use the principle of SpringBoot automatic assembly to explore the Ribbon source code

Step into the RibbonAutoConfiguration class, there are @AutoConfigureBefore and @AutoConfigureAfter annotations

LoadBalancerAutoConfiguration

# # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # only exist RestTemplate class, assembly effect, So during the process of Http invocation, we need to use RestTemplate # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {# # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # (1) declare a List<RestTemplate> collection object, where those additions are automatically injected@LoadBalancedAnnotated RestTemplate object (unified here concentrated) ## inject the RestTemplate object into the collection to be used # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =@LoadBalanced
    @Autowired(required = false)
    privateList<RestTemplate> restTemplates = Collections.emptyList(); # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # (2Inject the restTemplate customizer into the container. Add an interceptor LoadBalancerInterceptor to the restTemplate object # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =@Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = newArrayList<>(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # # (3) using a custom to set each of resttemplate object to add a blocker # # = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =@Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return() - > restTemplateCustomizers. IfAvailable (customizers - > {# # iterate through all the resttemplate object, give each resttemplate object configuration a blockerfor (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for(RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); }}}); }}Copy the code

The LoadBalancerInterceptor is the core of the intercept method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

  private LoadBalancerClient loadBalancer;
  private LoadBalancerRequestFactory requestFactory;
  public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
    this.loadBalancer = loadBalancer;
    this.requestFactory = requestFactory;
  }

  @Override
  public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  final ClientHttpRequestExecution execution) throws IOException {## retrieve the intercepted request URI, such as the one we visited: HTTP://lagou-servver-resume/resume/xxxx
    finalURI originalUri = request.getURI(); Lagou-service -resumt String serviceName = originaluri.gethost (); The rest of the load balancing is done by the LoadBalancerClient objectreturn this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); }}Copy the code

So? Where is the RibbonLoadBalancerClient object injected ===> Back to the original Auto-configuration class

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(a) {## inject a LoadBalancerClient object, instance asRibbonLoadBalancerClient
	return new RibbonLoadBalancerClient(springClientFactory());
}
Copy the code

Let’s move on to this key piece of code

return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
Copy the code

And I’m going to go in from the execute method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  
  ## 1ILoadBalancer loadBalancer = getLoadBalancer(serviceId); # #2Server server = getServer(loadBalancer, hint); The server is encapsulated as Ribbonserver objectnewRibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); # #3Continue executionreturn execute(serviceId, ribbonServer, request);
}
Copy the code
1, getLoadBalancer

The SpringClientFactory method is called in the getLoadBalancer method. The SpringClientFactory method is called in the getLoadBalancer method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #private SpringClientFactory clientFactory;

  public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
  	this.clientFactory = clientFactory;
  }
    
  protected ILoadBalancer getLoadBalancer(String serviceId) {
  	return this.clientFactory.getLoadBalancer(serviceId);
  }
Copy the code

Back to the RibbonAutoConfiguration configuration main class, we see the injected SpringClientFactory. During code tracking, we had to go back and forth a lot of times, as long as we did not get dizzy

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#springClientFactory # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Bean
  public SpringClientFactory springClientFactory(a) {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
  }
Copy the code

Now that we know when it was injected, let’s go back to the getLoadBalancer method. Actually, we can’t continue at this point because we’ve already reached the end, but it looks like we’re getting ILoadBalancer. Class data from the container

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.SpringClientFactory#getLoadBalancer # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # the basic access methodpublic ILoadBalancer getLoadBalancer(String name) {
    returngetInstance(name, ILoadBalancer.class); } ## continue tracing ==> discovery callssuper.getInstance(name, type);
  @Override
  public <C> C getInstance(String name, Class<C> type) {
    C instance = super.getInstance(name, type);
    if(instance ! =null) {
    	return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    returninstantiateWithConfig(getContext(name), type, config); } ## continue to trace ==> to see the familiar context.getBean(type) method, ===> proof that iloadbalancer.class should have been injected into the container earlypublic <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
      if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,   type).length > 0) {
      	return context.getBean(type);
      }
    return null;
  }
Copy the code

So what’s the key to the answer? Again see SpringClientFactory class, found a superclass constructor, it should be at the entrance to the truth, it was introduced into the type RibbonClientConfiguration. Class, according to our experience, the configuration class, should be within the methods for loading

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.SpringClientFactory # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
  public SpringClientFactory(a) {
  	super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name"); }}Copy the code

Sure enough, the configuration class contains a bunch of configuration information

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # configure load balancing strategy@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {## If a load balancing policy is configured by default, use the configuration fileif (this.propertiesFactory.isSet(IRule.class, name)) {
  	return this.propertiesFactory.get(IRule.class, config, name);
  }
  ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
  rule.initWithNiwsConfig(config);
  return rule;
}

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  	return this.propertiesFactory.get(ILoadBalancer.class, config, name); } ## Load balancer injected by defaultreturn new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,serverListFilter, serverListUpdater);
}
Copy the code
2, getServer

How do I get a Server

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
  if (loadBalancer == null) {
 	 return null;
  }
  // Use 'default' on a null hint, or just pass it on?
  returnloadBalancer.chooseServer(hint ! =null ? hint : "default");
}
Copy the code

At the end of the trace, we reach an interface and we can see that there are three implementations. Based on our analysis of the appeals, we know that our default implementation class is ZoneAwareLoadBalancer

The Ribbon used to be on Amazon cloud, so they have the concept of partitioning. In my country, this is not used very much, so the first step is to go into the Return branch

  @Override
  public Server chooseServer(Object key) {
    if(! ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <=1) {
      logger.debug("Zone aware logic disabled or there is only one zone");
      return super.chooseServer(key); }}Copy the code

We enter the parent super.chooseserver (key);

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.BaseLoadBalancer#chooseServer # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public Server chooseServer(Object key) {
  if (counter == null) {
  	counter = createCounter();
  }
  counter.increment();
  if (rule == null) {
  	return null;
  } else {
    try{## select a load balancing instance based on the rules, default is awsreturn rule.choose(key);
    } catch (Exception e) {
      logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
      return null; }}}Copy the code

And then you go further, you go into

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.PredicateBasedRule # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); ## Select the first server Optional< server > server = from the filtered service instance collection according to the polling policy getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);if (server.isPresent()) {
  	  return server.get();
    } else {
    	return null; }}Copy the code
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.AbstractServerPredicate#chooseRoundRobinAfterFiltering # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {## List<Server> eligible = getEligibleServers(Servers, loadBalancerKey); If the service instance is empty, a default value is returnedif (eligible.size() == 0) {
    returnOptional.absent(); } ## incrementAndGetModulo==> Polls to the instance index value calculation methodreturn Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
Copy the code
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.AbstractServerPredicate#incrementAndGetModulo # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #private final AtomicInteger nextIndex = new AtomicInteger();
    
private int incrementAndGetModulo(int modulo) {
  for(;;) {## get the index of the current service instanceintcurrent = nextIndex.get(); An index value is recorded by residualsint next = (current + 1) % modulo; Set the next index value via CAS.if (nextIndex.compareAndSet(current, next) && current < modulo)
    	returncurrent; }}Copy the code
3, the execute

So let’s go back to step three, execute

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  Server server = getServer(loadBalancer, hint);
  
  if (server == null) {
	  throw new IllegalStateException("No instances available for " + serviceId);
  }
  
  RibbonServer ribbonServer = newRibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); # # = = = = = = >execute
  return execute(serviceId, ribbonServer, request);
}
Copy the code

Request. Apply (serviceInstance); request. Apply (serviceInstance); There’s a lot of code inside this method, but the reason we’re going into this method is because we want to know how the current method performs the remote call, so we’re not looking at the other side, but we’re looking at things like requests, right

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  Server server = null;
  if(serviceInstance instanceof RibbonServer) {
  server = ((RibbonServer)serviceInstance).getServer();
  }
  if (server == null) {
  throw new IllegalStateException("No instances available for " + serviceId);
  }

  RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId);
  
  RibbonStatsRecorder statsRecorder = newRibbonStatsRecorder(context, server); T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal);return returnVal;

  return null;
}

Copy the code

This time we clicked on the apply method and found there was no trace at all, so we had to debug from the breakpoint and go to the Request. apply(serviceInstance) method

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory#createRequest ####################################################################################### public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) { return instance -> { HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer); if (transformers ! = null) { for (LoadBalancerRequestTransformer transformer : transformers) { serviceRequest = transformer.transformRequest(serviceRequest, instance); Execute (serviceRequest, body); execute(serviceRequest, body); }; }Copy the code

Track again

org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { HttpMethod method = request.getMethod(); Assert.state(method ! =null."No standard HTTP method");
  ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
  request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
  if (body.length > 0) {
    if (delegate instanceofStreamingHttpOutputMessage) { StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;  streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream)); }else{ StreamUtils.copy(body, delegate.getBody()); }} ## ==> Execute method of proxy classreturn delegate.execute();
}
Copy the code

At this point, you are at the bottom of the code executing the RestTemplate, which verifies that the final request is still called by the RestTemplate

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.http.client.AbstractClientHttpRequest#execute # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Override
public final ClientHttpResponse execute(a) throws IOException {
  assertNotExecuted();
  ClientHttpResponse result = executeInternal(this.headers);
  this.executed = true;
  return result;
}

Copy the code

LoadBalancer LoadBalancer LoadBalancer LoadBalancer LoadBalancer LoadBalancer LoadBalancer LoadBalancer

Come to RibbonClientConfiguration

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonLoadBalancer # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # serverList = = > in the constructor, Use serverList to indicate that in other places, ServerList @bean@conditionalonmissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); } ## Here we can see that when we build a load balancer, ServerList return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }Copy the code

Inject the ServerList Bean object into the container. The object does not discover that it actually does anything. Therefore, we assume that we should inject an empty object and assign values to the object later

@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
  if (this.propertiesFactory.isSet(ServerList.class, name)) {
    return this.propertiesFactory.get(ServerList.class, config, name); } # #newA basic configuration serverList ConfigurationBasedServerList serverList =newConfigurationBasedServerList(); . # # the initialization configuration information serverList initWithNiwsConfig (config); ## Return directlyreturn serverList;
}
Copy the code

Subsequent assignments to serverList must be in this class

Keep going up

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) { super(clientConfig, rule, ping); this.serverListImpl = serverList; this.filter = filter; this.serverListUpdater = serverListUpdater; if (filter instanceof AbstractServerListFilter) { ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats()); } ## ==> Key call restOfInit(clientConfig); }Copy the code

The most critical method

void restOfInit(IClientConfig clientConfig) {
  boolean primeConnection = this.isEnablePrimingConnections();
  // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
  this.setEnablePrimingConnections(false); After a certain period of time, the Eureka Client will obtain new service instance information from the cache (EeurakaClient will also periodically update the service information from EurekaServer). And then update to the Ribbon enableAndInitLearnNewServersFeature (); UpdateListOfServers ();if (primeConnection && this.getPrimeConnections() ! =null) {
  this.getPrimeConnections()
  .primeConnections(getReachableServers());
  }
  this.setEnablePrimingConnections(primeConnection); 
}
Copy the code
EnableAndInitLearnNewServersFeature related code
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #public void enableAndInitLearnNewServersFeature(a) {
  LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName()); Serverlistupdater. start(updateAction); } ## updateAction is a class object that has a doUpdate method and the core logical pair is updateListOfServersprotected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
  @Override
  public void doUpdate(a) { updateListOfServers(); }};Copy the code

Take a look at the start method of the service list updater

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # com.netflix.loadbalancer.PollingServerListUpdater#start # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #@Override
public synchronized void start(final UpdateAction updateAction) {
  if (isActive.compareAndSet(false.true) {### ====> defines the thread, and the logic is to call the doUpdate method of the passed updateActionfinal Runnable wrapperRunnable = new Runnable() {
          @Override
          public void run(a) {
              if(! isActive.get()) {if(scheduledFuture ! =null) {
                      scheduledFuture.cancel(true);
                  }
                  return;
              }
              try{## ==> perform updateAction.doupDate (); lastUpdated = System.curr } }; Timed delay timed task, ScheduledFuture = getRefreshExecutor(). ScheduleWithFixedDelay (wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); }else {
      logger.info("Already active, no-op"); }}Copy the code

4.2 RoundRobinRule RoundRobin Policy Source Code Analysis

public class RoundRobinRule extends AbstractLoadBalancerRule {## load balancing policy class core class methodpublic Server choose(ILoadBalancer lb, Object key) {
  if (lb == null) {
      log.warn("no load balancer");
      return null;
  }

  Server server = null;
  int count = 0; Retry policy is added here, retry10timewhile (server == null && count++ < 10List<Server> reachableServers = lb.getreachableservers (); List<Server> allServers = lb.getallServers ();int upCount = reachableServers.size();
      int serverCount = allServers.size();

      if ((upCount == 0) || (serverCount == 0)) {
          log.warn("No up servers available from load balancer: " + lb);
          return null; } ## Polling for each server according to CASintnextServerIndex = incrementAndGetModulo(serverCount); Server = allServers.get(nextServerIndex);if (server == null) {
          /* Transient. */
          Thread.yield();
          continue; } ## return if the service is availableif (server.isAlive() && (server.isReadyToServe())) {
          return (server);
      }

      // Next.
      server = null;
  }

  if (count >= 10) {
      log.warn("No available alive servers after 10 tries from load balancer: "
              + lb);
  }
  return server;
}

private int incrementAndGetModulo(int modulo) {
  for(;;) {## fetch the last countintcurrent = nextServerCyclicCounter.get(); Because it's modulo, count +1After that, take the moldint next = (current + 1) % modulo;
      if (nextServerCyclicCounter.compareAndSet(current, next))
          returnnext; }}}Copy the code

4.3 RandomRule random strategy source code analysis

public class RandomRule extends AbstractLoadBalancerRule {

/** * Randomly choose from all living servers */
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
  public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    }
    Server server = null; When the service is empty, it will always fetch, no10The limits ofwhile (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            return null; } ## return a random number when retrieving an indexint index = chooseRandomInt(serverCount);
        server = upList.get(index);

        if (server == null) {
            Thread.yield();
            continue;
        }

        if (server.isAlive()) {
            return (server);
        }

        // Shouldn't actually happen.. but must be transient or a bug.
        server = null;
        Thread.yield();
    }

    return server;

  }

  protected int chooseRandomInt(int serverCount) {## return a random number using ThreadLocalRandomreturnThreadLocalRandom.current().nextInt(serverCount); }}Copy the code

Ribbon sample code