Ribbon principle analysis
Ribbon uses interceptors to realize remote invocation of services. The source code is as follows:
public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean // Loop all enhancement classes to enhance restTemplate, and add interceptors to it here 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); } } }); } @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean // Register the load balancing interceptor. LoadBalancerClient is the function class that is executing the service balancing public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean // Register the enhanced class of RestTemplate public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); // Add interceptor list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } // Slightly
After entering the intercept of loadbalancerinceptor, you can see that it uses LoadBalancerClient to execute the real process. LoadBalancerClient is responsible for calling the process, and the specific execution is executed by ILoadBalancer load balancer
@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); // Perform load balancing return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }
Then enter the execute method of RibbonLoadBalancerClient to view.
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { // 1. Get load balancer from container ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 2. Use ILoadBalancer to get a service Server server = getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } // Assemble information into a service instance RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); // 3. Call this service return execute(serviceId, ribbonServer, request); }
Service discovery and load balancing take place in these three steps. We focus on what these three steps do
1,getLoadBalancer
Obtain the load balancer from the container, and register the zonewareloadbalancer with the container in the RibbonClientConfiguration, so it is actually obtained.
Then look at the construction process of a ZoneAwareLoadBalancer and what it has done. Click in to find the DynamicServerListLoadBalancer construction method of its parent class. The source code is as follows
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) { // Call the superior structure to initialize some configuration information super(clientConfig, rule, ping); // serverList service list, which is used to discover the service this.serverListImpl = serverList; // Service filters this.filter = filter; // Updater for service list this.serverListUpdater = serverListUpdater; if (filter instanceof AbstractServerListFilter) { ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats()); } // Initialize, pull service and start service update restOfInit(clientConfig); }
restOfInit method source code
void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this.isEnablePrimingConnections(); // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList() this.setEnablePrimingConnections(false); // Enable listening of service list enableAndInitLearnNewServersFeature(); // Pull service updateListOfServers(); if (primeConnection && this.getPrimeConnections() != null) { this.getPrimeConnections() .primeConnections(getReachableServers()); } this.setEnablePrimingConnections(primeConnection); LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString()); }
Enableandinitrelearnnewserversfeature starts a scheduled task pull service, which calls updateListOfServers. It mainly depends on how updateListOfServers pull the service. The method source code is as follows:
public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl != null) { // Call the service list service to pull and update the service 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); } } // Update service list information of memory updateAllServerList(servers); }
serverListImpl is a standard interface for load balancing, which is used to obtain services. Nacos also implements this interface to obtain the service list from Ribbon
public interface ServerList<T extends Server> { public List<T> getInitialListOfServers(); /** * Return updated list of servers. This is called say every 30 secs * (configurable) by the Loadbalancer's Ping cycle * */ public List<T> getUpdatedListOfServers(); }
Part of the source code of nacoserverlist:
@Override public List<NacosServer> getUpdatedListOfServers() { return getServers(); } private List<NacosServer> getServers() { try { String group = discoveryProperties.getGroup(); // Use Nacos's NameingService to initiate Api calls to obtain the list of service instances List<Instance> instances = discoveryProperties.namingServiceInstance() .selectInstances(serviceId, group, true); return instancesToServerList(instances); } catch (Exception e) { throw new IllegalStateException( "Can not get service instances from nacos, serviceId=" + serviceId, e); } }
Then you will know how the service discovery is. The rest is to select a service to invoke according to the load policy. Then look at the next step, getServer
2,getServer
Go back to the second step of RibbonLoadBalancerClient getServer. This step is to use the load balancer to select a service:
protected Server getServer(ILoadBalancer loadBalancer, Object hint) { if (loadBalancer == null) { return null; } // Use 'default' on a null hint, or just pass it on? return loadBalancer.chooseServer(hint != null ? hint : "default"); }
Because the zone will not be used by default, it will directly enter the chooseServer method of BaseLoadBalancer. The source code is as follows:
public Server chooseServer(Object key) { // Create counter or + 1 operation if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { // Select a service using load policy return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } }
In this step, I use the load policy to select a service. As for the Rule policy, I will analyze it in the next step, and then look at the next step
3,execute
This step is to call the service
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 { // Send assembly return results 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; }
Ribbon's load and Nacos's service discovery process are basically analyzed!!