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