Spring-cloud-gateway integrates load balancing strategies by default, such as polling, randomization, weighting based on response time, and so on. Because of business needs, we need to customize a policy, so we took the time to explore the source code. Conclusion first:
A, the conclusion
-
The ServiceInstance choose(String serviceId) method is defined on the LoadBalancerClient interface to obtain a specific ServiceInstance based on the service name.
-
SpringClientFactory class defines a Map < String, AnnotationConfigApplicationContext > contexts, the key is serviceId, The value for AnnotationConfigApplicationContext object. This means that a different load balancing policy can be set for each service. AnnotationConfigApplicationContext in the main preserved the ILoadBalancer bean, defines the realization of load balancing; The latter relies on:
- IClientConfig: defines the client configuration, which is used to initialize the client and load balancing configuration.
- IRule: load balancing policies, such as polling.
- IPing: defines how to determine whether a service instance is healthy.
- ServerList: defines methods to get a list of servers;
- ServerListFilter: Select a specific server list based on the configuration or filtering rules.
- ServerListUpdater: Defines a policy for dynamically updating the server list.
The diagram is as follows:
- ILoadBalancer AnnotationConfigApplicationContext in order is: first through external configuration import, followed by the configuration class.
Two, source code analysis
1. LoadBalancerClient
LoadBalancerClient is a load balancing client. It defines the ServiceInstance choose(String serviceId) method. That is, it obtains the ServiceInstance based on the service name and the specified policy.
Its interface is defined as follows:
public interface LoadBalancerClient extends ServiceInstanceChooser {... }public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
public interface ServiceInstance {
default String getInstanceId(a) {
return null;
}
String getServiceId(a);
String getHost(a);
int getPort(a);
boolean isSecure(a);
URI getUri(a);
Map<String, String> getMetadata(a);
default String getScheme(a) {
return null; }}Copy the code
The default implementation class is RibbonLoadBalancerClient:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId); }}Copy the code
Line 9: getLoadBalancer(serviceId) obtains the load balancing policy based on the service name; GetServer (getLoadBalancer(serviceId), hint), and then select the specific service instance based on the policy.
Line 18: Load balancing policies based on the service name are implemented by the SpringClientFactory class.
2. SpringClientFactory
A complete implementation of load balancing for each service is stored in SpringClientFactory.
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
@Override
public <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);
if(instance ! =null) {
return instance;
}
IClientConfig config = getInstance(name, IClientConfig.class);
returninstantiateWithConfig(getContext(name), type, config); }}public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean.ApplicationContextAware {
// Contexts stores all configurations for each serviceId
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
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;
}
/ / return corresponding AnnotationConfigApplicationContext serviceId
// If it does not exist, call createContext(String name)
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name)); }}}return this.contexts.get(name);
}
/ / create a service name corresponding AnnotationConfigApplicationContext
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
Check whether the serviceId service is included in the imported configurations, and import it if so
if (this.configurations.containsKey(name)) {
for(Class<? > configuration :this.configurations.get(name) .getConfiguration()) { context.register(configuration); }}// 2. Check whether the imported configuration contains the default configuration. If yes, import it
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for(Class<? > configuration : entry.getValue().getConfiguration()) { context.register(configuration); }}}/ / 3. PropertyPlaceholderAutoConfiguration and defaultConfigType bean registered to the context of registered (if using SpringClientFactory by default Bean, the enclosing here defaultConfigType refers to RibbonClientConfiguration)
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object> singletonMap(this.propertyName, name)));
if (this.parent ! =null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
returncontext; }}Copy the code
3. ILoadBalancer
The getServer method of LoadBalancerClient ends up calling the chooseServer method of the ILoadBalancer interface.
public class RibbonLoadBalancerClient implements LoadBalancerClient {
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"); }... }public interface ILoadBalancer {
/** * Choose a server from load balancer. */
public Server chooseServer(Object key); . }Copy the code
Select the service instance according to ILoadBalancer’s chooseServer(Object Key) method.
The ILoadBalancer interface implementation class is shown in the figure below. The default bean is ZoneAwareLoadBalancer.
Analyze the basic elements of ILoadBase:
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
serverList, ServerListFilter
filter, ServerListUpdater serverListUpdater)
{
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
Copy the code
- ClientConfig: Defines the client configuration for client initialization and load balancing configuration.
- IRule: load balancing rules (policies), such as polling.
- IPing: defines how to determine whether a service instance is healthy.
- ServerList: Defines methods to get a list of servers.
- ServerListUpdater: Defines a policy for dynamically updating the server list.
- ServerListFilter: according to the configuration or filtering rules to select a specific server list, use a consul as the default implementation for HealthServiceServerListFilter registry.
In RibbonClientConfiguration configuration class defines a default implementation:
@Configuration
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.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);
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
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);
}
@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 RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public ServerIntrospector serverIntrospector(a) {
return new DefaultServerIntrospector();
}
@PostConstruct
public void preprocess(a) {
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
}
static class OverrideRestClient extends RestClient {
private IClientConfig config;
private ServerIntrospector serverIntrospector;
protected OverrideRestClient(IClientConfig config, ServerIntrospector serverIntrospector) {
super(a);this.config = config;
this.serverIntrospector = serverIntrospector;
initWithNiwsConfig(this.config);
}
@Override
public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.config,
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri);
}
@Override
protected Client apacheHttpClientSpecificInitialization(a) {
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
apache.getClientHandler().getHttpClient().getParams().setParameter(
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
returnapache; }}}Copy the code
If you use consul registry, the default implementation is as follows:
@Configuration
public class ConsulRibbonClientConfiguration {
@Autowired
private ConsulClient client;
@Value("${ribbon.client.name}")
private String serviceId = "client";
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
public ConsulRibbonClientConfiguration(a) {}public ConsulRibbonClientConfiguration(String serviceId) {
this.serviceId = serviceId;
}
@Bean
@ConditionalOnMissingBean
publicServerList<? > ribbonServerList(IClientConfig config, ConsulDiscoveryProperties properties) { ConsulServerList serverList =new ConsulServerList(client, properties);
serverList.initWithNiwsConfig(config);
return serverList;
}
@Bean
@ConditionalOnMissingBean
public ServerListFilter<Server> ribbonServerListFilter(a) {
return new HealthServiceServerListFilter();
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(a) {
return new ConsulPing();
}
@Bean
@ConditionalOnMissingBean
public ConsulServerIntrospector serverIntrospector(a) {
return new ConsulServerIntrospector();
}
@PostConstruct
public void preprocess(a) {
setProp(this.serviceId, DeploymentContextBasedVipAddresses.key(), this.serviceId);
setProp(this.serviceId, EnableZoneAffinity.key(), "true");
}
protected void setProp(String serviceId, String suffix, String value) {
// how to set the namespace properly?
String key = getKey(serviceId, suffix);
DynamicStringProperty property = getProperty(key);
if(property.get().equals(VALUE_NOT_SET)) { ConfigurationManager.getConfigInstance().setProperty(key, value); }}protected DynamicStringProperty getProperty(String key) {
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
}
protected String getKey(String serviceId, String suffix) {
return serviceId + "." + DEFAULT_NAMESPACE + "."+ suffix; }}Copy the code
As you can see, the default implementation of several beans has been overwritten:
- ServerList: ConsulServerList;
- ServerListFilter: HealthServiceServerListFilter;
- IPing: ConsulPing.
Continue to look at the source code, and finally locate the IRule interface’s Choose (Object Key) method to select the service instance.
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
@Override
public Server chooseServer(Object key) {
if(! ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <=1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key); }... }... }public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener.IClientConfigAware {
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null; }}}}public interface IRule {
public Server choose(Object key);
}
Copy the code
Take a look at the implementation class of the IRule interface,
The default Zone Avoidancerule is selected based on zone area and availability.
ZoneAvoidanceRule extends the PredicateBasedRule class,
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// lb.getallServers (): Get all service instances
// Select the instance based on the serviceId and load balancing policy
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null; }}}// Get all service instances as follows
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener.IClientConfigAware {
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
// Return allServerList to indicate that the BaseLoadBalancer object has been initialized
@Override
public List<Server> getAllServers(a) {
returnCollections.unmodifiableList(allServerList); }}Copy the code
Take a look at how the ZoneAwareLoadBalancer bean is generated:
public class RibbonClientConfiguration {
@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 newZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }}public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
serverList, ServerListFilter
filter, ServerListUpdater serverListUpdater)
{
super(clientConfig, rule, ping, serverList, filter, serverListUpdater); }}public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList
serverList, ServerListFilter
filter, ServerListUpdater serverListUpdater)
{
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
// Initialize the operationrestOfInit(clientConfig); }}Copy the code
Finally, the restOfInit(clientConfig) method is located and the analysis of this method continues:
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
this.setEnablePrimingConnections(false);
/ / enableAndInitLearnNewServersFeature () method defined inside a timing task, will perform regularly updateListOfServers ()
enableAndInitLearnNewServersFeature();
// Execute immediately
updateListOfServers();
if (primeConnection && this.getPrimeConnections() ! =null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
public void enableAndInitLearnNewServersFeature(a) {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
@VisibleForTesting
public void updateListOfServers(a) {
List<T> servers = new ArrayList();
if (this.serverListImpl ! =null) {
// 1. Get a list of all servers and call ConsulServerList
ConsulServerList is implemented by calling the HTTP interface provided by Consul: /health/service/:service returns all instances by setting the passing parameter to false
servers = this.serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}".this.getIdentifier(), servers);
if (this.filter ! =null) {
// 2. Get the filtered server list (return the service instances that pass the health check)
/ / call HealthServiceServerListFilter class
servers = this.filter.getFilteredListOfServers((List)servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}".this.getIdentifier(), servers); }}// 3. Update the available service instances
this.updateAllServerList((List)servers);
}
protected void updateAllServerList(List<T> ls) {
// other threads might be doing this - in which case, we pass
if (serverListUpdateInProgress.compareAndSet(false.true)) {
try {
for (T s : ls) {
s.setAlive(true); // set so that clients can start using these
// servers right away instead
// of having to wait out the ping cycle.
}
setServersList(ls);
super.forceQuickPing();
} finally {
serverListUpdateInProgress.set(false); }}}}public class PollingServerListUpdater implements ServerListUpdater {
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false.true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run(a) {
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); }}};PollingServerListUpdater-%d PollingServerListUpdater-%d
// The initial execution delay is 1s, and the subsequent execution interval is 30s by default
The scheduled task is mainly the updateAction.doupDate () method above
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op"); }}// The interval of scheduled tasks is preferentially obtained from clientConfig. If it is not set, the default interval is 30s
private static long getRefreshIntervalMs(IClientConfig clientConfig) {
returnclientConfig.get(CommonClientConfigKey.ServerListRefreshInterval, LISTOFSERVERS_CACHE_REPEAT_INTERVAL); }}Copy the code
The logic of the above code is to initialize all service instance lists, available service instance lists, and so on by calling the restOfInit method. And by setting a thread group in the PollingServerListUpdater class to perform a scheduled task, with a default interval of 30 seconds, for updating all service instances and the list of instances that pass the health check.
This code uses IRule, ServerList, ServerListFilter, etc. What does IPing do?
Keep looking at the source code,
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener.IClientConfigAware {
// 1. There is a setupPingTask constructor
public BaseLoadBalancer(a) {
this.name = DEFAULT_NAME;
this.ping = null;
setRule(DEFAULT_RULE);
setupPingTask();
lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
// 2. This method sets up a thread for each service and executes PingTask periodically
void setupPingTask(a) {
// You can skip it
if (canSkipPing()) {
return;
}
if(lbTimer ! =null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000); / / the default 10 s
forceQuickPing();
}
class PingTask extends TimerTask {
public void run(a) {
try {
new Pinger(pingStrategy).runPinger();
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error pinging", name, e); }}}class Pinger {
// 3. Specify the PingTask content
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();
// 3.1 In the case of a registry, allServerList is a list of servers retrieved from the registry
allServers = allServerList.toArray(new Server[allServerList.size()]);
allLock.unlock();
int numCandidates = allServers.length;
// 3.2 Check the health status of each service instance. Set health to true
/ / the installed allServers updateListOfServers DynamicServerListLoadBalancer to pass the health inspection service instance
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();
svr.setAlive(isAlive);
// 3.3 Is the state different from the previous state
if(oldIsAlive ! = isAlive) { changedServers.add(svr); logger.debug("LoadBalancer [{}]: Server [{}] status changed to {}",
name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
}
3.4 Updating the local variable newUpList
if (isAlive) {
newUpList.add(svr);
}
}
upLock = upServerLock.writeLock();
upLock.lock();
3.5 Update the list of available service instances
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress.set(false); }}}}Copy the code
After the above code, the flow is roughly as follows:
- PollingServerListUpdater and DynamicServerListLoadBalancer responsible for every 30 s to get all of the service from registry instance, And select the normal instances and add them to allServerList and upServerList.
- The setupPingTask() method in BaseLoadBalancer is responsible for “ping” these allServerList servers every 10 seconds and removing them from the upServerList if they are abnormal.
If a service instance hangs, the perception is that there will be at most 10 seconds or so for some requests to fail, which is unfortunately not true. It can reach about 30 seconds at most.
Further results = 3.2 pingerStrategy. PingServers (ping, allServers) this line source, final positioning (if using ConsulPing) :
public class ConsulPing implements IPing {
@Override
public boolean isAlive(Server server) {
boolean isAlive = true;
if(server ! =null && server instanceof ConsulServer) {
ConsulServer consulServer = (ConsulServer) server;
return consulServer.isPassingChecks();
}
returnisAlive; }}Copy the code
Here into ginseng is DynamicServerListLoadBalancer returned to the state of the normal server list, so its execution ConsulPing isAlive method, the result must be true; The reason is that the isAlive method to call ConsulPing does not perform a true “ping” (such as calling Consul’s API again to verify that the service instance is healthy). Instead, it returns the health check status in the ConsulServer object.
Three, the problem is not a problem
- BaseLoadBalancer class, which has a thread set up for each service to perform PingTask periodically; As a gateway, if hundreds of microservices need to be connected, hundreds of threads will be generated, and each thread will be idle most of the time. Consider setting up a smaller thread pool instead.
- Consul as a registry, the implementation of ConsulPing has not worked as expected. Consider skipping the Ping check (for example, setting the default IPing to DummyPing) and reducing the execution interval of scheduled tasks in PollingServerListUpdater to reduce the impact of individual service instances going offline.