Analysis of Ribbon principle under Nacos

Analysis of Ribbon principle

1 initialize Ribbon configuration information

  1. In the project of nacos as the registry, the dependency of service discovery needs to be introduced
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

The spring cloud commons dependency will be introduced, and the dependency will introduce the loader, which is the core of ribbon.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-commons</artifactId>
</dependency>
  1. Through case analysis, restTemplate needs to use @ LoadBalanced to achieve load balancing. First, take a look at @ LoadBalanced. This annotation is translated in the annotation. The bean s used to mark restTemplate and webClient use LoadBalancerClient as configuration, and LoadBalancerClient is an interface that defines the method of executing requests.
@LoadBalanced
@Bean("xxxTemplate")// Custom bean name to facilitate subsequent differentiation.
public RestTemplate restTemplate() {
    return new RestTemplate();
}
/**
 * Annotation to mark a RestTemplate or WebClient bean to be configured to use a
 * LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
  1. Under the loaderballancer package, check all classes and find the LoadBalancerAutoConfiguration automatic configuration class. Obviously, this class is the configuration loading class to achieve load balancing. LoadBalancerClient.class and RestTemplate.class are added to the class annotation, indicating that both bean s need to exist before using this class, The LoadBalancerClient interface has only one implementation, that is, the RibbonLoadBalancerClient, that is, the load balancing client, as shown in the following figure.
  2. Then continue the analysis. You can see that restTemplates automatically inject all restTemplates. Here you can see that each restTemplate should be equipped with functions to achieve load balancing. Here you can try running the springboot project to see what to do.
  3. Initialize the LoadBalancerInterceptor, and then configure the loadbalancerinceptor interceptor interceptor into the restTemplate.
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {

    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }

    // Encapsulate interceptors into restTemplate
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(
            final LoadBalancerInterceptor loadBalancerInterceptor) {
        return restTemplate -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                    restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        };
    }
}
  1. Loadbalancerinceptor function
    Obtain the originalUri and serviceName in the interceptor. The subsequent guess is to use the serviceName, and then obtain the corresponding registered service ip from the registry. Finally, replace the initial url. The test code is as follows: two Nacos component provider services have been registered on Nacos, the port numbers are 8080 and 8081 respectively, and the url is= http://nacos-component-provider/hystrixI/1 Then debug step by step and observe the execution of restTemplate.
@GetMapping(value = "/hystrixII/{id}")
public String testRestTemplate(@PathVariable Integer id){
    return restTemplate.getForObject("http://nacos-component-provider/hystrixI/" + id, String.class);
}

2. Resttemplate load balancing execution process

  1. For restTemplate implementation, first set the format of the returned information, because there is no specific implementation yet.
  2. Add the extended attributes of URI, such as parameters, and then call RestTemplate#execute(String, HttpMethod, RequestCallback, ResponseExtractor, java.lang.Object...) to execute the request.
  3. Create a request and execute the request method
  4. Judge whether the request has been executed. If so, an exception will be thrown, otherwise continue to execute.
  5. Then execute InterceptingClientHttpRequest#executeInternal, mainly to execute the methods in the interceptor. After execution, the final method to send the request url will be executed.
  6. Since there is only one interceptor list in the restTemplate request, you can directly go to the LoadBalancerInterceptor#intercept method, which is the core method of sending the request.
  7. This loadBalancer is the RibbonLoadBalancerClient, which performs the load balancing function
  8. Then call the RibbonLoadBalancerClient#execute(String, LoadBalancerRequest, Object) method and getLoadBalancer(serviceId) method. serviceId is used here. This is mainly to make a cache. When accessing the load balancer again, you don't need to create one.
  9. Call the RibbonLoadBalancerClient#getLoadBalancer method to obtain the load balancer of the service. If the IOC principle is used for the first time, because there is no instance of ILoadBalancer.class type, a zoneawarelooadbalancer object will be created. The code is as follows. Zoneawarelooadbalancer load balancer: regional awareness The load balancer of dynamic service list inherits the dynamic serverlistloadbalancer, that is, it will synchronize the service information nodes registered on nacos.
public ILoadBalancer getLoadBalancer(String name) {
    return getInstance(name, ILoadBalancer.class);
}
// When there is no object of ILoadBalancer type, it will create one and execute the following method
@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);
    }
    // The zonewareloadbalancer load balancer: a load balancer with regional awareness and dynamic service list
    return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
            serverListFilter, serverListUpdater);
}
  1. The load balancer is the zonewareloadbalancer (dynamic service list load balancer), which is mainly responsible for obtaining the registered services from nacos or removing the offline services. Each service has a corresponding load balancer.
  2. getServer(loadBalancer, hint) method. It can be guessed here that the corresponding load balancer will select a service to use according to certain rules, such as polling and random rules. By default, the method of polling is used to obtain the instance.
  3. Execute the chooseround Robin afterfiltering method in the PredicateBasedRule#choose method: read all serverlists corresponding to the serviceName from nacos, filter out available services, and then poll. The following is the specific implementation of the polling rule
// Implementation of polling rules
//modulo: the size of the list of available services
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        // This class maintains a self increaser, and then + 1 for each access to take the module of the available list to achieve the effect of polling
        int current = nextIndex.get();
        int next = (current + 1) % modulo;
        if (nextIndex.compareAndSet(current, next) && current < modulo)
            return current;
    }
}
  1. According to the service name and load balancer, a service information, including ip:port and other information, is obtained for subsequent replacement url requests.
  2. request.apply(serviceInstance) is a function interface. The specific execution method is this.requestFactory.createRequest(request, body, execution). This method encapsulates the original httpRequest as ServiceRequestWrapper serviceRequest, and then uses the serviceRequest to execute the request.
  3. Then we return to the InterceptingClientHttpRequest#execute(HttpRequest, byte []) method. Since all interceptors have been executed, we will now execute the part of else to really send the request. The following code is the new URI request after construction.
//This is serviceRequest.getURI(). The original http://nacos-component-provider/hystrixI/1 , replace with a request in the form of ip:port
public URI getURI() {
    // Method of performing replacement
    URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
    return uri;
}


16. Finally, a delegate class delegate is constructed to execute the modified URI, and subsequent codes do not need to be debug ged, because they are all related to http requests and the content after processing the response.

Tags: Spring Cloud

Posted on Sun, 24 Oct 2021 09:12:06 -0400 by phant0m