02 Deep Resolution Spring Cloud Ribbon--LoadBalancerAutoConfiguration Interceptor Injection

02 Deep Resolution Spring Cloud Ribbon--LoadBalancerAutoConfiguration Interceptor Injection

Introduction to LoadBalancerAutoConfiguration

In the previous chapter, we have analyzed that RestTemplate adds related interceptors to enable load balancing, and the implementation of adding intercepts is implemented in LoadBalancerAutoConfiguration with the following code and additional comments

@Configuration
@ConditionalOnClass(RestTemplate.class)  // Dependent on RestTemplate
@ConditionalOnBean(LoadBalancerClient.class)  // Bean of LoadBalancerClient needs to be injected in advance, where configuration class RibbonAutoConfiguration needs to be injected in advance
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    // This is the focus, the most important thing, here is to inject all estTemplates with @LoadBalanced 
    // This is because @Qualifier is included in @LoadBalanced, which is the key
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

    // This is to wait for the other beans to be injected before running afterSingletonsInstantiated
    // It's actually adding interceptors
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
			final List<RestTemplateCustomizer> customizers) {
		return new SmartInitializingSingleton() {
			@Override
			public void afterSingletonsInstantiated() {
				for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
					for (RestTemplateCustomizer customizer : customizers) {
						customizer.customize(restTemplate);
					}
				}
			}
		};
	}

    //LoadBalancerRequestTransformer interface
   // No implementation class
	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();


    //This is a RequestFactory for creating a new LoadBalancerRequest () 
	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

   //  This was injected without a retry mechanism. Take a look at the ConditionalOnMissingClass, which will be mentioned later
	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
	  
	    // Here's building a LoadBalancerInterceptor with LoadBalancerRequestFactory and loadBalancerClient 
	    @Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

       // This is adding interceptors, which will be called here later to add interceptors to RestTemplate
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return new RestTemplateCustomizer() {
				@Override
				public void customize(RestTemplate restTemplate) {
					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
							restTemplate.getInterceptors());
					list.add(loadBalancerInterceptor);
					restTemplate.setInterceptors(list);
				}
			};
		}
	}
	}

The code above has some comments to summarize

1. LoadBalancerAutoConfiguration's injection meets the criteria

RestTemplate.class exists
(3) Bean s of LoadBalancerClient need to be injected in advance, where configuration class RibbonAutoConfiguration needs to be injected in advance. As shown below, RibbonAutoConfiguration also has an elevated need to be injected before LoadBalancerAutoConfiguration.class, which fully matches.
In addition, the only implementation class for LoadBalancerClient is the RibbonLoadBalancerClient

2. LoadBalancerAutoConfiguration Content

Injection inside LoadBalancerAutoConfiguration:

  1. A LoadBalancerInterceptor interceptor was generated with parameters LoadBalancerClient and LoadBalancerRequestFactory
  2. A RestTemplate Customizer Bean was injected to add an interceptor to the estTemplate with @LoadBalanced, which is then called by the afterSingletonsInstantiated
  3. Generated a Bean for transformers, which has no implementation class and can be implemented by itself
  4. A Bean was generated for the loadBalancerRequestFactory, which is mainly used for creating a new LoadBalancerRequest ()
  5. The main point here is to inject a restTemplate with @LoadBalanced. Why can I collect all the notes with @LoadBalanced because there is a @Qualifier inside, which I can check out for myself. It's really good, it's awesome
  6. Instance of a SmartInitializingSingleton, mainly for adding interceptors to RestTemplate after other Bean injections.

3. Summary

Once the LoadBalancerAutoConfiguration autoconfiguration class injection is complete, the RestTemplate with the @LoadBalanced annotation will be load balanced.

2. Logic related to LoadBalancerInterceptor

1. Interept intercept content

Now that RestTemplate is capable of load balancing, let's look at how specific interceptors achieve load balancing. First, look at the code below:

From the source code above, you can see that:

  1. Get Request Link
  2. Get ServiceName, which is why typing ip:port into the requested link will cause an error
  3. Run execute from serviceName into LoadBalancerClient interface

Next, let's analyze this LoadBalancerClient, which is load balancing for the client and has only one implementation class (RibbonLoadBalancerClient). Let's see what interfaces LoadBalancerClient has, as follows:

public interface ServiceInstanceChooser {
    // Select an instance of the corresponding service from the load balancer based on the incoming serviceId
    ServiceInstance choose(String serviceId);
}

public interface LoadBalancerClient extends ServiceInstanceChooser {
    
    // Based on the incoming serviceId, load balancing selects an instance and executes the request
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
    
    // Reconstruct url: Replace the original service name (serverName) in the URL to Ip:port
    URI reconstructURI(ServiceInstance instance, URI original);
}

2. RibbonLoadBalancerClient Specifics

Next, let's look at the logic within the specific implementation class, which is coded as follows:

Here we can also see that there are the following steps:

  1. Select Load Balancer ILoadBalancer
  2. Selecting an application instance server through the load balancer involves the load balancer ILoadBalancer. This knowledge point is detailed in the third point below. Let's walk through this process first </>
  3. Encapsulate application instance server into corresponding ribbonServer
  4. Call execute further, which calls back the interface function request.apply(serviceInstance);
  5. Here request.apply(serviceInstance); that is, the Load BalancerRequest interface passed in above, will be executed in the following specific ways, as shown below


    *** 5.1 Here's building a request (via ServiceRequestWrapper). Make sure you go into ServiceRequestWrapper to see that this class overrides the getURI() method by putting the original http://service name===> http://ip:port, which will be called later
    **** 5.2 and then run execution.execute(serviceRequest, body); the iteration call itself (which feels like another kind of recursion), which explains that if (this.iterator.hasNext()) in the previous chapter is a cycle here, and for this writing I can only say'I wipe, I abuse'
  6. The main process of such a load-balanced interceptor is over, so let's continue with the details of each step.

3. ILoadBalancer analysis

The first step mentioned above is to select the load balancer ILoadBalancer

ILoadBalancer loadBalancer = getLoadBalancer(serviceId); 

Let's first look at the methods provided by this interface, as follows:

public interface ILoadBalancer {
	 // Add Server
	public void addServers(List<Server> newServers);

	 // Select a server from the load balancer by key
	public Server chooseServer(Object key);

	 //Identity notifies this server that it is offline or the load balancer will think it is still active until PING again
	public void markServerDown(Server server);

	 // This method is obsolete
	@Deprecated
	public List<Server> getServerList(boolean availableOnly);

    //Get all active servers
    public List<Server> getReachableServers();

     //Get all servers, active and inactive
	public List<Server> getAllServers();
}

Take another look at which classes implement this interface, as follows:

You can see that the base implementation class is the BaseLoadBalancer class, on which DynamicServerListLoadBalancer extends, and ZoneAwareLoadBalancer inherits the DynamicServerListLoadBalancer class and makes some improvements.

3.1 Default injection and dependency properties for ILoadBalancer

Before analyzing the specific properties and methods of ILoadBalancer, let's confirm which implementation class is injected by default
In advance, the Bean configuration was done within the RibbonClientConfiguration.class class class, as follows:

This Bean relies on a lot of attributes. Let's list and explain that these attributes are all configured in RibbonClientConfiguration. This is not used in conjunction with Eureka. If Euraka is used, there may be some differences. Configuration is as follows:

Name Default implementation Configuration when combining Eureka explain
IClientConfig DefaultClientConfigImpl DefaultClientConfigImpl Define client configurations for various API s used to initialize clients or load balancers and to execute methods.
ServerList ConfigurationBasedServerList (Configuration-based) DomainExtractingServerList, which is DiscoveryEnabledNIWSServerList Define the interface for getting a list of servers
ServerListFilter ZonePreferenceServerListFilter ZonePreferenceServerListFilter Logical filtering of ServerList instance lists
IRule ZoneAvoidanceRule ZoneAvoidanceRule Load Balancing Selection Server Rules
IPing DummyPing NIWSDiscoveryPing Method implementation to verify whether a service is available
ServerListUpdater PollingServerListUpdater PollingServerListUpdater Operation on ServerList Update

3.1.1 Locate RibbonClientConfiguration

How do we navigate to RibbonClientConfiguration.class and know that these are implemented within this Bean configuration class as follows:
When injecting SpringClientFactory, defaultConfigType = RibbonClientConfiguration.class is specified

So when you get ILoadBanacer, the following are true:

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

public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
	}

public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}

Step by step, click on the code to see the following at the end:

Here is the configuration class injected into the RibbonClientConfiguration Bean

3.2 AbstractLoadBalancer class

Continue below to implement methods within classes:
**AbstractLoadBalancer class: This is an abstract class of ILoadBalancer, inside

public abstract class AbstractLoadBalancer implements ILoadBalancer {
    //Added a service instance grouping enumeration
    public enum ServerGroup{
        ALL,                //All Services
        STATUS_UP,                     //Normal Service
        STATUS_NOT_UP           // Abnormal Service
    }
    // Call the chooseServer method and enter null
    public Server chooseServer() {
    	return chooseServer(null);
    }
    
    //Get different server collections based on server grouping type
    public abstract List<Server> getServerList(ServerGroup serverGroup);
    
    //Get statistics related to LoadBalancer 
    //LoadBalancerStats is an important class, which is covered in the following server selection strategies, which are detailed later
    public abstract LoadBalancerStats getLoadBalancerStats();    
}

3.3 BaseLoadBalancer class

** BaseLoadBalancer class: This class is the default implementation. First let's look at the properties and methods:

3.3.1 Properties of the BaseLoadBalancer class

public class BaseLoadBalancer extends AbstractLoadBalancer implements
        PrimeConnections.PrimeConnectionListener, IClientConfigAware {

  // The default load balancing strategy is round robin
    private final static IRule DEFAULT_RULE = new RoundRobinRule();
    
    // Specific Ping execution policy object, default is to take turns, large amount of inefficient
    private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
    private static final String DEFAULT_NAME = "default";
    private static final String PREFIX = "LoadBalancer_";
    protected IRule rule = DEFAULT_RULE;
    protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
    protected IPing ping = null;
    // Defines a collection of all services
    @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> allServerList = Collections
            .synchronizedList(new ArrayList<Server>());
            
    // Defines a collection of update services
    @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List<Server> upServerList = Collections
            .synchronizedList(new ArrayList<Server>());

    // A read-write lock is defined
    protected ReadWriteLock allServerLock = new ReentrantReadWriteLock();
    protected ReadWriteLock upServerLock = new ReentrantReadWriteLock();

    protected String name = DEFAULT_NAME;

    // Defines a Timer that runs every 10S timer
    protected Timer lbTimer = null;
    protected int pingIntervalSeconds = 10;
    protected int maxTotalPingTimeSeconds = 5;
    protected Comparator<Server> serverComparator = new ServerComparator();
    protected AtomicBoolean pingInProgress = new AtomicBoolean(false);

    // Defines a LoadBalancerStats statistic
    protected LoadBalancerStats lbStats;

    private volatile Counter counter = Monitors.newCounter("LoadBalancer_ChooseServer");
    private PrimeConnections primeConnections;
    private volatile boolean enablePrimingConnections = false;
    private IClientConfig config;
    // Server List Change Listening 
    private List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<ServerListChangeListener>();
    //Server status change notification (specific implementation class not found)
    private List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<ServerStatusChangeListener>();
}

1 Load balancing policy IRule initializes RoundRobinRule() by default;
2. The specific implementation strategy SerialPingStrategy is a static class, the note clearly indicates that the effect is not ideal when the equivalent is large, and the logic inside is also a for loop
3. Defines a collection allServerList that stores all server instances
4. Defines a collection of upServerList s to store normal server instances
5. Define a Load BalancerStats to collect statistics
6. Defines a timer task to be used by Timer to run every 10S
7. There are other ancillary properties defined, such as read-write locks, server list listening, server state listening (serverStatusListeners), which are not found for the specific implementation class but do not affect the entire process.
BaseLoadBalancer also involves PrimeConnections, which are mainly controlled by enablePrimingConnections.
PrimeConnections is to test the list of available servers for asynchronous connections, do some statistics, feel useless, specific logic
primeConnectionsAsync method in PrimeConnections.class:

The BaseLoadBalancer constructor defines a timed task that periodically detects the state of some servers every 10S. When the rule is DummyPing, it does not run. This is described in the next subsection.

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

3.3.2 Introduction to PingTask ()

This is where upServerList's normally active collection of service classes is updated, coded as follows:

void setupPingTask() {
      // Determine if skipping is possible
        if (canSkipPing()) {
            return;
        }
        // Cancel last time first
        if (lbTimer != null) {
            lbTimer.cancel();
        }
         // Re-ShutdownEnabledTimer
        lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
                true);
          // Run, every 10 seconds      
        lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
        
        // Force run again, maybe the author thought it was asynchronous, not timely, don't know, anyway feel a little redundant here
        forceQuickPing();
    }

  class PingTask extends TimerTask {
        public void run() {
            try {
              // This will continue to validate against IPing logic
            	new Pinger(pingStrategy).runPinger();
            } catch (Exception e) {
                logger.error("LoadBalancer [{}]: Error pinging", name, e);
            }
        }
    }

The general process is as follows:
1. Can Skipping() is used to determine whether skipping is possible. ping == null || ping is of type DummyPing and returns directly
2. It's better to cancel the last time and then try new again
3. Then start running, every 10 S to detect the running status of some servers, whether UP
4. pingerStrategy is called here, where the server went to Check yesterday
5. Check calls the specific implementation of Ping isAlive(), which uses Eurka, the method inside the walking NIWSDiscoveryPing
By the way, here is the code structure of IPING. There is only one method, isAlive(), so the logic is simple, so don't mention much.

3.3.3 Introduction to BaseLoadBalancer method

  1. addServers(List newServers) - This is to add both the original allServerList and the new newServers to a collection and point the old allServerList to the new list.
  2. getServerList(boolean availableOnly) --- Based on whether availableOnly returns all serverList s or available, all are allServerList s, and upServerList s are available
  3. List getServerList(ServerGroup serverGroup) - Returns the corresponding data based on the serverGroup
  4. markServerDown(Server server) - Change server's active Flag(isAliveFlag) to false

3.4 Introduction to DynamicServerListLoadBalancer

3.4.1 DynamicServerListLoadBalancer property

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {

    // Server Information Collection
    volatile ServerList<T> serverListImpl;

    // Filter collections on servers
    volatile ServerListFilter<T> filter;

    // Server Update Execution Specific Implementation
    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };
    //Update the server
    protected volatile ServerListUpdater serverListUpdater;
}

Here are some key attributes, so let's look at them one by one

Introduction to 3.4.1.1 ServerList
public interface ServerList<T extends Server> {
    // Get the initial server list
    public List<T> getInitialListOfServers();
    // Get an updated list of servers
    public List<T> getUpdatedListOfServers();
}

There are two methods, so let's look at the implementation classes

AbstractServerList is its abstract class with an additional method
getFilterImpl(IClientConfig niwsClientConfig) - This is to get an instance of ServerListFilter, which defaults to ZoneAffinityServerListFilter.class

Properties inside DomainExtractingServerList

public class DomainExtractingServerList implements ServerList<DiscoveryEnabledServer> {

     // Here another ServerList type is defined 
     // If there is no Eureka, the type here is: ConfigurationBasedServerList
     // If combined with Eureka, here is: DiscoveryEnabledNIWSServerList
	private ServerList<DiscoveryEnabledServer> list;

    // An IClientConfig is defined 
	private IClientConfig clientConfig;

	private boolean approximateZoneFromHostname;

	public DomainExtractingServerList(ServerList<DiscoveryEnabledServer> list,
			IClientConfig clientConfig, boolean approximateZoneFromHostname) {
		this.list = list;
		this.clientConfig = clientConfig;
		this.approximateZoneFromHostname = approximateZoneFromHostname;
	}
}

Both getInitialListOfServers() and getUpdatedListOfServers() are methods that return lists corresponding to lists inside, where lists are of type ConfigurationBasedServerList if they do not have Eureka, and if they work with Eureka, here is DiscoveryEnabledNIWSServerList,ConfigurationBasedServerList, the results returned are detailed in the analysis of the corresponding implementation class

	private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) {
		List<DiscoveryEnabledServer> result = new ArrayList<>();
		boolean isSecure = this.clientConfig.getPropertyAsBoolean(
				CommonClientConfigKey.IsSecure, Boolean.TRUE);
		boolean shouldUseIpAddr = this.clientConfig.getPropertyAsBoolean(
				CommonClientConfigKey.UseIPAddrForServer, Boolean.FALSE);
		for (DiscoveryEnabledServer server : servers) {
		 // Encapsulate the obtained servers further extension into the DomainExtractingServer type
			result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr,
					this.approximateZoneFromHostname));
		}
		return result;
	}

This approach extends servers to DomainExtracting Server with some additional properties

Both getInitialListOfServers() and getUpdatedListOfServers() methods of Configuration BasedServerList return the list of servers configured within listOfServers

Both getInitialListOfServers() of DiscoveryEnabledNIWSServerList and getUpdatedListOfServers() call obtainServersViaDiscovery(). For our analysis, the main points are:

  1. Get the Eureka client, if not, error
  2. Get listOfInstanceInfo server instance from Eureka client
  3. Encapsulate the server instance as a DiscoveryEnabledServer type
  4. Set up a zone so that you can choose the same Zone to call later (this involves concepts related to Region and Zone)
  5. Return to server list
Introduction to 3.4.1.2 ServerListFilter

The interface ServerListFilter has only one method, that is

    public List<T> getFilteredListOfServers(List<T> servers);

Take a look at the related implementation structure:

AbstractServerListFilter is its abstract class with an additional load balancing statistic, LoadBalancerStats

(2) The ZoneAffinityServerListFilter inherits the AbstractServerListFilter, which filters out instances that are not in the same zone by comparing the Zone of the server instance to the Zone of the caller.

    @Override
    public List<T> getFilteredListOfServers(List<T> servers) {
       // Need to satisfy servers list number > 1 and zoneAffinity or zoneExclusive turned on
        if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){
            // Here's the first step of filtering
            List<T> filteredServers = Lists.newArrayList(Iterables.filter(
                    servers, this.zoneAffinityPredicate.getServerOnlyPredicate()));
             // Continue here to determine if Zone filtering needs to be turned on
            if (shouldEnableZoneAffinity(filteredServers)) {
                return filteredServers;
            } else if (zoneAffinity) {
                overrideCounter.increment();
            }
        }
        return servers;
    }

The above method is mainly filtered by Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate()), which filters out inconsistencies by comparing Zone s of all servers with callers.

After filtering, you still need to decide if you need to enable Zone Affinity. The logic is shouldEnableZone Affinity (filteredServers), which has a lot of computational indicators. Don't worry, just slowly infiltrate, as follows:
I Create a snapshot with detailed logic explained in the next method
II Get the corresponding server load and total number of servers, number of servers that are not working properly
III There are three indicators to judge whether the conditions are met:
1. Number of servers not available/total >0.8
2. Average load on available servers >= 0.6
3. Available Servers (Total Server-Paused Servers)<2
Satisfy any of the above without turning on Zone Affinity

    private boolean shouldEnableZoneAffinity(List<T> filtered) {    
        if (!zoneAffinity && !zoneExclusive) {
            return false;
        }
        if (zoneExclusive) {
            return true;
        }
        LoadBalancerStats stats = getLoadBalancerStats();
        if (stats == null) {
            return zoneAffinity;
        } else {
            logger.debug("Determining if zone affinity should be enabled with given server list: {}", filtered);
            // Create a snapshot with detailed logic in the next method
            ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered);
            // Get the corresponding server load
            double loadPerServer = snapshot.getLoadPerServer();
            // Get the number of servers
            int instanceCount = snapshot.getInstanceCount();      
            // Number of servers not available
            int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount();
            /**
              There are three indicators:
              1. Number of servers not available/total >0.8
              2. Average load on available servers >= 0.6 
              3. Number of Servers Available (Total Server-Paused Servers)<2
              Satisfy any of the above without turning on Zone Affinity
            **/
            if (((double) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold.get() 
                    || loadPerServer >= activeReqeustsPerServerThreshold.get()
                    || (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold.get()) {
                logger.debug("zoneAffinity is overriden. blackOutServerPercentage: {}, activeReqeustsPerServer: {}, availableServers: {}", 
                        new Object[] {(double) circuitBreakerTrippedCount / instanceCount,  loadPerServer, instanceCount - circuitBreakerTrippedCount});
                return false;
            } else {
                return true;
            }
        }
    }

The getZoneSnapshot code is as follows:

 public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
        if (servers == null || servers.size() == 0) {
            return new ZoneSnapshot();
        }
        int instanceCount = servers.size();
        int activeConnectionsCount = 0;
        int activeConnectionsCountOnAvailableServer = 0;
        int circuitBreakerTrippedCount = 0;
        double loadPerServer = 0;
        long currentTime = System.currentTimeMillis();
        for (Server server: servers) {
            ServerStats stat = getSingleServerStat(server);   
            // Determine if the current time can access the current server, if it can, then the number of times visited in the same window time + 1, not Trippedcount ++
            if (stat.isCircuitBreakerTripped(currentTime)) {
                circuitBreakerTrippedCount++;
            } else {
                activeConnectionsCountOnAvailableServer +=    stat.getActiveRequestsCount(currentTime);
            }
            activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
        }
        // circuitBreakerTrippedCount == instanceCount, indicating that no server is currently available
        if (circuitBreakerTrippedCount == instanceCount) {
            if (instanceCount > 0) {
                // should be NaN, but may not be displayable on Epic
                loadPerServer = -1;
            }
        } else {
        // Describes the available servers and calculates the corresponding load
        //  Total number of accesses on available servers/ (Total number of servers - Number of unavailable servers)
            loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
        }
        // Construct a snapshot of the total number of servers, the number of servers not available, the number of times all servers have been accessed in the last window, and the load PerServer is passed in
        return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
    }

(3) DefaultNIWSServerListFilter inherits ZoneAffinityServerListFilter completely and has not been extended

(4) ServerListSubsetFilter limits the number of servers used by the load balancer to a subset of all servers.Reclaim the ability of relatively abnormal servers by comparing network failures with concurrent connections.The specific logic is as follows:

  1. Get a filtered list of servers
  2. Get a list of currentSubset servers you maintain
  3. Traverse through the currentSubset to remove offline ones and get ones that are not in the filter server list
  4. At the same time, the remaining filters are determined as follows:
    4.1. Accesses to the last window on the server > Settings (default is 0)
    4.2. Number of server access failures > Number of settings (default is 0)
  5. Gets a subset of the settings that need to be preserved (default 20), and the rejection percentage (default 0.1f)
  6. Judge that if the number remaining > the percentage set to be retained or excluded has not been reached, then continue to exclude
  7. This is to sort the remaining servers in a healthy order and then exclude the corresponding
  8. If the number of reservations is less than the default number (default 20), then candidates.removeAll(newSubSet) is added; if the number is not enough, roll back to zoneAffinityFiltered and retrieve the corresponding amount of data, the code will not be pasted.

The logic of getFilteredListOfServers() here in ZonePreferenceServerListFilter is also simple:

  1. Get the list of servers after filtering
    2. Traverse one by one to determine if the server's Zone is the same as the caller's Zone, and if so, collect
  2. Returns, if empty, parent filtered with the following code:
	@Override
	public List<Server> getFilteredListOfServers(List<Server> servers) {
		List<Server> output = super.getFilteredListOfServers(servers);
		if (this.zone != null && output.size() == servers.size()) {
			List<Server> local = new ArrayList<Server>();
			for (Server server : output) {
				if (this.zone.equalsIgnoreCase(server.getZone())) {
					local.add(server);
				}
			}
			if (!local.isEmpty()) {
				return local;
			}
		}
		return output;
	}
Introduction to 3.4.1.3 ServerStats

The properties and methods of ServerStats are described below, with a selection:

public class ServerStats {
    //Connection failure threshold, default 3
    private final DynamicIntProperty connectionFailureThreshold;    
    //Trigger Loop Break Timeout Factor, Default 10
    private final DynamicIntProperty circuitTrippedTimeoutFactor; 
    // Maximum loopback break time, default 30S
    private final DynamicIntProperty maxCircuitTrippedTimeout;
    //Active Request Count Time Window
    private static final DynamicIntProperty activeRequestsCountTimeout = DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);
   //Last Connection Failure Time
    private volatile long lastConnectionFailedTimestamp;
     //First Connection Time
    private volatile long firstConnectionTimestamp = 0;
      // Constructor gives default
     public ServerStats() {
	        connectionFailureThreshold = DynamicPropertyFactory.getInstance().getIntProperty(
	                "niws.loadbalancer.default.connectionFailureCountThreshold", 3);        
	        circuitTrippedTimeoutFactor = DynamicPropertyFactory.getInstance().getIntProperty(
	                "niws.loadbalancer.default.circuitTripTimeoutFactorSeconds", 10);
	
	        maxCircuitTrippedTimeout = DynamicPropertyFactory.getInstance().getIntProperty(
	                "niws.loadbalancer.default.circuitTripMaxTimeoutSeconds", 30);
    }
   
   // Get the next accessible time and compare it with the current time to determine if the current time can be continued
    public boolean isCircuitBreakerTripped(long currentTime) {
        long circuitBreakerTimeout = getCircuitBreakerTimeout();
        if (circuitBreakerTimeout <= 0) {
            return false;
        }
        return circuitBreakerTimeout > currentTime;
    }

    
    /** 
    Get the time you need to pause, plus the time when the last connection failed to start, and get the time when you can access it again
    */
    private long getCircuitBreakerTimeout() {
        long blackOutPeriod = getCircuitBreakerBlackoutPeriod();
        if (blackOutPeriod <= 0) {
            return 0;
        }
        return lastConnectionFailedTimestamp + blackOutPeriod;
    }
    
    // Get the time to pause
    private long getCircuitBreakerBlackoutPeriod() {
        // Number of persistent connection failures
        int failureCount = successiveConnectionFailureCount.get();
        // Default Continuous Failure Threshold 3
        int threshold = connectionFailureThreshold.get();
        // If the threshold is not reached, return directly to 0
        if (failureCount < threshold) {
            return 0;
        }
        //The difference between the number of consecutive failures and the threshold, compared with 16, up to 16
        int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
        // Gets the diff power with a stop time of 2, multiplied by factor 10S
        int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
        //Pause time and maximum loop break time(30S)Compare, Maximum30S
        if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
            blackOutSeconds = maxCircuitTrippedTimeout.get();
        }
        return blackOutSeconds * 1000L;
    }
    
    // This is where you set the current time to the last time a connection failed
    // Number of Failures+1
    //Total pause due to failure
    public void incrementSuccessiveConnectionFailureCount() {
        lastConnectionFailedTimestamp = System.currentTimeMillis();
        successiveConnectionFailureCount.incrementAndGet();
        totalCircuitBreakerBlackOutPeriod.addAndGet(getCircuitBreakerBlackoutPeriod());
    } 
    
    // Here is the number of successful visits within the Get Active Window
    // If the last access time is longer than one window, return 0 
    // The default time for windows is 60*10 seconds
    public int getActiveRequestsCount(long currentTime) {
        int count = activeRequestsCount.get();
        if (count == 0) {
            return 0;
        } else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
            activeRequestsCount.set(0);
            return 0;            
        } else {
            return count;
        }
    }
}
Introduction to 3.4.1.4 ServerListUpdater

ServerListUpdater is a strategy for dynamic server list updates in different ways. First, look at the interfaces:

public interface ServerListUpdater {

    // An interface is built into the interface to execute the dopudate inside to update the server list
    public interface UpdateAction {
        void doUpdate();
    }

    // Start updating the server list, implemented in the UpdateAction interface
    void start(UpdateAction updateAction);

     //Stop updating the server list
    void stop();

     // Return to the time of the last update, as a String
    String getLastUpdate();

     //Returns the time elapsed since the last update (milliseconds)
    long getDurationSinceLastUpdateMs();

     //Return number of missed update cycles
    int getNumberMissedCycles();

     // Return Number of Core Threads
    int getCoreThreads();
}

Looking at the implementation structure of ServerListUpdater, there are two implementation classes, PollingServerListUpdater and EurekaNotificationServerListUpdater

(1) EurekaNotificationServerListUpdater class

EurekaNotificationServerListUpdater is an implementation of a server list update for DynamicServerListLoadBalancer that uses eureka's event listener to trigger LB cache updates.In addition, the actual updates on the serverList are updated in a separate thread pool when a cache refresh notification is received.

The main logic of the start(final UpdateAction updateAction) method is:

  1. Create a new EurekaEventListener(),
  2. Rewrite the onEvent(EurekaEvent event) event inside, which starts a thread to update the specific content
  3. updateAction.doUpdate(); the real content is the caller, the updateAction method inside the DynamicServerListLoadBalancer class
  4. Set the last update time

stop() is to log off event monitoring

@Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            this.updateListener = new EurekaEventListener() {
                @Override
                public void onEvent(EurekaEvent event) {
                    if (event instanceof CacheRefreshedEvent) {
                        if (!updateQueued.compareAndSet(false, true)) {  // if an update is already queued
                            logger.info("an update action is already queued, returning as no-op");
                            return;
                        }

                        try {
                            refreshExecutor.submit(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                    // Implement the updateAction method in the DynamicServerListLoadBalancer class
                                        updateAction.doUpdate();
                                        // Record time
                                        lastUpdated.set(System.currentTimeMillis());
                                    } catch (Exception e) {
                                        logger.warn("Failed to update serverList", e);
                                    } finally {
                                        updateQueued.set(false);
                                    }
                                }
                            });  // fire and forget
                        } catch (Exception e) {
                            logger.warn("Error submitting update task to executor, skipping one round of updates", e);
                            updateQueued.set(false);  // if submit fails, need to reset updateQueued to false
                        }
                    }
                }
            };
            if (eurekaClient == null) {
                eurekaClient = eurekaClientProvider.get();
            }
            if (eurekaClient != null) {
                eurekaClient.registerEventListener(updateListener);
            } else {
                logger.error("Failed to register an updateListener to eureka client, eureka client is null");
                throw new IllegalStateException("Failed to start the updater, unable to register the update listener due to eureka client being null.");
            }
        } else {
            logger.info("Update listener already registered, no-op");
        }
    }

(2) PollingServerListUpdater class
PollingServerListUpdater is the default implementation policy for dynamic updates of server lists. It calls a timed task to execute at a fixed time, and runs one second after initialization, with an interval of 30S. The same implementation is also the updateAction method in the DynamicServerListLoadBalancer class

  private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
  private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;
  
@Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        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");
        }
    }
Introduction to 3.4.1.5 IRule

IRule is the choice strategy for load balancing. First look at the interface of IRule, there are three, mainly for the analysis of select (Object key)

public interface IRule{
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

Take another look at the inheritance structure of IRule, and then we'll analyze the characteristics of each rule:

_AbstractLoadBalancerRule is an abstract class of IRule, adding an ILoadBalancer lb attribute
_RandomRule random strategy is to randomly take an index by Random.nextInt(serverCount) and get the corresponding Server from the available services.
I think there are several problems with this random strategy
1. The number of Size servers available must be less than or equal to the number of all servers, then server = upList.get(index); there is no array crossing here.
2. If you can't get the server all the time, you'll get in the dead loop

 public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            int index = rand.nextInt(serverCount);
            server = upList.get(index);
            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }
            if (server.isAlive()) {
                return (server);
            }
            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }
        return server;
    }

_RoundRobinRule polling policy, the logic is to maintain a counter, nextServerCyclicCounter, each time + 1, to access the next one in turn, if 10 consecutive acquisitions are invalid servers, it will terminate directly, no longer acquiring servers.

_RetryRule retry policy, set a period of time, and a RoundRobinRule() policy, that is, maxRetryMillis = 500 by default; that is, within 500 ms, if not acquired, the server is continuously acquired in turn, and when the time is up, the current thread interrupt() is triggered by Timer; then null is returned;

_WeightedResponseTimeRule takes response time as weight, and the shorter the response time, the more likely the server will be selected.

_ClientConfigEnabled RoundRobinRule is nothing special. It has a built-in RoundRobinRule policy, which calls RoundRobinRule's policy. My question is why any subclass doesn't inherit RoundRobinRule, inherit it, or take another detour.

_BestAvailableRule Policy, which is to filter all available servers. The methods involved are described in serverStats, that is, to first determine if each server is still in a paused period of access and if it is abandoned directly,
Next, determine the total number of valid requests within a sliding window, taking the smallest of all servers, indicating the minimum load

_PredicateBasedRule Policy, Abstract class, call chooseRoundRobinAfterFiltering, guess from name is to filter through the server, then get the server through RoundRobin polling policy, most important of all is this.apply (new PredicateKey, loadBalancerKey, in getEligibleServers method.Server), here you need to re-implement the apply method inside Predicate, that is, to re-implement the public abstract AbstractServerPredicate getPredicate() inside the PredicateBasedRule class and return an implementation class corresponding to a specific Predicate interface.

    @Override
    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;
        }       
    }

    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        return Optional.of(eligible.get(nextIndex.getAndIncrement() % 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();
            for (Server server: servers) {
                if (this.apply(new PredicateKey(loadBalancerKey, server))) {
                    results.add(server);
                }
            }
            return results;            
        }
    }

_ZoneAvoidanceRule inherits the PredicateBasedRule class, where Predicate is a combined filter, a combination of ZoneAvoidancePredicate and AvailabilityPredicate, is an and logic, and then, if not met, from filtering logic until the condition is met.

    public ZoneAvoidanceRule() {
        super();
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }


Take a look at the apply implementation of the AndPredicate class. AndPredicate.class also implements Predicate, which overrides the apply method and does not pass if only one fails

The Loop analyzes the logic inside the method getEligibleServers of CompositePredicate, coded as follows:

    public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
        List<Server> result = super.getEligibleServers(servers, loadBalancerKey);
        Iterator<AbstractServerPredicate> i = fallbacks.iterator();
        while (!(result.size() >= minimalFilteredServers && result.size() > (int) (servers.size() * minimalFilteredPercentage))
                && i.hasNext()) {
            AbstractServerPredicate predicate = i.next();
            result = predicate.getEligibleServers(servers, loadBalancerKey);
        }
        return result;
    }

The logic is:
1. Use master filter to filter all instances and return the filtered list of server instances
2. The following two criteria need to be determined: as long as one of them meets, no filtering is performed and the current result is returned for linear polling algorithm selection:

	1. Total filtered server instances >=Minimal filtered server instances (default 1)
	2. Percentage of filtered server instances > Minimal Filtered Percentage (default 0)
Introduction to 3.4.1.5.1 ZoneAvoidanceRule related methods

One way: Map < String, ZoneSnapshot > createSnapshot (LoadBalancerStats lbStats)
Get all the available areas first, then create a snapshot of each area and save it in Map<String, ZoneSnapshot>

Two methods: String random ChooseZone (Map < String, ZoneSnapshot > snapshot,
Set chooseFrom)
Just pick one randomly from chooseFrom, but the logical individual feels complicated, add a judgment of the number of servers, why not set it directly to list, then random.nextInt won't do.

Three methods: Set getAvailableZones
Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
double triggeringBlackoutPercentage)
The main logical flow of this code is as follows:
I Get all the region sets, and if Size =1, return directly
II Begin calculating for each region, exclude if the following conditions are met:

    The number of server instances below this zone is 0 
    Number of unavailable server instances/total > percentage triggered (default 0.99999d)
    Load less than 0 means the server list is totally bad

III also collects the higher load Set worstZones, the higher the load the worse, and gets the highest load area
IV If the maximum load in all zones does not exceed the trigger point (default is 0.2d) and all zones are normal (not excluded), return directly to all zones
V Otherwise, randomly remove one from the heavily loaded worstZones and return

Four methods: getAvailableZones(LoadBalancerStats lbStats,
Double triggering Load, double triggering Blackout Percentage) This method is a combination of the above two methods, creating a snapshot of the area and then selecting the available area

3.4.1.5.2 AvailabilityFilteringRule

_AvailabilityFilteringRule inherits the PredicateBasedRule class and writes the choose method.
First a server is polled by roundRobinRule, then a predicate (where the specific implementation class is AvailabilityPredicate) is used to determine if it meets the criteria, then the server is returned if it meets the criteria, the server is obtained by continuing polling and then judged. If it fails 10 consecutive times, the parent class's choose method is called.

AvailabilityPredicate's apply determines whether it conforms to the logic or the piece in the stat.

  1. Is this server during access prohibition
  2. Whether the number of instance requests is greater than the threshold (default Integer.MAX_VALUE)
    public Server choose(Object key) {
        int count = 0;
        Server server = roundRobinRule.choose(key);
        while (count++ <= 10) {
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        return super.choose(key);
    }

   //apply method of AvailabilityPredicate class
       @Override
    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) {
            return true;
        }
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }
    
    private boolean shouldSkipServer(ServerStats stats) {        
        if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped()) 
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }

Introduction to the 3.4.2 DynamicServerListLoadBalancer method

DynamicServerListLoadBalancer inherits the ability of BaseLoadBalancer to dynamically obtain a list of candidate servers.
Here are three ways to do this, and analyze what you're doing without putting code and notes on it

The method restOfInit(IClientConfig clientConfig) mainly does the following:

  1. Save the original enablePrimingConnections value first, then temporarily set enablePrimingConnections to false to prevent starting two asynchronous threads to run the related earlier httpClient requests, as there will also be enablePrimingConnections related judgments during the initialization of BaseLoadBalancer
  2. By enableAndInitLearnNewServers Feature (); Open a timed task to update serverList information regularly (analyzed above, after initialization of 1S), where the service list information update instance is the default SollingServerListUpdater instance, the updateAction method is updateListOfServers() in the DynamicServerListLoadBalancer class;
  3. Run updateListOfServers() immediately; //This should take into account the one-second delay above
  4. Change enablePrimingConnections to the original value here

The main logic of the method ** setServersList(List lsrv) is as follows:

  1. Call super.setServersList(lsrv) of the parent class; process the related logic
  2. Create it in the cache serverStatsCache (if not)
  3. Create a Map < String, List > mapping relationship key (Zone) --> Value (corresponding server list)
  4. Call the setServerListForZones method to update the upServerListZoneMap and make sure that each Zone information is in the zoneStatsMap

Method ** updateListOfServers() This is how UpdateAction updates serverList. It has been mentioned several times above. Here's some logic:

    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };
@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            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);
            }
        }
        updateAllServerList(servers);
    }
  1. Get available servers, here the serverListImpl injection score depends, if it's pure Ribbon, ConfigurationBasedServerList, if it's combined with Eureka, then DomainExtractingServerList, which is DiscoveryEnabledNIWSServerList, which defines a list. The specific acquisition logic is described above

  2. In filtering the server list through the ServerListFilter, the default implementation of the ServerListFilter here is ZonePreferenceServerListFilter, which is also described above in the implementation of getFilteredListOfServers, which is omitted here

  3. Update all the server information, Ping it once (if the Ping condition is met), and call super.forceQuickPing();

3.4.3 DynamicServerListLoadBalancer Summary

  1. The DynamicServerListLoadBalancer class does not actually handle Zone-related issues. setServerListForZones in the DynamicServerListLoadBalancer class mainly updates the Map of key (zone) ->value (serverlist) and stores Zone-related information.
  2. The DynamicServerListLoadBalancer class does not override the chooseServer method, but instead calls the BaseLoadBalancer's chooseServer method, which is the policy of the new RoundRobinRule() polling.

3.5 Introduction to ZoneAwareLoadBalancer

The ZoneAwareLoadBalancer load balancer also inherits the DynamicServerListLoadBalancer, extends the DynamicServerListLoadBalancer, and mainly overrides the two methods of setServerListForZones (Map < String, List > ZoeServersMap), chooseServer(Object key).

ZoneAwareLoadBalancer can be seen by its name that the ZoneAwareLoadBalancer load balancer takes into account the Zone factor of the zone. The DynamicServerListLoadBalancer class does not handle zones, it is accessed through RoundRobinRule() round robin () policy, and for cross-zone scenarios, delays in accessing different zonesWhen you're older, you can choose the same zone access, which improves performance. Let's see what improvements ZoneAwareLoadBalancer has made, starting with the setServerListForZones method, which has the following general logic:

    @Override
    protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {
        super.setServerListForZones(zoneServersMap);
        if (balancers == null) {
            balancers = new ConcurrentHashMap<String, BaseLoadBalancer>();
        }
        for (Map.Entry<String, List<Server>> entry: zoneServersMap.entrySet()) {
        	String zone = entry.getKey().toLowerCase();
            getLoadBalancer(zone).setServersList(entry.getValue());
        }
        // check if there is any zone that no longer has a server
        // and set the list to empty so that the zone related metrics does not
        // contain stale data
        for (Map.Entry<String, BaseLoadBalancer> existingLBEntry: balancers.entrySet()) {
            if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {
                existingLBEntry.getValue().setServersList(Collections.emptyList());
            }
        }
    }   
  1. Or set ServerListForZones (zoneServersMap) to the parent class; update key(zone) - > value (serverlist) and zone information
  2. Update all zone-related information into the cache balancers (key is zone Zone, value is BaseLoadBalancer), divide by zone, one zone corresponds to one BaseLoadBalancer, and fill in the ServerList below the zone.
  3. Check again to leave those filtered Zone s empty for the serverList below.
    //Analytically, DynamicServerListLoadBalancer has actually divided the zone->serverList, but there is no subsequent action, which is ZoneAwareLoadBalancer calls super.setServerListForZones(zoneServersMap) first; on this basis, it further extends

Let's take another look at ZoneAwareLoadBalancer's chooseServer method:

    @Override
    public Server chooseServer(Object key) {
        // Determine if more than one area is open and available
        if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
            logger.debug("Zone aware logic disabled or there is only one zone");
            return super.chooseServer(key);
        }
        Server server = null;
        try {
            LoadBalancerStats lbStats = getLoadBalancerStats();
            // Take a snapshot of the area and save it in Map
            Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
            logger.debug("Zone snapshots: {}", zoneSnapshot);
             // Gets the parameter that triggers the instance load by default, which is 0.2d by default
            if (triggeringLoad == null) {
                triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
            }
           // Gets the percentage of trigger server instance failures, defaulting to 0.99999d
            if (triggeringBlackoutPercentage == null) {
                triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
                        "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
            }
            // Get Available Zones
            Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
            logger.debug("Available zones: {}", availableZones);
            if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                // Randomly select an area
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    // Get BaseLoadBalancer 
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    // Get a list of servers
                    server = zoneLoadBalancer.chooseServer(key);
                }
            }
        } catch (Exception e) {
            logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
        }
        if (server != null) {
            return server;
        } else {
            logger.debug("Zone avoidance logic is not invoked.");
            return super.chooseServer(key);
        }
    }

The entire logic of the code above is as follows:

  1. Determines whether zone filtering is turned on and the number of available zones is > 1, otherwise the parent class's chooseServer is called
  2. Call ZoneAvoidanceRule.createSnapshot, create a snapshot of the area, and save it in Map < String, ZoneSnapshot > zoneSnapshot
  3. ZoneAvoidanceRule.getAvailableZones is called again for zone filtering, and the logic above is explained in ZoneAvoidanceRule.getAvailableZones above
  4. If the available zones availableZones are not empty and are smaller than the size of all snapshot zones, then randomly select an area zone from the available zones
  5. Get the corresponding BaseLoadBalancer from the selected zone, and then get the server server instance from the corresponding IRule
  6. If the server instance is null (no server instance is obtained), call the super.chooseServer(key) of the parent class;

3. Flow charts for operation

4. Summary

Originally introduced the Ribbon related source code and related calling process, which contains more content, if there is something wrong, please point out, thank you.

Twenty-five original articles were published. 8 were praised. 10,000 visits+
Private letter follow

Tags: snapshot less Spring network

Posted on Sun, 15 Mar 2020 21:21:16 -0400 by proxydude