What is the Ribbon

Ribbon is a load balancer released by Netflix. Is one of the SpringCloud components used to implement client-side load balancing.

Server-side 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

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

Ribbon implementation source code

Load balancing in the ribbon is enabled via @loadBalanced. First take a look at the LoadBalanced annotation. Note that the annotated RestTemplate bean is configured to use LoadBalancerClient. The main execution is in the LoadBalancerClient object.

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
 * @author Spencer Gibb
 */
...
public @interface LoadBalanced {
}
Copy the code

LoadBalancerClient

I’m going to delete the comment here, so you can see that the execute method is definitely an execution method.

public interface LoadBalancerClient extends ServiceInstanceChooser {


	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
        
	URI reconstructURI(ServiceInstance instance, URI original);
}
Copy the code

Now think about when the executor method is executed.

Check the reference. It is called from an Interceptor method.

RibbonLoadBalancerClient.execute

Now let’s look at the execute method

public <T> T execute(String serviceId, LoadBalancerRequest<T> Request) throws IOException {// Obtain a loadBalancer object ILoadBalancer = getLoadBalancer(serviceId); Server = getServer(loadBalancer); RibbonServer = New RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); // Run return execute(serviceId, ribbonServer, request). }Copy the code
GetLoadBalancer method

From a SpringClientFactory object

protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
}
Copy the code
GetServer method

Perform loadBalancer. ChooseServer method

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

Look at the else logic and execute the parent’s chooseServer method

Public Server chooseServer(Object key) {// If (ENABLED. Get () && this.getLoadBalancerStats().getAvailableZones().size() > 1) { ... } else { logger.debug("Zone aware logic disabled or there is only one zone"); return super.chooseServer(key); }}Copy the code
BaseLoadBalancer chooseServer method
Public Server chooseServer(Object key) { If (this.counter == null) {this.counter = this.createcounter (); } this.counter.increment(); // Load balancing policy if (this.rule == null) {return null; } else { try { return this.rule.choose(key); } catch (Exception var3) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3}); return null; }}}Copy the code
PredicateBasedRule. Choose method

The main logic to get the server is to get all the server sets from the LoadBalancer object

Public Server choose(Object key) {// Obtain loadbalancer Object ILoadBalancer lb = getLoadBalancer(); // Select an instance to perform load balancing after filtering Optional< server > server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; }}Copy the code

This is how the Ribbon works. Here are the details.

Initialization query

Before you solve the problem, you need to understand the ribbon’s configuration classes. Generally, Settings are done during initial configuration. 1.LoadBalancerAutoConfiguration 2.RibbonAutoConfiguration 3.RibbonClientConfiguration

Question 1. When is the interceptor set? What objects are used to intercept?

Is load balance related to this problem, first locate the LoadBalancerAutoConfiguration class, detailed look at the following method

Public class LoadBalancerAutoConfiguration {/ / load balancing @ LoadBalanced / / injection all RestTemplate object @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 each executive RestTemplateCustomizer restTemplate. Customize method The customize method here is the following lambda expression, which sets the interceptor for (RestTemplateCustomizer customizer: customizers) { customizer.customize(restTemplate); }}}); }... @Configuration @ConditionalOnClass(RetryTemplate.class) public static class RetryInterceptorAutoConfiguration { @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory, loadBalancedRetryFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final RetryLoadBalancerInterceptor loadBalancerInterceptor) {/ / here use functional programming, This method is to set the interceptor for restTemplate return restTemplate - > {List < ClientHttpRequestInterceptor > List = new ArrayList < > ( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }}}Copy the code

So we know is in LoadBalancerAutoConfiguration class set up interceptors, intercept restTemplate object.

Question 2. When was SpringClientFactory set up?

As shown below, SpringClientFactory is initialized in the RibbonAutoConfiguration automatic configuration class

. public class RibbonAutoConfiguration { .... @Bean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; }... }Copy the code

Question 3. When is the LoadBalancer initialized? When was Rule initialized?

The problem is instantiated and requires implementation, so we guess it is related to the time when RibbonClient is generated. So I only found RibbonClientConfiguration class, retained the important code, the other… Instead,

Specific as follows

public class RibbonClientConfiguration { ... Rule @bean@conditionalonmissingBean public IRule ribbonRule(IClientConfig config) { Generates configuration if (this. PropertiesFactory. IsSet (IRule. Class name)) {return this. PropertiesFactory. Get (IRule. Class, config, name); } // Default partition isolation load balancing policy ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; } // same as rul@bean@conditionalonmissingbean public IPing ribbonPing(IClientConfig config) {if (this.propertiesFactory.isSet(IPing.class, name)) { return this.propertiesFactory.get(IPing.class, config, name); } return new DummyPing(); } / / generated serverList when the object is empty @ 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); } ConfigurationBasedServerList serverList = new ConfigurationBasedServerList(); serverList.initWithNiwsConfig(config); return serverList; } @Bean @ConditionalOnMissingBean public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { return new PollingServerListUpdater(config); } @bean@conditionalonmissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater ServerListUpdater) {// If config file is configured, The configuration file if (this. PropertiesFactory. IsSet (ILoadBalancer. Class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); } LoadBalancer return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }}Copy the code

Can see RibbonClientConfiguration generates all of the components needed for the ribbon, form the service ribbon.

Question 4. When was the serverList loaded?

Can see RibbonClientConfiguration generated empty serverList object, and then create the empty object into the loadbalancer. So let’s focus on the LoadBalancer initialization process

The superclass construct is called first

public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
    super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
Copy the code

When we enter the superclass construction, we ignore the attribute assignment part and focus on the restOfInit method

 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());
    }
    restOfInit(clientConfig);
}
Copy the code
DynamicServerListLoadBalancer restOfInit method

Enter restOfInit method, basically see enableAndInitLearnNewServersFeature and updateListOfServers methods

void restOfInit(IClientConfig clientConfig) { boolean primeConnection = this.isEnablePrimingConnections(); // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList() this.setEnablePrimingConnections(false); / / timing task refreshes the server cache list enableAndInitLearnNewServersFeature (); UpdateListOfServers () = 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

DynamicServerListLoadBalancer enableAndInitLearnNewServersFeature method

public void enableAndInitLearnNewServersFeature() { LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName()); serverListUpdater.start(updateAction); Public synchronized void start(final UpdateAction) {if (isActive.compareAndSet(false, synchronized); WrapperRunnable = new Runnable() {@override public void run() {if (! isActive.get()) { if (scheduledFuture ! = null) { scheduledFuture.cancel(true); } return; } try {// update updateaction.doupDate (); lastUpdated = System.currentTimeMillis(); } catch (Exception e) { logger.warn("Failed one update cycle", e); }}}; ScheduledFuture = getRefreshExecutor(). ScheduleWithFixedDelay (wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } else { logger.info("Already active, no-op"); }}Copy the code

So let’s take a look at the updateAction object and the doUpdate method,

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateListOfServers(); }};Copy the code
DynamicServerListLoadBalancer updateListOfServers method

Here is how to update the list of services

public void updateListOfServers() { List<T> servers = new ArrayList<T>(); if (serverListImpl ! = null) {/ / access server set the servers = serverListImpl getUpdatedListOfServers (); . } updateAllServerList(servers); }Copy the code

The serverListImpl is the concrete registry client implementation that sends a request to the registry for a list of services

This is how the Ribbon starts and executes