擼一擼Spring Cloud Ribbon的原理-負載均衡器

在上一篇《擼一擼Spring Cloud Ribbon的原理》中整理髮現,RestTemplate內部調用負載均衡攔截器,攔截器內最終是調用了負載均衡器來選擇服務實例。html

接下來擼一擼負載均衡器的內部,看看是如何獲取服務實例,獲取之後作了哪些處理,處理後又是如何選取服務實例的。git

分紅三個部分來擼:github

  • 配置
  • 獲取服務
  • 選擇服務

 

配置spring

在上一篇《擼一擼Spring Cloud Ribbon的原理》的配置部分能夠看到默認的負載均衡器是ZoneAwareLoadBalancer。app

看一看配置類。負載均衡

位置:dom

spring-cloud-netflix-core-1.3.5.RELEASE.jar
org.springframework.cloud.netflix.ribbon
RibbonClientConfiguration.classasync

@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實例。ide

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配置後,實際上各實例變成了

config:DefaultClientConfigImpl。

rule:ZoneAvoidanceRule。

ping:NIWSDiscoveryPing。

serverList:DomainExtractingServerList,內部是DiscoveryEnabledNIWSServerList,其實是經過服務發現獲取服務信息列表。

serverListFilter:ZonePreferenceServerListFilter。

serverListUpdater:PollingServerListUpdater。

 

獲取服務

在找到獲取服務信息入口前,先把負載均衡器的類繼承關係擼一下。 

在ZoneAwareLoadBalancer的構造中調用了父類DynamicServerListLoadBalancer構造。

位置:

ribbon-loadbalancer-2.2.2.jar

com.netflix.loadbalancer

ZoneAwareLoadBalancer.class

在DynamicServerListLoadBalancer的構造中,調用了restOfInit函數。

ribbon-loadbalancer-2.2.2.jar

com.netflix.loadbalancer

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信息,作到服務列表中。

 

2.經過ServerListFilter 實例對獲取到的服務信息列表進行過濾。

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實例。

 

3.將過濾後的服務信息列表保存到LoadBalancerStats中做爲狀態保持。

跟進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。

 

一個個來看一下

1.經過配置ZoneAwareNIWSDiscoveryLoadBalancer.enabled=false關閉區域感知負載均衡,或者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策略實例。

 

2.採用區域感知,或者zone的個數>1。

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

相關文章
相關標籤/搜索