Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,基于Netflix Ribbon实现。java
请在上一篇文章的基础上进行下面的学习,点击这里阅读上一篇算法
下面咱们看一下具体的的负载均衡器,也就是ILoadBalancer接口的实现类。spring
该类是ILoadBalancer接口的抽象实现类。segmentfault
在该抽象实现类中含有一个关于服务实例的分组枚举类,该枚举类主要有如下三种类型:服务器
该抽象类下面的的函数有如下几个:并发
该类是Ribbon负载均衡器的基础实现类,在该类中定义了不少关于负载均衡相关的基础内容。app
该类中定义并维护了两个存储服务实例Server对象的列表。一个用于存储全部服务实例的清单,一个用于存储正常服务的实例清单。代码以下:负载均衡
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>()); @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());
定义了用来存储负载均衡器各服务实例属性和统计信息的LoadBalancerStats对象。dom
定义了检查服务实例是否正常的IPing对象,在BaseLoadBalancer中默认为null,须要在构造时注入它的实现。ide
定义了检查服务实例操做的执行策略对象IPingStrategy,在BaseLoadBalancerz中默认使用了该类中定义的静态内部类SerialPingStrategy。根据源码,能够看到该策略采用线性遍历ping服务实例的方式实现检查。可是该策略在当IPing的实现速度不理想或者Server列表过大时,可能会影响系统性能。这时就须要本身去实现本身的IPing策略。
定义了负载均衡的处理规则IRule对象,从BaseLoadBalancer中chooseServer(Object key)方法源码中也能够看出它是将服务实例选择的任务交给了IRule中的Server choose(Object key)方法。默认的IRule实现是RoundRobinRule。
启动Ping任务,在BaseLoadBalancer的默认构造函数中,会直接启动一个用于定时检查Server是否健康的任务。该任务默认执行的时间间隔为10s。
实现了ILoadBalancer接口定义的负载均衡器应该具有如下操做:
DynamicServerListLoadBalancer该类继承于BaseLoadBalancer类,它是对基础负载均衡器的扩展。
在该负载均衡器,实现了服务实例清单在运行期的动态更新能力;同时,它还具有了对服务实例清单的过滤功能,咱们能够经过过滤器来选择性的获取一批服务实例清单。
下面看一下负载均衡器增长了哪些内容。
经过查看源码,发现增长了一个关于服务列表的操做对象ServerList<T> serverListImpl,其中T是一个Server的子类,即表明了一个具体的服务实例的扩展类。其中ServerList的定义以下:
public interface ServerList<T extends Server> { public List<T> getInitialListOfServers(); public List<T> getUpdatedListOfServers(); }
该抽象接口定义了两个抽象方法,以下:
该抽象接口的实现类有不少,由于该负载均衡器中须要实现服务实例的动态更新,那么就须要Ribbon具有访问Eureka服务注册中心获取服务实例的能力,在DynamicServerListLoadBalancer默认的ServerList是DomainExtractingServerList(默认的实现是在EurekaRibbonClientConfiguration),源码以下:
package org.springframework.cloud.netflix.ribbon.eureka; @Configuration public class EurekaRibbonClientConfiguration { @Bean @ConditionalOnMissingBean public ServerList<?> 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); return serverList; } }
查看DomainExtractingServerList的源码能够看出,该类中有一个ServerList<DiscoveryEnabledServer> list,经过查看DomainExtractingServerList的构造函数,DomainExtractingServerList中的ServerList对象就是从上面的代码中传过来的DiscoveryEnabledNIWSServerList,源码以下:
package org.springframework.cloud.netflix.ribbon.eureka; public class DomainExtractingServerList implements ServerList<DiscoveryEnabledServer> { private ServerList<DiscoveryEnabledServer> list; private final RibbonProperties ribbon; private boolean approximateZoneFromHostname; public DomainExtractingServerList(ServerList<DiscoveryEnabledServer> list, IClientConfig clientConfig, boolean approximateZoneFromHostname) { this.list = list; this.ribbon = RibbonProperties.from(clientConfig); this.approximateZoneFromHostname = approximateZoneFromHostname; } @Override public List<DiscoveryEnabledServer> getInitialListOfServers() { List<DiscoveryEnabledServer> servers = setZones(this.list .getInitialListOfServers()); return servers; } @Override public List<DiscoveryEnabledServer> getUpdatedListOfServers() { List<DiscoveryEnabledServer> servers = setZones(this.list .getUpdatedListOfServers()); return servers; } }
同时,经过上面的源码还能够看出,getInitialListOfServers()和getUpdatedListOfServers()方法的实现其实交给DiscoveryEnabledNIWSServerList来实现的,下面看一下DiscoveryEnabledNIWSServerList中这两个方法的实现
package com.netflix.niws.loadbalancer; public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{ private static final Logger logger = LoggerFactory.getLogger(DiscoveryEnabledNIWSServerList.class); String clientName; String vipAddresses; boolean isSecure = false; boolean prioritizeVipAddressBasedServers = true; String datacenter; String targetRegion; int overridePort = DefaultClientConfigImpl.DEFAULT_PORT; boolean shouldUseOverridePort = false; boolean shouldUseIpAddr = false; private final Provider<EurekaClient> eurekaClientProvider; @Override public List<DiscoveryEnabledServer> getInitialListOfServers(){ return obtainServersViaDiscovery(); } @Override public List<DiscoveryEnabledServer> getUpdatedListOfServers(){ return obtainServersViaDiscovery(); } private List<DiscoveryEnabledServer> obtainServersViaDiscovery() { List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>(); if (eurekaClientProvider == null || eurekaClientProvider.get() == null) { logger.warn("EurekaClient has not been initialized yet, returning an empty list"); return new ArrayList<DiscoveryEnabledServer>(); } EurekaClient eurekaClient = eurekaClientProvider.get(); if (vipAddresses!=null){ for (String vipAddress : vipAddresses.split(",")) { // if targetRegion is null, it will be interpreted as the same region of client List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion); for (InstanceInfo ii : listOfInstanceInfo) { if (ii.getStatus().equals(InstanceStatus.UP)) { if(shouldUseOverridePort){ if(logger.isDebugEnabled()){ logger.debug("Overriding port on client name: " + clientName + " to " + overridePort); } // copy is necessary since the InstanceInfo builder just uses the original reference, // and we don't want to corrupt the global eureka copy of the object which may be // used by other clients in our system InstanceInfo copy = new InstanceInfo(ii); if(isSecure){ ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build(); }else{ ii = new InstanceInfo.Builder(copy).setPort(overridePort).build(); } } DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr); des.setZone(DiscoveryClient.getZone(ii)); serverList.add(des); } } if (serverList.size()>0 && prioritizeVipAddressBasedServers){ break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers } } } return serverList; } }
上述代码的主要逻辑是借助EurekaClient从服务注册中心获取到具体的服务实例(InstanceInfo)列表,首页获取到EurekaClient,而后更具逻辑服务名(vipAddress),获取服务实例,将服务实例状态为UP(正常服务)的实例转换为DiscoveryEnabledServer对象,最终放在一个列表里返回。
在获取到ServerList以后,DomainExtractingServerList会调用自身的setZones方法,源码以下:
private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) { List<DiscoveryEnabledServer> result = new ArrayList<>(); boolean isSecure = this.ribbon.isSecure(true); boolean shouldUseIpAddr = this.ribbon.isUseIPAddrForServer(); for (DiscoveryEnabledServer server : servers) { result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname)); } return result; }
经过源码能够看出,该方法的主要做用是将DiscoveryEnabledNIWSServerList返回的List<DiscoveryEnabledServer>列表中的元素,转换成DiscoveryEnabledServer的子类对象DomainExtractingServer,在该类对象的构造函数中将为服务实例对象设置一些必要的属性,如id,zone,isAliveFlag,readToServer等。
在DynamicServerListLoadBalancer类中有以下一段代码,ServerListUpdater对象的实现就是对ServerList的更新
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() { @Override public void doUpdate() { updateListOfServers(); } };
下面看一下ServerListUpdater接口,该类内部还定义了一个UpdateAction接口,下面看一下源码:
package com.netflix.loadbalancer; public interface ServerListUpdater { public interface UpdateAction { void doUpdate(); } void start(UpdateAction updateAction); void stop(); String getLastUpdate(); long getDurationSinceLastUpdateMs(); int getNumberMissedCycles(); int getCoreThreads(); }
下面是该接口方法的介绍
下面看一下ServerListUpdater的具体实现类
下面看一下PollingServerListUpdater的实现,咱们从start函数看起
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"); } }
经过上述代码能够看出大体逻辑,建立了一个Runnable线程任务,在线程中调用了UpdateAction的doUpdate()方法,最后再启动定时任务,initialDelayMs默认值1000ms,refreshIntervalMs默认值是30*1000ms,也就是说更新服务实例在初始化以后延迟1s后开始执行,并以30s为周期重复执行。
下面咱们回顾一下UpdateAction中doUpdate()方法的具体实现,源码以下:
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); }
在上述源码能够看出,首先是调用了ServerList的getUpdatedListOfServers方法,这是用来从Eureka Server获取正常的服务实例列表。在获取完服务实例列表之后,咱们会调用filter.getFilteredListOfServers(servers),此处的filter就是咱们所要找的ServerListFilter。
ServerListFilter接口很是简单,仅仅有一个List<T> getFilteredListOfServers(List<T> servers)方法,用于实现对服务列表的过滤,下面看一下它的主要实现类:
在上面的图中,ZonePreferenceServerListFilter的实现是Spring Cloud Ribbon中对Netflix Ribbon的扩展实现,其余都是Netflix Ribbon中的原生实现类。下面咱们这些类的特色。
package com.netflix.loadbalancer; public abstract class AbstractServerListFilter<T extends Server> implements ServerListFilter<T> { private volatile LoadBalancerStats stats; public void setLoadBalancerStats(LoadBalancerStats stats) { this.stats = stats; } public LoadBalancerStats getLoadBalancerStats() { return stats; } }
该类是一个抽象过滤器,在这里定义了过滤时须要的一个重要依据对象LoadBalancerStats,该对象存储了关于负载均衡器的一些属性和统计信息等。
该过滤器基于区域感知(Zone Affinity)的方式实现服务实例的过滤,它会根据提供服务的实例所处的区域(Zone)与消费者自身所处区域(Zone)进行比较,过滤掉那些不是同处一个区域的实例。
public List<T> getFilteredListOfServers(List<T> servers) { if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){ List<T> filteredServers = Lists.newArrayList(Iterables.filter( servers, this.zoneAffinityPredicate.getServerOnlyPredicate())); if (shouldEnableZoneAffinity(filteredServers)) { return filteredServers; } else if (zoneAffinity) { overrideCounter.increment(); } } return servers; }
从上面的源码能够看出,对于服务实例列表的过滤是经过Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate())来实现的,其中判断依据由ZoneAffinityPredicate实现服务实例与消费者的Zone比较。
在比较事后,并非当即返回过滤以后的ServerList。而是经过shouldEnableZoneAffinity方法来判断是否要启用区域感知的功能。下面看一下shouldEnableZoneAffinity的实现:
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); ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered); double loadPerServer = snapshot.getLoadPerServer(); int instanceCount = snapshot.getInstanceCount(); int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount(); 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; } } }
经过查看源码能够看出,它调用了LoadBalancerStats的getZoneSnapshot方法来获取这些过滤后的同区域实例的基础指标(包含实例数量、断路由器断开数、活动请求数、实例平均负载等),而后根据一系列的算法求出下面的几个评价值并与设置的阀值进行对比,若是有一个条件符合,就不启用区域感知过滤的服务实例清单。
上述算法实现为集群出现区域故障时,依然能够依靠其余区域的实例进行正常服务提供了完善的高可用保障。
该过滤器彻底继承自ZoneAffinityServerListFilter,是默认的NIWS(Netflix Internal Web Service)过滤器。
该过滤器继承自ZoneAffinityServerListFilter,适合拥有大规模服务集群(上百或更多)的系统。该过滤器能够产生一个区域感知结果的子集列表,同时还可以经过比较服务实例的通讯失败数量和并发链接数来断定该服务是否健康来选择性地从服务实例列表中剔除那些相对不够健康的实例。该过滤器的实现主要有如下三步:
1.获取区域感知的过滤结果,做为候选的服务实例清单。
2.从当前消费者维护的服务实例子集中剔除那些相对不够健康的实例(同时将这些实例从候选清单中剔除,防止第三步的时候又被选入),不健康的标准以下:
a. 服务实例的并发链接数超过客户端配置的值,默认为0,配置参数为<clientName>.<nameSpace>.ServerListSubsetFilter.eliminationConnectionThresold
b. 服务实例的失败数超过客户端配置的值,默认为0,配置参数为<clientName>.<nameSpace>.ServerListSubsetFilter.eliminationFailureThresold。
c. 若是按符合上面任一规则的服务实例剔除后,剔除比例小于客户端默认配置的百分比,默认为10%,配置参数为<clientName>.<nameSpace>.ServerListSubsetFilter.forceEliminatePercent,那么就先对剩下的实例列表进行健康排序,再从最不健康的实例进行剔除,直到达到配置的剔除百分比。
3.在完成剔除后,清单已经少了至少10%的服务实例,最后经过随机的方式从候选清单中选出一批实例加入到清单中,以保持服务实例子集与原来的数量一致,默认的实例本身数量为20,配置参数为<clientName>.<nameSpace>.ServerListSubsetFilter.size。
Spring Cloud整合时新增的过滤器。若使用Spring Cloud整合Eureka和Ribbon时会默认使用该过滤器。它实现了经过配置或者Eureka实例元数据的所属区域(Zone)来过滤出同区域的服务实例。下面看一下源码:
@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<>(); for (Server server : output) { if (this.zone.equalsIgnoreCase(server.getZone())) { local.add(server); } } if (!local.isEmpty()) { return local; } } return output; }
经过源码分析能够得出如下几个步骤:
ZoneAwareLoadBalancer负载均衡器是对DynamicServerListLoadBalancer的扩展。
在DynamicServerListLoadBalancer中,并无对chooseServer函数进行重写,所以会采用BaseLoadBalancer中chooseServer,使用RoundRobinRule规则,以线性轮询的方式来选择调用的服务实例,该算法实现简单并无区域(Zone)的概念,因此会把全部实例视为一个Zone下的节点看待,这样就会周期性的产生跨区域(Zone)访问的状况,因为跨区域会产生更高的延迟,这些跨区域的实例主要以用来防止区域性故障实现高可用为目的,不能做为常规的访问实例。
ZoneAwareLoadBalancer能够有效的避免DynamicServerListLoadBalancer的问题。下面咱们来看一下是如何避免这个问题的。
首先,在ZoneAwareLoadBalancer中并无重写setServerList,说明实现服务实例清单的更新主逻辑没有修改。可是ZoneAwareLoadBalancer中重写了setServerListForZones(Map<String, List<Server>> zoneServersMap)函数。
下面咱们先看一下DynamicServerListLoadBalancer中setServerListForZones中的实现:
@Override public void setServersList(List lsrv) { super.setServersList(lsrv); List<T> serverList = (List<T>) lsrv; Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>(); for (Server server : serverList) { // make sure ServerStats is created to avoid creating them on hot // path getLoadBalancerStats().getSingleServerStat(server); String zone = server.getZone(); if (zone != null) { zone = zone.toLowerCase(); List<Server> servers = serversInZones.get(zone); if (servers == null) { servers = new ArrayList<Server>(); serversInZones.put(zone, servers); } servers.add(server); } } setServerListForZones(serversInZones); } protected void setServerListForZones( Map<String, List<Server>> zoneServersMap) { LOGGER.debug("Setting server list for zones: {}", zoneServersMap); getLoadBalancerStats().updateZoneServerMapping(zoneServersMap); }
经过分析源码能够看出,setServerListForZones的调用位于更新服务实例清单setServersList函数的最后,在setServerListForZones的实现中,首先获取了LoadBalancerStats对象,而后调用其updateZoneServerMapping方法,下面咱们看一下该方法的具体实现:
private ZoneStats getZoneStats(String zone) { zone = zone.toLowerCase(); ZoneStats zs = zoneStatsMap.get(zone); if (zs == null){ zoneStatsMap.put(zone, new ZoneStats(this.getName(), zone, this)); zs = zoneStatsMap.get(zone); } return zs; } public void updateZoneServerMapping(Map<String, List<Server>> map) { upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>(map); // make sure ZoneStats object exist for available zones for monitoring purpose for (String zone: map.keySet()) { getZoneStats(zone); } }
经过上述源码能够看出,setServerListForZones方法的主要做用是根据按区域(Zone)分组的实例列表,为负载均衡器中的LoadBalancerStats对象建立ZoneStats并放入Map zoneStatsMap集合中,每个区域对应一个ZoneStats,它用于存储每一个Zone的一些状态和统计信息。
下面咱们看一下ZoneAwareLoadBalancer负载均衡器中setServerListForZones方法的实现:
@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()); } } }
首先建立了一个ConcurrentHashMap<String, BaseLoadBalancer>类型的balancers对象,它将用来存储每一个Zone区域对应的负载均衡器。具体的负载均衡器的建立则是在下面的第一个循环中调用getLoadBalancer方法来完成,在建立负载均衡器的时候同时会建立它的规则(若是当前实现中没有IRule,就建立一个AvailabilityFilteringRule规则,若是已经有实例,则克隆一个)。
在建立完负载均衡器以后立刻调用setServersList方法为其设置对应Zone区域的实例清单。
第二个循环是对Zone区域中实例清单的检查,看看是否有Zone区域下已经没有实例了,是的话就将balancers中对应Zone区域的实例列表清空,该操做的做用是为了后续选择节点时,防止过期的Zone区域统计信息干扰具体实例的选择算法。
下面咱们再看一下负载均衡器是如何挑选服务实例,来实现对区域的识别的:
@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); } Server server = null; try { LoadBalancerStats lbStats = getLoadBalancerStats(); Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats); logger.debug("Zone snapshots: {}", zoneSnapshot); if (triggeringLoad == null) { triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty( "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d); } if (triggeringBlackoutPercentage == null) { triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty( "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d); } Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()); logger.debug("Available zones: {}", availableZones); if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) { String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones); logger.debug("Zone chosen: {}", zone); if (zone != null) { BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone); server = zoneLoadBalancer.chooseServer(key); } } } 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); } }
经过源码能够看出,只有当负载均衡器中维护的实例所属的Zone区域的个数大于1的时候才会执行这里的选择策略,不然仍是将使用父类的实现。当Zone区域的个数大于1的时候,它的实现步骤以下:
1.调用ZoneAvoidanceRule中的静态方法createSnapshot(lbStats),为当前负载均衡器中全部的Zone区域分别建立快照,保存在在Map zoneSnapshot中,这些快照中的数据将用于后续的算法。
2.调用ZoneAvoidanceRule中的静态方法getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()),来获取可用的Zone区域集合,在该函数中会经过Zone区域快照中的统计数据来实现可用区的挑选
a.首先会剔除符合这些规则的Zone区域:所属实例数为0的Zone区域;Zone区域内实例的平均负载小于0,或者实例故障率(断路由器断开次数/实例数)大于等于阀值(默认值为0.99999)
b.而后根据Zone区域的实例平均负载计算出最差的Zone区域,这里的最差指的是实例平均负载最高的Zone区域
c.若是在上面的过程当中没有符合剔除要求的区域,同时实例最大平均负载小于阀值(默认20%),就直接返回全部Zone区域为可用区域。不然,从最坏Zone区域集合中随机选择一个,将它从可用Zone区域集合中剔除。
3.当得到的可用Zone区域集合不为空,而且个数小于Zone区域总数,就随机选择一个Zone区域
4.在肯定了某个Zone区域后,则获取了对应Zone区域的负载均衡器,并调用chooseServer来选择具体的服务实例,而在chooseServer中将使用IRule接口的choose方法来选择具体的服务实例。在这里,IRule接口的实现会采用ZoneAvoidanceRule来挑选具体的服务实例。
后面会介绍负载均衡策略的源码分析,请继续关注!!!