Series of articles:
SpringCloud source series (1) – registry initialization for Eureka
SpringCloud source code series (2) – Registry Eureka service registration, renewal
SpringCloud source code series (3) – Registry Eureka crawl registry
SpringCloud source code series (4) – Registry Eureka service offline, failure, self-protection mechanism
SpringCloud source series (5) – Registry EurekaServer cluster for Eureka
SpringCloud source Code Series (6) – Summary of the Registry Eureka
SpringCloud source series (7) – load balancing Ribbon RestTemplate
ILoadBalancer load balancer
One of the most important components of a RestTemplate load balancing capability is the ILoadBalancer, because it is used to obtain the Server that can be called. The original URI with the service name can be refactored with the Server. This section takes a look at how the Ribbon load balancer is created and how to use it to fetch servers.
RibbonLoadBalancerClient:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// Get ILoadBalancer from SpringClientFactory
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// Use the load balancer to obtain the Server
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
SpringClientFactory and context
As you learned from the previous article, the ILoadBalancer is retrieved by SpringClientFactory when the RibbonLoadBalancerClient performs load balancing requests. From getInstance step by step, finally will enter into the NamedContextFactory, from getContext method can be found that different service will create a AnnotationConfigApplicationContext, That is, an ApplicationContext ApplicationContext. That is, each service has its own context that binds to a different ILoadBalancer.
RibbonLoadBalancerClient:
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
Copy the code
SpringClientFactory:
public ILoadBalancer getLoadBalancer(String name) {
/ / get ILoadBalancer
return getInstance(name, ILoadBalancer.class);
}
public <C> C getInstance(String name, Class<C> type) {
// From the parent NamedContextFactory class
C instance = super.getInstance(name, type);
if(instance ! =null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
return instantiateWithConfig(getContext(name), type, config);
}
Copy the code
NamedContextFactory:
/** * name - service name * type - The type of the bean to get */
public <T> T getInstance(String name, Class<T> type) {
// Obtain by name
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return context.getBean(type);
}
return null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
// contexts => Map<String, AnnotationConfigApplicationContext>
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name)); }}}return this.contexts.get(name);
}
Copy the code
Debugging see AnnotationConfigApplicationContext context, you can see in the and the service binding ILoadBalancer, IClientConfig, RibbonLoadBalancerContext, etc.
Why does it bind an ApplicationContext for each service here? Because the list of service instances can come from multiple sources, such as from the Eureka registry, through code configuration, through configuration files, and there are many personalized configurations for each service, including default configurations, customized global configurations, service-specific configurations, and so on, In doing so, it makes it easy for users to customize the load balancing strategy for each service.
Create ILoadBalancer
Where is ILoadBalancer created? See RibbonClientConfiguration, this configuration class provides a default ILoadBalancer creation method, can see the default implementation of ILoadBalancer ZoneAwareLoadBalancer class.
public class RibbonClientConfiguration {
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName
private String name = "client";
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig(a) {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
// You can see that the default connection timeout and read timeout are both 1 second
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@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();
}
@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
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList
serverList, ServerListFilter
serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater)
{
// Check whether a load balancer is configured in the configuration file
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
// Create by reflection
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return newZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }}Copy the code
IClientConfig, ServerList
, ServerListFilter
, IRule, IPing, ServerListUpdater, These six interfaces and ILoadBalancer are the core interfaces of the Ribbon. Together, they define the Ribbon’s behavior.
Can know from RibbonClientConfiguration these seven core interface and the default implementation class:
ILoadBalancer choose Server
Now that we have ILoadBalancer, we need to get the Server. As can be seen from the getServer method, RibbonLoadBalancerClient uses ILoadBalancer to obtain the Server.
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
returnloadBalancer.chooseServer(hint ! =null ? hint : "default");
}
Copy the code
ILoadBalancer default implementation class is ZoneAwareLoadBalancer, enter its chooseServer method, if only one zone is configured, go to the parent class chooseServer, otherwise select instances from multiple zones.
public Server chooseServer(Object key) {
/ / ENABLED = > ZoneAwareNIWSDiscoveryLoadBalancer. ENABLED by default is true
AvailableZones is configured with only one defaultZone
if(! ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <=1) {
logger.debug("Zone aware logic disabled or there is only one zone");
// Get the Server logic from the parent class
return super.chooseServer(key);
}
// Multi-zone logic....
}
Copy the code
First look at the class hierarchy structure of the ZoneAwareLoadBalancer, ZoneAwareLoadBalancer DynamicServerListLoadBalancer is the direct parent class, The parent class is BaseLoadBalancer DynamicServerListLoadBalancer.
ZoneAwareLoadBalancer calls the parent’s chooseServer method in BaseLoadBalancer, which uses IRule to select instances, leaving the policy to the IRule interface.
public Server chooseServer(Object key) {
if (rule == null) {
return null;
} else {
try {
// IRule
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null; }}}Copy the code
IRule polling selects the Server
The default implementation class of IRule is ZoneAvoidanceRule, and first look at the inheritance structure of ZoneAvoidanceRule. The immediate parent of ZoneAvoidanceRule is PredicateBasedRule.
In PredicateBasedRule, getPredicate() returns a CompositePredicate created by ZoneAvoidanceRule, It is this assertion that is used to filter out available servers and return a Server through a polling policy. The source of the Server list is obtained through ILoadBalancer’s getAllServers() method.
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// getPredicate() Server predicate => CompositePredicate
// RoundRobin obtains an instance in RoundRobin mode
// Servers => Obtain all servers from the LB load balancer
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null; }}Copy the code
ILoadBalancer is responsible for getting the list of servers, while IRule is responsible for selecting a Server from the list of servers by a policy.
When initializes the ZoneAvoidanceRule configuration, a CompositePredicate is created, and you can see that the CompositePredicate has two main assertions: one asserts whether the zone of the Server is available, and one asserts whether the Server itself is available. For example, the Server cannot be pinged through.
public void initWithNiwsConfig(IClientConfig clientConfig) {
// Declare whether the Server zone is available, even if there is only one defaultZone
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this, clientConfig);
// Assert whether the Server is available
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this, clientConfig);
// Encapsulate composite assertions
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
// The builder pattern creates assertions
return CompositePredicate.withPredicates(p1, p2)
.addFallbackPredicate(p2)
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
.build();
}
Copy the code
Then choose the Server chooseRoundRobinAfterFiltering, parameters of the servers is obtained through ILoadBalancer all instances, You can see that it actually returns all the servers that ILoadBalancer has cached in memory. We’ll see where this Server came from later.
public List<Server> getAllServers(a) {
// allServerList => List<Server>
return Collections.unmodifiableList(allServerList);
}
Copy the code
All instances are filtered by assertions to remove unavailable servers, and then a Server is returned by polling. This is how ILoadBalancer (ZoneAwareLoadBalancer) selects the Server via IRule (ZoneAvoidanceRule) on default configuration, which uses a polling policy to get the Server.
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
// Assert to get the available Server
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
// Polling the Server by fetching a mold
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null) {
return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList();
// Assert for each Server
for (Server server: servers) {
if (this.apply(newPredicateKey(loadBalancerKey, server))) { results.add(server); }}returnresults; }}private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
// The modular operation takes the remainder
int next = (current + 1) % modulo;
// CAS updates nextIndex
if (nextIndex.compareAndSet(current, next) && current < modulo)
returncurrent; }}Copy the code
The Ribbon integrates EurekaClient to pull the Server list
Lb.getallservers () = lb.getallServers () = lb.getallServers ();
ILoadBalancer initialization
The default implementation of ILoadBalancer class is ZoneAwareLoadBalancer. Look at the ZoneAwareLoadBalancer constructor to see what’s going on.
@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
As you can see, ZoneAwareLoadBalancer directly call the parent class DynamicServerListLoadBalancer constructor, DynamicServerListLoadBalancer first calls the superclass BaseLoadBalancer initialization, and then did some remaining initialization.
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
serverList, ServerListFilter
filter, ServerListUpdater serverListUpdater)
{
// DynamicServerListLoadBalancer
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
serverList, ServerListFilter
filter, ServerListUpdater serverListUpdater)
{
// BaseLoadBalancer
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// Initialize the rest
restOfInit(clientConfig);
}
public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
/ / createLoadBalancerStatsFromConfig = > LoadBalancerStats statistics
initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
Copy the code
BaseLoadBalancer initWithConfig (BaseLoadBalancer);
- Set IPing and IRule. The ping interval is
30 seconds
.setPing
Will start a background scheduled task, and then every other30 seconds
To run aPingTask
Task. - The ILoadBalancer statistic is set
LoadBalancerStats
To collect statistics on ILoadBalancer Server status, such as connection failure, success, and fuses. - With PrimeConnections requests preheated, create PrimeConnections to preheat the connection between the client and the Server. The default is off.
- Finally, some monitoring is registered and request preheating is enabled.
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
this.config = clientConfig;
String clientName = clientConfig.getClientName();
this.name = clientName;
// Ping interval, 30 seconds by default
int pingIntervalTime = Integer.parseInt(clientConfig.getProperty(CommonClientConfigKey.NFLoadBalancerPingInterval, Integer.parseInt("30")));
// Don't see any use for it
int maxTotalPingTime = Integer.parseInt(clientConfig.getProperty(CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime, Integer.parseInt("2")));
// Set the ping interval and reset the ping task
setPingInterval(pingIntervalTime);
setMaxTotalPingTime(maxTotalPingTime);
// Set IRule and IPing
setRule(rule);
setPing(ping);
// PrimeConnections, request preheating, default off
// This function is mainly used to solve the deployment environment (such as read EC2) before the actual use of real-time requests, from the firewall connection/path warm-up (such as whitelist, initialization, etc. It is time-consuming, can be used to get through).
boolean enablePrimeConnections = clientConfig.get(CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
if (enablePrimeConnections) {
this.setEnablePrimingConnections(true);
PrimeConnections primeConnections = new PrimeConnections(this.getName(), clientConfig);
this.setPrimeConnections(primeConnections);
}
// Register some monitoring
init();
}
protected void init(a) {
Monitors.registerObject("LoadBalancer_" + name, this);
// register the rule as it contains metric for available servers count
Monitors.registerObject("Rule_" + name, this.getRule());
// It is disabled by default
if(enablePrimingConnections && primeConnections ! =null) { primeConnections.primeConnections(getReachableServers()); }}Copy the code
Again see DynamicServerListLoadBalancer initialization, the core of initialization logic in restOfInit, main is to do two things:
- Enable dynamic update Server features, such as instance online, offline, failure, etc., to update the list of ILoadBalancer servers.
- The local Server list is then fully updated.
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
// Enable dynamic Server update
enableAndInitLearnNewServersFeature();
// Update the Server list
updateListOfServers();
// Preheat available servers with request preheating enabled
if (primeConnection && this.getPrimeConnections() ! =null) {
this.getPrimeConnections().primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
}
Copy the code
Update the Server list in full
Take a look at how updateListOfServers() updates the Server list and then how ILoadBalancer stores the Server.
- The first to use
ServerList
Get the list of all servers inRibbonClientConfiguration
Is configured inConfigurationBasedServerList
After, but had inherited, is not ConfigurationBasedServerList, then come back the next section. - Then use the
ServerListFilter
The default implementation class for Server list filtering isZonePreferenceServerListFilter
, which filters out the servers in the current Zone (defaultZone). - The last step is to update the list of all servers, starting with Settings
Server alive
, and then call the parent class (BaseLoadBalancer)setServersList
To update the Server list, which indicates that the Server is stored inBaseLoadBalancer
In the.
public void updateListOfServers(a) {
List<T> servers = new ArrayList<T>();
if(serverListImpl ! =null) {
// Get the list of all servers from ServerList
servers = serverListImpl.getUpdatedListOfServers();
if(filter ! =null) {
// Use ServerListFilter to filter the Serverservers = filter.getFilteredListOfServers(servers); }}// Update all servers to the local cache
updateAllServerList(servers);
}
protected void updateAllServerList(List<T> ls) {
if (serverListUpdateInProgress.compareAndSet(false.true)) {
try {
for (T s : ls) {
s.setAlive(true); // Set Server alive
}
setServersList(ls);
// Forcibly initialize Ping
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false); }}}public void setServersList(List lsrv) {
// BaseLoadBalancer
super.setServersList(lsrv);
// Update Server to LoadBalancerStats statistics....
}
Copy the code
AllServerList, the data structure that stores all servers, is a thread-safe container with synchronized. SetServersList simply replaces allServerList with the resulting list of servers.
protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>());
public void setServersList(List lsrv) {
Lock writeLock = allServerLock.writeLock();
ArrayList<Server> newServers = new ArrayList<Server>();
/ / write locks
writeLock.lock();
try {
// The for loop transfers the Server in LSRV to allServers
ArrayList<Server> allServers = new ArrayList<Server>();
for (Object server : lsrv) {
if (server instanceof String) {
// Static configuration of the IP :port format
server = new Server((String) server);
}
if (server instanceof Server) {
// The Server synchronized from EurekaClient
allServers.add((Server) server);
} else {
throw new IllegalArgumentException("Type String or Server expected, instead found:"+ server.getClass()); }}// If service warmup is enabled, start Server warmup...
// Direct substitution
allServerList = allServers;
// Ping To check the Server status
if (canSkipPing()) {
for (Server s : allServerList) {
s.setAlive(true);
}
upServerList = allServerList;
} else if(listChanged) { forceQuickPing(); }}finally {
// Release the write lockwriteLock.unlock(); }}Copy the code
Front chooseRoundRobinAfterFiltering to obtain a list of all Server is returning the allServerList.
public List<Server> getAllServers(a) {
return Collections.unmodifiableList(allServerList);
}
Copy the code
Eureka Ribbon client configuration
Access to the Server component is ServerList, RibbonClientConfiguration configured in the default implementation class is ConfigurationBasedServerList. ConfigurationBasedServerList default from the configuration file, you can like this configuration service instance address, multiple Server addresses with commas.
demo-producer:
ribbon:
listOfServers: http://10.215.0.92:8010,http://10.215.0.92:8011
Copy the code
But when combined with Eureka-client, which introduces spring-cloud-starter-Netflix-Eureka-client’s client-side dependencies, It will help us to introduce the spring – the cloud – netflix – eureka – client dependence, this package has a RibbonEurekaAutoConfiguration automation configuration class, It through @ RibbonClients annotation defines the Ribbon of global client configuration such as EurekaRibbonClientConfiguration.
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {}Copy the code
Enter the EurekaRibbonClientConfiguration can see:
- The default implementation class for IPing is
NIWSDiscoveryPing
. - ServerList default implementation class for DomainExtractingServerList, but DomainExtractingServerList at construction time into another type
DiscoveryEnabledNIWSServerList
ServerList. The name can be seen, presumably, DiscoveryEnabledNIWSServerList is obtained from EurekaClient Server components.
@Configuration(proxyBeanMethods = false)
public class EurekaRibbonClientConfiguration {
@Value("${ribbon.eureka.approximateZoneFromHostname:false}")
private boolean approximateZoneFromHostname = false;
@RibbonClientName
private String serviceId = "client";
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
return this.propertiesFactory.get(IPing.class, config, serviceId);
}
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
}
@Bean
@ConditionalOnMissingBean
publicServerList<? > ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname);
returnserverList; }}Copy the code
Obtain the Server list from DiscoveryClient
In DynamicServerListLoadBalancer ServerList getUpdatedListOfServers method the whole amount for services through the list, in eureka – client environment, ServerList default implementation class for DomainExtractingServerList, then you have to look at it first getUpdatedListOfServers method.
It can be seen that DomainExtractingServerList with DomainExtractingServerList access service list first, and then according to the Ribbon client configuration to reconstruct the Server object to return. To obtain a list service core in DiscoveryEnabledNIWSServerList.
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers(a) {
// list => DiscoveryEnabledNIWSServerList
List<DiscoveryEnabledServer> servers = setZones(this.list.getUpdatedListOfServers());
return servers;
}
private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) {
List<DiscoveryEnabledServer> result = new ArrayList<>();
boolean isSecure = this.ribbon.isSecure(true);
boolean shouldUseIpAddr = this.ribbon.isUseIPAddrForServer();
// DomainExtractingServer is restructured according to the client configuration
for (DiscoveryEnabledServer server : servers) {
result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname));
}
return result;
}
Copy the code
First look at the structure of the DiscoveryEnabledNIWSServerList initialization:
- It’s mostly incoming
Provider<EurekaClient>
Used to get EurekaClient. - Also set the clientName clientName, and vipAddresses are also the clientName, which you’ll need later.
public DiscoveryEnabledNIWSServerList(IClientConfig clientConfig, Provider<EurekaClient> eurekaClientProvider) {
this.eurekaClientProvider = eurekaClientProvider;
initWithNiwsConfig(clientConfig);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// The client name is the service name
clientName = clientConfig.getClientName();
// vipAddresses get the client name
vipAddresses = clientConfig.resolveDeploymentContextbasedVipAddresses();
// Some other configurations....
}
Copy the code
Looking at the getUpdatedListOfServers that get the instance, you can see that its core logic is to get the InstanceInfo instance list from EurekaClient based on the service name, and then return the Server information wrapped around it.
public List<DiscoveryEnabledServer> getUpdatedListOfServers(a){
return obtainServersViaDiscovery();
}
private List<DiscoveryEnabledServer> obtainServersViaDiscovery(a) {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
// Get EurekaClient. The actual type is CloudEurekaClient and its parent is DiscoveryClient
EurekaClient eurekaClient = eurekaClientProvider.get();
if(vipAddresses! =null) {// Split vipAddresses, default is the service name
for (String vipAddress : vipAddresses.split(",")) {
// Get instance information from EurekaClient based on the service name
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
// Create a Server based on InstanceInfoDiscoveryEnabledServer des = createServer(ii, isSecure, shouldUseIpAddr); serverList.add(des); }}}}return serverList;
}
Copy the code
Note that vipAddress is the service name:
Finally, the getInstancesByVipAddress of EurekaClient is clearly a list of all instances from the service name in the native application Applications of DiscoveryClient.
Eureka-client fully captures the registry and increments capture the registry every 30 seconds. These are incorporated into native Applications. When Ribbon is combined with Eureka, the Ribbon retrieves the Server list from DiscoveryClient’s Applications.
public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, String region) {
// ...
Applications applications;
if (instanceRegionChecker.isLocalRegion(region)) {
// Take the local application
applications = this.localRegionApps.get();
} else {
applications = remoteRegionVsApps.get(region);
if (null == applications) {
returnCollections.emptyList(); }}if(! secure) {// Return the instance corresponding to the service name
return applications.getInstancesByVirtualHostName(vipAddress);
} else {
returnapplications.getInstancesBySecureVirtualHostName(vipAddress); }}Copy the code
Update the Server list periodically
DynamicServerListLoadBalancer initialization time, didn’t say there is a way, is enableAndInitLearnNewServersFeature (). This method simply calls the ServerListUpdater to launch an UpdateAction, which in turn simply calls the updateListOfServers method, which is the logic of fully updating the Server described earlier.
public void enableAndInitLearnNewServersFeature(a) {
serverListUpdater.start(updateAction);
}
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate(a) {
/ / call updateListOfServersupdateListOfServers(); }};Copy the code
PollingServerListUpdater PollingServerListUpdater PollingServerListUpdater PollingServerListUpdater PollingServerListUpdater PollingServerListUpdater
It’s basically calling the updateListOfServers method at a fixed frequency of 30 seconds, Synchronize instances cached in Applications in DiscoveryClient to the allServerList list in ILoadBalancer.
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false.true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run(a) {
// Perform an updateListOfServers
updateAction.doUpdate();
// Set the last update timelastUpdated = System.currentTimeMillis(); }};// Fixed frequency scheduling
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs, / / the default 1000
refreshIntervalMs, // The default is 30 x 1000
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op"); }}Copy the code
How long does it take the Ribbon client to register a new instance?
- First DiscoveryClient every other
30 seconds
Fetching an incremental registry from the registry first reads from the read-only cache - Then eureka server registry is designed with a three-tier cache structure, every other
30 seconds
Synchronize from readWriteMap to readOnlyMap - After instances of the registry are synchronized locally to the client, the Ribbon will interval them
30 seconds
Synchronize from DiscoveryClient to BaseLoadBalancer
To summarize, this means that the Ribbon load balancing may take up to 90 seconds to detect new instances registered in the registry.
Check whether the Server is alive
IPing has not seen how this works when it creates ILoadBalancer. On initialization, you can see that you basically set up the current ping and then reset a scheduling task to schedule PingTask every 30 seconds by default.
public void setPing(IPing ping) {
if(ping ! =null) {
if(! ping.equals(this.ping)) {
this.ping = ping;
// Set the Ping tasksetupPingTask(); }}else {
this.ping = null;
// cancel the timer tasklbTimer.cancel(); }}void setupPingTask(a) {
// ...
// Create a timer scheduler
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name, true);
// pingIntervalTime defaults to 30 seconds, and PingTask is scheduled every 30 seconds
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
// Initiate Ping immediately
forceQuickPing();
}
Copy the code
ShutdownEnabledTimer is a descendant of Timer. It registers a callback with the Runtime at creation time to cancel the Timer execution when the JVM is shut down, freeing resources.
public class ShutdownEnabledTimer extends Timer {
private Thread cancelThread;
private String name;
public ShutdownEnabledTimer(String name, boolean daemon) {
super(name, daemon);
this.name = name;
// Cancel the timer thread
this.cancelThread = new Thread(new Runnable() {
public void run(a) {
ShutdownEnabledTimer.super.cancel(); }});// Register a hook with the Runtime that cancelThread cancels scheduled tasks when the JVM shuts down
Runtime.getRuntime().addShutdownHook(this.cancelThread);
}
@Override
public void cancel(a) {
super.cancel();
try {
// Remove the callback
Runtime.getRuntime().removeShutdownHook(this.cancelThread);
} catch (IllegalStateException ise) {
LOGGER.info("Exception caught (might be ok if at shutdown)", ise); }}}Copy the code
The core logic of PingTask is to iterate through the list of allServers, use IPingStrategy and IPing to determine whether the Server is alive and update the status of the Server. And update all live servers to the upServerList, which caches all live servers.
class PingTask extends TimerTask {
public void run(a) {
try {
// pingStrategy => SerialPingStrategy
new Pinger(pingStrategy).runPinger();
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error pinging", name, e); }}}class Pinger {
private final IPingStrategy pingerStrategy;
public Pinger(IPingStrategy pingerStrategy) {
this.pingerStrategy = pingerStrategy;
}
public void runPinger(a) throws Exception {
if(! pingInProgress.compareAndSet(false.true)) {
return; // Ping in progress - nothing to do
}
Server[] allServers = null;
boolean[] results = null;
Lock allLock = null;
Lock upLock = null;
try {
allLock = allServerLock.readLock();
allLock.lock();
// Add lock to allServerList
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
Use IPingStrategy and IPing to ping all servers
results = pingerStrategy.pingServers(ping, allServers);
final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = results[i];
Server svr = allServers[i];
boolean oldIsAlive = svr.isAlive();
// Set alive to whether to live
svr.setAlive(isAlive);
// Instance change
if(oldIsAlive ! = isAlive) { changedServers.add(svr); }// Add a living Server
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
// Update the upServerList. The upServerList stores only the surviving servers
upServerList = newUpList;
upLock.unlock();
// Notify the change
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false); }}}Copy the code
The default implementation class of IPingStrategy is SerialPingStrategy, which simply iterates through all servers and uses IPing to determine whether the Server is alive or not.
private static class SerialPingStrategy implements IPingStrategy {
@Override
public boolean[] pingServers(IPing ping, Server[] servers) {
int numCandidates = servers.length;
boolean[] results = new boolean[numCandidates];
for (int i = 0; i < numCandidates; i++) {
results[i] = false;
try {
if(ping ! =null) {
// Run IPing to check whether the Server is aliveresults[i] = ping.isAlive(servers[i]); }}catch (Exception e) {
logger.error("Exception while pinging Server: '{}'", servers[i], e); }}returnresults; }}Copy the code
After eureka-client is integrated, the default implementation class of IPing is NIWSDiscoveryPing. The isAlive method is used to determine whether the InstanceInfo of the corresponding Server is in UP state. The UP state indicates that the Server is alive.
public boolean isAlive(Server server) {
boolean isAlive = true;
if(server! =null && server instanceof DiscoveryEnabledServer){
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if(instanceInfo! =null){
InstanceStatus status = instanceInfo.getStatus();
if(status! =null) {// Check whether the Server instance is UPisAlive = status.equals(InstanceStatus.UP); }}}return isAlive;
}
Copy the code
One diagram summarizes the core principles of the Ribbon
1. Summary of Ribbon core working principles
First, the seven core interfaces of the Ribbon define the Ribbon’s behavior. They are the core framework of the Ribbon.
- Use the Ribbon to load balance clients
@LoadBalanced
The annotation annotates a RestTemplate bean object, followed by theLoadBalancerAutoConfiguration
The RestTemplate with the @loadBalanced annotation is added to the configuration classLoadBalancerInterceptor
The interceptor. - The LoadBalancerInterceptor intercepts HTTP requests from RestTemplate, binds the request to the Ribbon load balancing lifecycle, and then uses the
LoadBalancerClient
的execute
Method to process the request. - The LoadBalancerClient first gets one
ILoadBalancer
, and then use it to get a Server, which is the information encapsulation of a specific instance. Once you have the Server, you reconstruct the original URI using the IP and port of the Server. - ILoadBalancer will eventually pass when the instance is selected
IRule
Balancing policy to select a Server. - ILoadBalancer has one of its parents, BaseLoadBalancer
allServerList
Tables cache all servers. The source of servers in the Ribbon is allServerList. - ILoadBalancer is used when loading the Ribbon client context
ServerList
From the DiscoveryClientApplications
To obtain the list of instances corresponding to the client, and then useServerListFilter
Filter and finally update to allServerList. - ILoadBalancer also starts a background task
ServerListUpdater
every30 seconds
Run once to synchronize the list of instances in DiscoveryClient’s Applications to allServerList with ServerList. - ILoadBalancer also starts a background task
PingTask
every30 seconds
Run the IPing command once to check the Server status. In EurekaClient environment, the IPing command is used to check the Server statusInstanceInfo
Is the state ofUP
.
2. The following figure summarizes the core process of acquiring the Server through the Ribbon and the relationship between the corresponding core interfaces.