preface

The source version mentioned in the article:

  • Spring – the cloud – starter – netflix – ribbon:. 2.0.0 RELEASE
  • Spring – the cloud – alibaba – nacos: 2.2.5 RELEASE
  • nacos-example: master

Introduction to the

Spring Cloud Ribbon is a client load balancing tool based on HTTP and TCP, which is implemented by Netflix Ribbon. The encapsulation of Spring Cloud makes it easy to automatically convert service-oriented REST template requests into client-side load-balancing service invocations. Spring Cloud Ribbon is just a tool class framework. Unlike service registries, configuration centers, and API gateways, it does not need to be independently deployed. However, it exists in almost every microservice and infrastructure built by Spring Cloud. Because the Ribbon is used to make calls between microservices, request forwarding of API gateways, and so on. Therefore, understanding and using Spring Cloud Ribbon is very important for us to use Spring Cloud to build microservices.

Client load balancing

The following figure shows the simple working mode of load balancing. When the client invokes the server, it does not directly invoke the server, but requests the load balancer. The information of a server is selected and returned to the client through the load balancing algorithm, and then invokes the server.

How to use ribbon

Add corresponding dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>${spring-cloud-netflix.version}</version>
</dependency>
Copy the code

RestTemplate adds the @loadBalance annotation

@LoadBalanced
@Bean
public RestTemplate restTemplate(a) {
    return new RestTemplate();
}
Copy the code

Use the restTemplate directly for the service invocation

@RestController
public class TestController {

    private final RestTemplate restTemplate;

    @Autowired
    public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate; }@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
    public String echo(@PathVariable String str) {
        return restTemplate.getForObject("http://service-provider/echo/"+ str, String.class); }}Copy the code

Looking at the call, we see that the service name service-provider is invoked directly as the domain name when using restTemplate. However, we have not configured the resolution of the domain name. How do we find the IP address of the service-provider back end and initiate the call? If the back-end service has multiple nodes, how do I select them? With that in mind, let’s look at the source code for the ribbon.

Ribbon Ribbon Ribbon Ribbon Ribbon

What the @loadBalance annotation does

The service-provider name cannot be resolved without the @loadBalance annotation for the restTemplate. First let’s take a look at what the @loadBalance annotation does.

@LoadBalanced

/**
 * Annotation to mark a RestTemplate 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 {
}
Copy the code

Let’s look at the definition of the annotation. There is only one @qualifier and the content in the annotation: mark RestTemplate to use LoadBalancerClient. Let’s take a look at the annotation and see where it’s used other than in our project.

LoadBalancerAutoConfiguration

In org. Springframework. Cloud. Client. Loadbalancer. LoadBalancerAutoConfiguration, access to all the RestTemplate added @ Loadbalanced annotations. All obtained in loadBalancedRestTemplateInitializerDeprecated RestTemplateCustomizer and execute the customize () method.

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
                final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
    for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
        for(RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); }}}); }Copy the code

The RestTemplateCustomizer here is also declared under this configuration:

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

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
    return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
        };
}
Copy the code

As you can see, the restTemplateCustomizer only adds a loadBalancerInterceptor to the restTemplate org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#ribbonIntercep A bean declared in a TOR method.

The purpose of @loadBalanced is to add a filter named loadBalancerInterceptor to the restTemplate.

How do I get the node list corresponding to the service

Here we examine the implementation of the LoadBalance load through the call procedure of the restTemplate.

RestTemplate#doExecute

. We look at org. Springframework. Web client. RestTemplate# the doExecute method, because both the GET and POST methods, such as the final call is the doExecute method:

@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
      @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

   Assert.notNull(url, "URI is required");
   Assert.notNull(method, "HttpMethod is required");
   ClientHttpResponse response = null;
   try {
      ClientHttpRequest request = createRequest(url, method);
      if(requestCallback ! =null) {
         requestCallback.doWithRequest(request);
      }
      response = request.execute();
      handleResponse(url, method, response);
      return(responseExtractor ! =null ? responseExtractor.extractData(response) : null);
   }
   catch(IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query ! =null ? resource.substring(0, resource.indexOf('? ')) : resource);
      throw new ResourceAccessException("I/O error on " + method.name() +
            " request for "" + resource + "":" + ex.getMessage(), ex);
   }
   finally {
      if(response ! =null) { response.close(); }}}Copy the code

This method mainly does three things:

  • throughcreateRequest(url, method)The Request object () method gets the request object.
  • Initiate a request invocation.
  • Process the returned data and return it to the caller.

It seems that if you want to process domain name information in the request URI, it should be done at createRequest.

HttpAccessor#createRequest

protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
   ClientHttpRequest request = getRequestFactory().createRequest(url, method);
   if (logger.isDebugEnabled()) {
      logger.debug("Created " + method.name() + " request for "" + url + """);
   }
   return request;
}
Copy the code

This method is used to create a request object.

InterceptingHttpAccessor#getRequestFactory

@ Override public ClientHttpRequestFactory getRequestFactory () {/ / get all the interceptors of client requests a List < ClientHttpRequestInterceptor > interceptors = getInterceptors(); if (! CollectionUtils.isEmpty(interceptors)) { ClientHttpRequestFactory factory = this.interceptingRequestFactory; / / build a InterceptingClientHttpRequestFactory factory, And all of the interceptor into the if (factory = = null) {factory = new InterceptingClientHttpRequestFactory (super. GetRequestFactory (), interceptors); this.interceptingRequestFactory = factory; } return factory; } else { return super.getRequestFactory(); }}Copy the code

InterceptingHttpAccessor#getInterceptors

Here getInterceptors gets a list of all interceptors, whereas in the previous section @loadBalanced added a loadBalancerInterceptor interceptor to all restTemplates.

HttpAccessor#createRequest

And back again to the createRequest method, here, the call is actually InterceptingClientHttpRequestFactory# createRequest method, Return to the doExecute InterceptingClientHttpRequest object.

RestTemplate.doExecute

Further down from creatRequest, request.execute() is called after the request object is obtained.

response = request.execute();
Copy the code

AbstractClientHttpRequest#execute

@Override
public final ClientHttpResponse execute() throws IOException {
   assertNotExecuted();
   ClientHttpResponse result = executeInternal(this.headers);
   this.executed = true;
   return result;
}
Copy the code

ExecuteInternal (this.headers) is called in the execute method of the parent class. Through the above analysis, we know that the request object is InterceptingClientHttpRequest here

InterceptingClientHttpRequest#executeInternal

@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
   InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
   return requestExecution.execute(this, bufferedOutput);
}
Copy the code

Here again call InterceptingRequestExecution# execute.

InterceptingRequestExecution#execute

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
   if (this.iterator.hasNext()) {
      ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
      return nextInterceptor.intercept(request, body, this);
   }
   else {
      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 instanceof StreamingHttpOutputMessage) {
            StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
            streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
         }
         else {
            StreamUtils.copy(body, delegate.getBody());
         }
      }
      return delegate.execute();
   }
}
Copy the code

This method has two main processing logic:

  • If there is a next interceptor, the interceptor’sinterceptMethod intercepts the request.
  • Otherwise, the remote call follows normal processing logic

To initiate a remote call is to establish an HttpConnection for remote communication. Our main concern here is how the LoadBalancerInterceptor works during the call.

From our previous analysis, we know that there is at least one LoadBalancerInterceptor added by @loadBalanced.

LoadBalancerInterceptor#intercept

@Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName ! = null, "Request URI does not contain a valid hostname: " + originalUri); return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); }Copy the code

LoadBalancerClient LoadBalancerClient LoadBalancerClient LoadBalancerClient LoadBalancerClient

RibbonLoadBalancerClient#execute

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   Server server = getServer(loadBalancer);
   if (server == null) {
      throw new IllegalStateException("No instances available for " + serviceId);
   }
   RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
         serviceId), serverIntrospector(serviceId).getMetadata(server));

   return execute(serviceId, ribbonServer, request);
}
Copy the code
  • Get an ILoadBalancer based on serviceId
  • callorg.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServerMethod to get a service instance.
  • Check whether the server value is null. The server is actually the actual service node, which stores some meta information of the service node, such as host and port.

RibbonLoadBalancerClient#getServer

protected Server getServer(ILoadBalancer loadBalancer) {
   if (loadBalancer == null) {
      return null;
   }
   return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
Copy the code

Here actually call the loadBalancer. ChooseServer this method, ILoadBalancer this is a load balancing interface, the class diagram is as follows:

  • AbstractLoadBalancer implements the ILoadBalancer interface, defining an enumeration of service groups.
  • BaseLoadBalancer implements the basic functions of a load balancer. Such as service list maintenance, service survival status detection, load balancing algorithm selection server, etc. However, the basic functions are only realized here, and some complex scenarios cannot be realized, such as dynamic service list and server filtering.
  • DynamicServerListLoadBalancer BaseLoadbalancer a subclass, it is the basis of load balancing provides extension, it can be seen from the name, it provides the dynamic characteristics of the service list.
  • ZoneAwareLoadBalancer it is on the basis of DynamicServerListLoadBalancer, added in the form of a Zone to configure multiple LoadBalancer function.

Here you can see org.springframework.cloud.net flix. Ribbon. RibbonClientConfiguration# ribbonLoadBalancer the default is used ZoneAwareLoadBalancer;

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

ZoneAwareLoadBalancer#chooseServer

The main logic of this approach is:

  • If zone configuration is not enabled and the number of zones is less than or equal to 1, you can call the parent class method to return the zone.
  • Get availability zones for all available states ->ZoneAvoidanceRule.getAvailableZones.
  • Select a free zone at random from the list of available zones ->ZoneAvoidanceRule.randomChooseZone.
  • Select a server from the selected available zones ->zoneLoadBalancer.chooseServer
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);
    }
    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

BaseLoadBalancer#chooseServer

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
            return null; }}}Copy the code

We’ve seen how the Ribbon uses load balancing algorithms to access a target service address from the service list. Next we continue the dynamic loading process with the list of services.

How is the list of services loaded when combined with the registry

This is because dynamic loading list service, through the analysis of the front, we have already know BaseLoadbalancer DynamicServerListLoadBalancer is a subclass provides the dynamic characteristics of the service list.

DynamicServerListLoadBalancer#DynamicServerListLoadBalancer

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

In the constructor of DynamicServerListLoadBalancer, Call the com.net flix. Loadbalancer. DynamicServerListLoadBalancer# restOfInit method is used to initialize or update the service list.

DynamicServerListLoadBalancer#restOfInit

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
    this.setEnablePrimingConnections(false);
    enableAndInitLearnNewServersFeature();
    // Update the list of services
    updateListOfServers();
    if (primeConnection && this.getPrimeConnections() ! =null) {
        this.getPrimeConnections()
                .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
Copy the code

Here we focus on the update Server method.

DynamicServerListLoadBalancer#updateListOfServers

@VisibleForTesting
public void updateListOfServers(a) {
    List<T> servers = new ArrayList<T>();
    if(serverListImpl ! =null) {
        servers = serverListImpl.getUpdatedListOfServers();
        LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                getIdentifier(), servers);

        if(filter ! =null) {
            servers = filter.getFilteredListOfServers(servers);
            LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);
        }
    }
    updateAllServerList(servers);
}
Copy the code

UpdateListOfServers does the following logic:

  • Check whether the current serverListImpl is empty. If not, execute serverList to get the list of services and update it.
  • If serverListImple is empty, the update service list is empty.

So where does serverListImpl come from?

So if you look at the constructor above, which is injected by the constructor, In the org. Springframework. Cloud. Alibaba. Nacos. Ribbon. NacosRibbonClientConfiguration# ribbonServerList configuration, ServerList injected with NACOS.

So here serverListImpl. GetUpdatedListOfServers () we see nacos implementation can directly.

NacosServerList#getUpdatedListOfServers

@Override
public List<NacosServer> getUpdatedListOfServers(a) {
   return getServers();
}

private List<NacosServer> getServers(a) {
   try {
      List<Instance> instances = discoveryProperties.namingServiceInstance()
            .selectInstances(serviceId, true);
      return instancesToServerList(instances);
   }
   catch (Exception e) {
      throw new IllegalStateException(
            "Can not get service instances from nacos, serviceId="+ serviceId, e); }}Copy the code

Nacos namingService is requested to retrieve all service node information and return it.

RibbonLoadBalancerClient#execute

Return to the RibbonLoadBalancerClient#execute method, where getServer(loadBalancer) returns the list of services obtained from nacOS and selects a service node according to load balancing rules.

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   Server server = getServer(loadBalancer);
   if (server == null) {
      throw new IllegalStateException("No instances available for " + serviceId);
   }
   RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
         serviceId), serverIntrospector(serviceId).getMetadata(server));

   return execute(serviceId, ribbonServer, request);
}
Copy the code

execute

Here we call another overloaded execute method, which will eventually call the Apply method, which will send a request to a specific instance.

@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 = new RibbonStatsRecorder(context, server);

   try {
       // Request initiated
      T returnVal = request.apply(serviceInstance);
      statsRecorder.recordStats(returnVal);
      return returnVal;
   }
   // catch IOException and rethrow so RestTemplate behaves correctly
   catch (IOException ex) {
      statsRecorder.recordStats(ex);
      throw ex;
   }
   catch (Exception ex) {
      statsRecorder.recordStats(ex);
      ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}
Copy the code

LoadBalancerRequest#apply

Request is a LoadBalancerRequest interface that provides an apply method. This parameter is the org. Springframework. Cloud. Client. Loadbalancer. LoadBalancerInterceptor# intercept injection:

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
      final ClientHttpRequestExecution execution) throws IOException {
   finalURI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName ! =null."Request URI does not contain a valid hostname: " + originalUri);
   return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
Copy the code

Request here requestFactory. CreateRequest (request, body, execution)

LoadBalancerRequestFactory#createRequest

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
      final byte[] body, final ClientHttpRequestExecution execution) {
   return instance -> {
           // Wrap the request object
           HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
           if(transformers ! =null) {
               for(LoadBalancerRequestTransformer transformer : transformers) { serviceRequest = transformer.transformRequest(serviceRequest, instance); }}return execution.execute(serviceRequest, body);
       };
}
Copy the code

As you can see from the code, the request object passed is wrapped by ServiceRequestWrapper.

ServiceRequestWrapper#getURI

@Override
public URI getURI(a) {
   URI uri = this.loadBalancer.reconstructURI(
         this.instance, getRequest().getURI());
   return uri;
}
Copy the code

RibbonLoadBalancerClient#reconstructURI

@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
   Assert.notNull(instance, "instance can not be null");
   String serviceId = instance.getServiceId();
   RibbonLoadBalancerContext context = this.clientFactory
         .getLoadBalancerContext(serviceId);

   URI uri;
   Server server;
   if (instance instanceof RibbonServer) {
      RibbonServer ribbonServer = (RibbonServer) instance;
      server = ribbonServer.getServer();
      uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
   } else {
      server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
      IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
      ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
      uri = updateToSecureConnectionIfNeeded(original, clientConfig,
            serverIntrospector, server);
   }
   return context.reconstructURIWithServer(server, uri);
}
Copy the code

ReconstructURI, in effect, reconstructs the URI by converting a http:// service name/into a http:// address /.

  • Get a serviceId first.
  • According to serviceId obtain a RibbonLoadBalancerContext object, this is used to store some context is used by the load balancer.
  • Call the reconstructURIWithServer method to build the URI of the service instance

LoadBalancerContext#reconstructURIWithServer

The implementation logic here is easier to understand, first get the host and port information from the Server. Then replace the original URI with the service name host with the address of the target server

public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost();
    int port = server.getPort();
    String scheme = server.getScheme();
    
    if (host.equals(original.getHost()) 
            && port == original.getPort()
            && scheme == original.getScheme()) {
        return original;
    }
    if (scheme == null) {
        scheme = original.getScheme();
    }
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    try {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append(": / /");
        if(! Strings.isNullOrEmpty(original.getRawUserInfo())) { sb.append(original.getRawUserInfo()).append("@");
        }
        sb.append(host);
        if (port >= 0) {
            sb.append(":").append(port);
        }
        sb.append(original.getRawPath());
        if(! Strings.isNullOrEmpty(original.getRawQuery())) { sb.append("?").append(original.getRawQuery());
        }
        if(! Strings.isNullOrEmpty(original.getRawFragment())) { sb.append("#").append(original.getRawFragment());
        }
        URI newURI = new URI(sb.toString());
        return newURI;            
    } catch (URISyntaxException e) {
        throw newRuntimeException(e); }}Copy the code

Once we understand the URI conversion process, we go back to the createRequest method and end up calling execution. Execute to create a ClientHttpRespose object. Here is actually call ClientHttpRequestExcution interface the execute method. In fact, the remote communication is initiated through the transformed URL, so the subsequent request process is no longer analyzed.

Ribbon source code

Above is the call flow chart according to the source code analysis of this article.

conclusion

This section focuses on how Spring Cloud Ribbon implements load balancing.

The main points are as follows:

  1. @LoadbalancedThe annotation actually adds one to the RestTemplateLoadBalancerInterceptorInterceptor for intercepting all requests.
  2. The main behavior of the interceptor is to get the available service nodes for the current service and select one node as the final node to invoke through a load-balancing algorithm. The serviceId in the URL is changed to the IP address of the actual node :PORT, and the HTTP call is made.
  3. In registries such as Nacos integration,ZoneAwareLoadBalancerThe parent classDynamicServerListLoadBalancerWill askNacosServerList#getUpdatedListOfServersMethod to get all available service nodes and add them to the cache.