在上一篇《撸一撸Spring Cloud Ribbon的原理》中整理發現,RestTemplate内部調用負載均衡攔截器,攔截器内最終是調用了負載均衡器來選擇服務執行個體。
接下來撸一撸負載均衡器的内部,看看是如何擷取服務執行個體,擷取以後做了哪些處理,處理後又是如何選取服務執行個體的。
分成三個部分來撸:
- 配置
- 擷取服務
- 選擇服務
在上一篇《撸一撸Spring Cloud Ribbon的原理》的配置部分可以看到預設的負載均衡器是ZoneAwareLoadBalancer。
看一看配置類。
位置:
spring-cloud-netflix-core-1.3.5.RELEASE.jar
org.springframework.cloud.netflix.ribbon
RibbonClientConfiguration.class
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
// 略
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> 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);
}
// 略
}
在執行個體化ZoneAwareLoadBalancer的時候注入了,config、rule、ping、serverList、serverListFilter、serverListUpdater執行個體。
config:配置執行個體。
rule:負載均衡政策執行個體。
ping:ping執行個體。
serverList:擷取和更新服務的執行個體。
serverListFilter:服務過濾執行個體。
serverListUpdater:服務清單資訊更新執行個體。
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
// 略
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
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<Server> serverList, ServerListFilter<Server> 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);
}
// 略
}
在這裡配置相關的執行個體
config:DefaultClientConfigImpl。
rule:ZoneAvoidanceRule。
ping:DummyPing。
serverList:ConfigurationBasedServerList,基于配置的服務清單執行個體。
serverListFilter:ZonePreferenceServerListFilter。
serverListUpdater:PollingServerListUpdater。
要注意的是,在這裡serverList的執行個體是ConfigurationBasedServerList,這是在未使用Eureka時擷取服務資訊的執行個體,是從配置檔案中擷取。
那麼在和Eureka配合使用時,需要從 Eureka Server擷取服務資訊,那該是哪個執行個體來做這件事情呢。
在啟用Eureka服務發現時,會首先會采用EurekaRibbonClientConfiguration配置類。
spring-cloud-netflix-eureka-client-1.3.5.RELEASE.jar
org.springframework.cloud.netflix.ribbon.eureka
EurekaRibbonClientConfiguration.class
@Configuration
@CommonsLog
public class EurekaRibbonClientConfiguration {
// 略
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
return this.propertiesFactory.get(IPing.class, config, serviceId);
}
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
}
@Bean
@ConditionalOnMissingBean
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;
}
// 略
}
在首先采用了EurekaRibbonClientConfiguration配置後,實際上各執行個體變成了
ping:NIWSDiscoveryPing。
serverList:DomainExtractingServerList,内部是DiscoveryEnabledNIWSServerList,實際上是通過服務發現擷取服務資訊清單。
在找到擷取服務資訊入口前,先把負載均衡器的類繼承關系撸一下。

在ZoneAwareLoadBalancer的構造中調用了父類DynamicServerListLoadBalancer構造。
ribbon-loadbalancer-2.2.2.jar
com.netflix.loadbalancer
ZoneAwareLoadBalancer.class
在DynamicServerListLoadBalancer的構造中,調用了restOfInit函數。
DynamicServerListLoadBalancer.class
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
先是通過調用enableAndInitLearnNewServersFeature方法啟動定時更新服務清單,然後立即調用updateListOfServers函數馬上擷取并更新服務清單資訊。
先看下enableAndInitLearnNewServersFeature方法,實際上是調用了服務清單資訊更新執行個體的start方法啟動定時更新功能。
/**
* Feature that lets us add new instances (from AMIs) to the list of
* existing servers that the LB will use Call this method if you want this
* feature enabled
*/
public void enableAndInitLearnNewServersFeature() {
LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
serverListUpdater.start(updateAction);
}
這裡的服務清單資訊更新執行個體就是配置階段配置的PollingServerListUpdater執行個體,看一下這個類的構造和start方法。
public class PollingServerListUpdater implements ServerListUpdater {
// 略
private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;
// 略
private final AtomicBoolean isActive = new AtomicBoolean(false);
private volatile long lastUpdated = System.currentTimeMillis();
private final long initialDelayMs;
private final long refreshIntervalMs;
// 略
public PollingServerListUpdater(IClientConfig clientConfig) {
this(LISTOFSERVERS_CACHE_UPDATE_DELAY, getRefreshIntervalMs(clientConfig));
}
public PollingServerListUpdater(final long initialDelayMs, final long refreshIntervalMs) {
this.initialDelayMs = initialDelayMs;
this.refreshIntervalMs = refreshIntervalMs;
}
@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");
}
}
// 略
}
從構造和常量定義看出來,延遲一秒執行,預設每隔30秒執行更新,可以通過配置修改間隔更新的時間。
從start方法看,就是開了一個定時執行的schedule,定時執行 updateAction.doUpdate()。
回到start方法調用方DynamicServerListLoadBalancer類中看一下UpdateAction執行個體的定義。
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
實際上就是調用了DynamicServerListLoadBalancer類的updateListOfServers方法,這跟啟動完定時更新後立即更新服務資訊清單的路徑是一緻的。
繼續看updateListOfServers方法。
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.通過ServerList執行個體擷取服務資訊清單。
2.通過ServerListFilter 執行個體對擷取到的服務資訊清單進行過濾。
3.将過濾後的服務資訊清單儲存到LoadBalancerStats中作為狀态保持。
接下分别看一下。
1.通過ServerList執行個體擷取服務資訊清單。
ServerList執行個體就是配置階段生成的DomainExtractingServerList,擷取服務資訊都是委托給DiscoveryEnabledNIWSServerList。
public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
// 略
@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;
}
// 略
}
可以看到其實就是通過Eureka用戶端從Eureka服務端擷取所有服務執行個體資訊并把上線的包裝成DiscoveryEnabledServer執行個體,帶有zone資訊,做到服務清單中。
serverListFilte執行個體就是配置階段生成的ZonePreferenceServerListFilter,通過調用該執行個體的getFilteredListOfServers方法進行過濾。
@Data
@EqualsAndHashCode(callSuper = false)
public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> {
private String zone;
@Override
public void initWithNiwsConfig(IClientConfig niwsClientConfig) {
super.initWithNiwsConfig(niwsClientConfig);
if (ConfigurationManager.getDeploymentContext() != null) {
this.zone = ConfigurationManager.getDeploymentContext().getValue(
ContextKey.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<Server>();
for (Server server : output) {
if (this.zone.equalsIgnoreCase(server.getZone())) {
local.add(server);
}
}
if (!local.isEmpty()) {
return local;
}
}
return output;
}
}
在getFilteredListOfServers方法裡面,一上來是調用父類的同名方法先過濾,其實父類也是把和消費端同區域的服務給過濾出來使用,不僅如此,增加了些智能的判定,保證在故障/負載較高時或者可用執行個體較少時不進行同區域的過濾。
但是在ZonePreferenceServerListFilter.getFilteredListOfServers這裡,就算父類沒做過過濾,這裡依然要把同zone的服務給濾出來使用,誰叫這裡的類是ZonePreference的呢。
這是比較怪異的地方,感覺父類的智能判定沒什麼作用。
還是看看ZoneAffinityServerListFilter.getFilteredListOfServers做的辛苦工作吧。
public class ZoneAffinityServerListFilter<T extends Server> extends
AbstractServerListFilter<T> implements IClientConfigAware {
// 略
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;
}
}
}
@Override
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;
}
// 略
}
首先會将與消費端相同的zone的服務過濾出來,然後通過shouldEnableZoneAffinity(filteredServers)來判定是否可以采納同zone的服務,還是采用所有的服務。
在shouldEnableZoneAffinity方法内,對相同zone的服務做了一次snapshot,擷取這些服務的執行個體數量,平均負載,斷路的執行個體數進行計算判定。
可以看一下initWithNiwsConfig方法中關鍵名額的值。
判定條件:
斷路執行個體百分比>=0.8(斷路的執行個體數/服務的執行個體數量)
平均負載>=0.6
可用執行個體數<2(執行個體數量-斷路的執行個體數)
如果達到判定條件,那麼就使用全部的服務,保證可用性。
但,上面也說了,因為ZonePreferenceServerListFilter本身總是會選用和消費端zone一緻的服務,是以ZoneAffinityServerListFilter.getFilteredListOfServers中做的智能操作并沒什麼用。
不過,當然可以通過自定義配置來采用ZoneAffinityServerListFilter執行個體。
跟進updateAllServerList(servers);去,一步步深入,會發現,實際上是儲存到LoadBalancerStats中,并且這時候的服務是按照zone分組以HashMap<String, List<Server>>結構儲存的,key是zone。
實作了ILoadBalancer接口的負載均衡器,是通過實作chooseServer方法來進行服務的選擇,選擇後的服務做為目标請求服務。
看一下ZoneAwareLoadBalancer.chooseServer方法。
@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);
}
}
注意這裡有兩種用法:
1.通過配置ZoneAwareNIWSDiscoveryLoadBalancer.enabled=false關閉區域感覺負載均衡,或者zone的個數<=1個。
2.采用區域感覺,或者zone的個數>1。
一個個來看一下
這種情況下,調用了父類BaseLoadBalancer.chooseServer方法。
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;
}
}
}
這裡使用的負載均衡政策rule實際上就是構造ZoneAwareLoadBalancer時傳進來的,在配置階段生成的ZoneAvoidanceRule政策執行個體。
public void setRule(IRule rule) {
if (rule != null) {
this.rule = rule;
} else {
/* default rule */
this.rule = new RoundRobinRule();
}
if (this.rule.getLoadBalancer() != this) {
this.rule.setLoadBalancer(this);
}
}
假設,如果沒有配置,預設用的是RoundRobinRule政策執行個體。
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);
}
}
在這種情況下預設使用ZoneAvoidanceRule負載均衡政策。
擷取zone的snapshot資訊。
擷取可用的zone,通過觀察ZoneAvoidanceRule.getAvailableZones定義,不是可用zone的條件是:
- 所屬執行個體數==0。
- 故障率>0.99999或者平均負載<0。
- 如果不是上面兩種情況,就選擇負載最高的一個去除不作為可用的zone。
可用zone都擷取後,随機選一個。
并從該zone中,通過ZoneAwareLoadBalancer的父類BaseLoadBalancer.chooseServer選取服務,上面整理過,BaseLoadBalancer裡如果沒有傳入rule,那麼預設使用RoundRobinRule政策輪尋一個服務。
其實,還是上面擷取服務中ZonePreferenceServerListFilter過濾器的問題,實際上過濾出來的隻有一個和消費端相同的一個zone的服務,是以第2.部分的從可用zone中選取服務的功能是走不到,要走到就得把過濾器給換掉。
總結:
配置的負載均衡器會啟動schedule擷取服務資訊,在使用了Eureka用戶端時,會從Eureka服務擷取所有服務執行個體資訊,通過過濾器過濾出可以使用的服務,過濾器預設隻過濾出與消費端相同zone的服務,如果要保證高可用可配置ZoneAffinityServerListFilter過濾器,過濾後的服務清單,通過實作了IRule接口的負載均衡政策選取對應的服務,如果是使用zone感覺的政策,可以從負載情況良好的zone中選取合适的服務。
End