Related reading:

SpringCloud source code read 0-SpringCloud essential knowledge

SpringCloud source code read 1-EurekaServer source code secrets

SpringCloud source code read 2-Eureka client secrets

3 Ribbon Load Balancing

The configuration file

Like other microservice components and spring integration processes, the Ribbon has an automatic configuration file. RibbonAutoConfiguration

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
	// Load the configuration specification
	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>();
	// Load the hunger attribute.
	@Autowired
	private RibbonEagerLoadProperties ribbonEagerLoadProperties;
	// Ribbon features class
	@Bean
	public HasFeatures ribbonFeature(a) {
		return HasFeatures.namedFeature("Ribbon", Ribbon.class);
	}
	// Client production factory
	@Bean
	public SpringClientFactory springClientFactory(a) {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
	// Load balancing client
	@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient(a) {
		return newRibbonLoadBalancerClient(springClientFactory()); }... . }Copy the code

Here’s what’s in a configuration file.

RibbonClientSpecification:

RibbonClient specification. A specification corresponds to a type of RibbonClient. How does the specification work?

  • @RibbonClients: Specifications specified for all services.
  • @RibbonClient: For some of the specifications.

The two annotations will introduce a RibbonClientConfigurationRegistrar class. From its name, we can also tell that this is a registration class used to register client configurations.

RibbonClientConfigurationRegistrar will @ RibbonClients with @ RibbonClient annotation corresponding configuration class, register for a class RibbonClientSpecification Bean.

  • The corresponding configuration class is passed in as an argument to the constructor.
  • Is passed in as a construct parameter.

So you got RibbonClientSpecification specification list.

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);// Client name
		builder.addConstructorArgValue(configuration);// Corresponding configuration class.
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}
Copy the code

As discussed in the Ribbon Load Balancing (1) section, @RibbonClients and @RibbonClient replace the default component with a custom client component.

So the difference in specifications is actually the difference in individual components.

Note: Value attribute of @RibbonClients, You can configure the plural @RibbonClient(value = {@RibbonClient(name = “XXX “,configuration =) XxxRibbonConfig.class),@RibbonClient(name = “demo”,configuration = DemoRibbonConfig.class) })

The @RibbonClients defaultConfiguration property is used to replace the default components of all non-custom clients

SpringClientFactory:

Each microservice invokes multiple microservices. The RibbonClient configurations for invoking different microservices may be different. SpringClientFactory according to different RibbonClient specification (RibbonClientSpecification), to create a context for the different micro service. To store RibbonClient component beans of different specifications. In order to achieve the purpose of individuality coexist.

You can see from the code that SpringClientFactory passes in List Configurations

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

For example, the user service needs to be deployed. The @RibbonClient(name = “A”, Configuration = AConfiguration. Class) service A defines IPing as MyIPing. Three contexts are created:

  • Create A context, using A.R ibbonClientSpecification specification, corresponding Bean is MyMyIPing IPing
  • B context, using the default. Create RibbonClientSpecification specification, corresponding Bean is DummyPing IPing
  • C context, using the default. Create RibbonClientSpecification specification, corresponding Bean is DummyPing IPing
RibbonClientConfiguration

SpringClientFactory initialized to its parent class, passing RibbonClientConfiguration configuration class as RibbonClient default configuration.

public SpringClientFactory(a) {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}

public NamedContextFactory(Class
        defaultConfigType, String propertySourceName, String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
}
Copy the code

RibbonClientConfiguration configuration register in the class of Ribbon is the default components

EurekaRibbonClientConfiguration

When used with Eureka, RibbonEurekaAutoConfiguration using the @ RibbonClients introduce annotations EurekaRibbonClientConfiguration configuration class to cover RibbonClient default configuration of a part of the components.

@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {}Copy the code

EurekaRibbonClientConfiguration configuration class will cover:

  • DiscoveryEnabledNIWSServerList replace ribbonServerList, default installation a DiscoveryEnabledNIWSServerList DomainExtractingServerList agent
  • NIWSDiscoveryPing Replaces (IPing) DummyPing

RibbonLoadBalancerClient

RibbonLoadBalancerClient is the load balancer client.

From this client, we can pass in the service ID and select the corresponding configuration context from the springClientFactory. Load balancing is achieved using a set of load balancing components applicable to the current service.

@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient(a) {
		return new RibbonLoadBalancerClient(springClientFactory());
	}
Copy the code

Load Balancing Principle

Routing and Load

LoadBalancerClient

RibbonLoadBalancerClient# choose method. Find a service from multiple copies by passing in the service name to achieve load balancing.

@Override
	public ServiceInstance choose(String serviceId) {
		Server server = getServer(serviceId);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}
protected Server getServer(String serviceId) {
		return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
Copy the code

RibbonLoadBalancerClient# choose method call loadBalancer. ChooseServer

ILoadBalancer: load balancer

Get the load balancer from the factory, where the Bean is ZoneAwareLoadBalancer as stated in the configuration class above

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

ZoneAwareLoadBalancer# chooseServer method

ZoneAwareLoadBalancer
public Server chooseServer(Object key) {...// Call the parent's chooseServer (key) directly by default for a region case
 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); }}... }BaseLoadBalancer
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
IRule: load balancing policy

Rule. Choose (key) Selects a service from the list based on the policy.

The default IRule is ZoneAvoidanceRule.

The Choose method is in its parent class, PredicateBasedRule

 public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null; }}Copy the code

You can see that ILoadBalancer#getAllServers is used to get all the services. In the policy execution method passed in, select a service.

Get and update services

So ILoadBalancer allServerList is how to store all the service?

ServerListUpdater: Service update

The direct parent ZoneAvoidanceRule DynamicServerListLoadBalancer:

When the property is initialized, the UpdateAction property is initialized. UpdateAction is an internal interface to a ServerListUpdater, where an anonymous implementation class is initialized.

 protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate(a) {
            updateListOfServers();// Update the list of services.}};Copy the code

When initializing the constructor: PollingServerListUpdater() is created by default.

And invoke restOfInit (clientConfig), then call enableAndInitLearnNewServersFeature (); Methods.

public void enableAndInitLearnNewServersFeature(a) {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);// Perform the updateAction action.
}
Copy the code

Finally, there is a scheduled scheduler in the PollingServerListUpdater that periodically executes the UpdateAction task to update the list of services. By default, a task is executed one second after it is created. The interval between the last execution and the next execution is 30 seconds by default.

scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
);
Copy the code
ServerList Service list

UpdateAction#doUpdate() calls updateListOfServers() to update the list of services.

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

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

The serverListImpl implementation class is DiscoveryEnabledNIWSServerList DiscoveryEnabledNIWSServerList# getUpdatedListOfServers Perform obtainServersViaDiscovery method

private List<DiscoveryEnabledServer> obtainServersViaDiscovery(a) {
 / / get DiscoveryClient
 EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
 // Get the list of services
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion); . }Copy the code

The eurekaClientProvider is essentially a proxy to DiscoveryManager. DiscoveryManager in the Eureka client secret is the manager of DiscoveryClient.

EurekaClient. GetInstancesByVipAddress eventually call DiscoveryClient. LocalRegionApps access service list.

DiscoveryClient. LocalRegionApps in Eureka client secret have said is the client service cache list.

From here, we can also see that the Ribbon gets the service list from DiscoveryClient when it works with Eureka.

ServerListFilter Service list filter

When the list of services is retrieved from the updateListOfServers method, instead of returning it directly, it is filtered through ServerListFilter

This is the default ZonePreferenceServerListFilter, can filter out with regional service instance, namely area is preferred

servers = filter.getFilteredListOfServers(servers);
Copy the code
IPing: Checks the service status

After filtering is done in the updateListOfServers method, the last operation updateAllServerList is done.

updateAllServerList(servers);

 protected void updateAllServerList(List<T> ls) {
        // other threads might be doing this - in which case, we pass
        if (serverListUpdateInProgress.compareAndSet(false.true)) {
            try {
                for (T s : ls) {
                    s.setAlive(true); // set so that clients can start using these
                }
                setServersList(ls);
                super.forceQuickPing();
            } finally {
                serverListUpdateInProgress.set(false); }}}Copy the code

The final step in updateAllServerList is the ping operation, which checks for service viability. The default is DummyPing,

 public boolean isAlive(Server server) {
        return true;// The default is always alive.
    }
Copy the code

Ping task actually has a scheduled task:

BaseLoadBalancer Load balancer, which creates a scheduled task during initialization. NFLoadBalancer -pingtimer – Executes the Ping task periodically at a 10-second interval

public BaseLoadBalancer(a) {
        this.name = DEFAULT_NAME;
        this.ping = null;
        setRule(DEFAULT_RULE);
        setupPingTask();
        lbStats = new LoadBalancerStats(DEFAULT_NAME);
    }
Copy the code

At this point, the Ribbon provides an overview of how load balancing works, because the purpose of this article is to explain how the Ribbon works. Specific policy details for IRule are beyond the scope of this article and will be discussed at a later opportunity.

conclusion

When Ribbon is used with Eureka, the Ribbon retrieves a list of services from the Eureka client’s cache.

When using Ribbon, we do not directly use RibbonLoadBalancerClient. Instead, we often use Resttemplate+ @loadBalanced to send requests. So how does @loadbalanced make the Resttemplate LoadBalanced?

See the next article