国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring Cloud Ribbon負(fù)載均衡器

y1chuan / 3618人閱讀

摘要:代碼如下定義了用來存儲(chǔ)負(fù)載均衡器各服務(wù)實(shí)例屬性和統(tǒng)計(jì)信息的對(duì)象。下面看一下負(fù)載均衡器增加了哪些內(nèi)容。

客戶端負(fù)載均衡Spring Cloud Ribbon

?Spring Cloud Ribbon是一個(gè)基于HTTP和TCP的客戶端負(fù)載均衡工具,基于Netflix Ribbon實(shí)現(xiàn)。

目錄

客戶端負(fù)載均衡

源碼分析

負(fù)載均衡器(本文重點(diǎn))

負(fù)載均衡策略

配置詳解

自動(dòng)化配置

客戶端負(fù)載均衡&源碼分析

?請(qǐng)?jiān)谏弦黄恼碌幕A(chǔ)上進(jìn)行下面的學(xué)習(xí),點(diǎn)擊這里閱讀上一篇

負(fù)載均衡器

?下面我們看一下具體的的負(fù)載均衡器,也就是ILoadBalancer接口的實(shí)現(xiàn)類。

AbstractLoadBalancer

?該類是ILoadBalancer接口的抽象實(shí)現(xiàn)類。

?在該抽象實(shí)現(xiàn)類中含有一個(gè)關(guān)于服務(wù)實(shí)例的分組枚舉類,該枚舉類主要有以下三種類型:

ALL:所有服務(wù)實(shí)例

STATUS_UP:正常服務(wù)的實(shí)例

STATUS_NOT_UP:停止服務(wù)的實(shí)例

?該抽象類下面的的函數(shù)有以下幾個(gè):

chooseServer():該函數(shù)通過調(diào)用接口中的chooseServer(Object key)實(shí)現(xiàn),其中參數(shù)key為null,表示在選擇具體服務(wù)實(shí)例時(shí)忽略key的條件判斷

List getServerList(ServerGroup serverGroup):定義了根據(jù)分組類型來獲取不同的服務(wù)實(shí)例的列表

LoadBalancerStats getLoadBalancerStats():定義了獲取LoadBalancerStats對(duì)象的方法,LoadBalancerStats對(duì)象被用來存儲(chǔ)負(fù)載均衡器中各個(gè)服務(wù)實(shí)例當(dāng)前的屬性和統(tǒng)計(jì)信息。這些信息可以用來觀察負(fù)載均衡器的運(yùn)行情況,同時(shí)也是用來制定負(fù)載均衡策略的重要依據(jù)。

BaseLoadBalancer

?該類是Ribbon負(fù)載均衡器的基礎(chǔ)實(shí)現(xiàn)類,在該類中定義了很多關(guān)于負(fù)載均衡相關(guān)的基礎(chǔ)內(nèi)容。

?該類中定義并維護(hù)了兩個(gè)存儲(chǔ)服務(wù)實(shí)例Server對(duì)象的列表。一個(gè)用于存儲(chǔ)所有服務(wù)實(shí)例的清單,一個(gè)用于存儲(chǔ)正常服務(wù)的實(shí)例清單。代碼如下:

    @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List allServerList = Collections.synchronizedList(new ArrayList());
    @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
    protected volatile List upServerList = Collections.synchronizedList(new ArrayList());

?定義了用來存儲(chǔ)負(fù)載均衡器各服務(wù)實(shí)例屬性和統(tǒng)計(jì)信息的LoadBalancerStats對(duì)象。

?定義了檢查服務(wù)實(shí)例是否正常的IPing對(duì)象,在BaseLoadBalancer中默認(rèn)為null,需要在構(gòu)造時(shí)注入它的實(shí)現(xiàn)。

?定義了檢查服務(wù)實(shí)例操作的執(zhí)行策略對(duì)象IPingStrategy,在BaseLoadBalancerz中默認(rèn)使用了該類中定義的靜態(tài)內(nèi)部類SerialPingStrategy。根據(jù)源碼,可以看到該策略采用線性遍歷ping服務(wù)實(shí)例的方式實(shí)現(xiàn)檢查。但是該策略在當(dāng)IPing的實(shí)現(xiàn)速度不理想或者Server列表過大時(shí),可能會(huì)影響系統(tǒng)性能。這時(shí)就需要自己去實(shí)現(xiàn)自己的IPing策略。

?定義了負(fù)載均衡的處理規(guī)則IRule對(duì)象,從BaseLoadBalancer中chooseServer(Object key)方法源碼中也可以看出它是將服務(wù)實(shí)例選擇的任務(wù)交給了IRule中的Server choose(Object key)方法。默認(rèn)的IRule實(shí)現(xiàn)是RoundRobinRule。

?啟動(dòng)Ping任務(wù),在BaseLoadBalancer的默認(rèn)構(gòu)造函數(shù)中,會(huì)直接啟動(dòng)一個(gè)用于定時(shí)檢查Server是否健康的任務(wù)。該任務(wù)默認(rèn)執(zhí)行的時(shí)間間隔為10s。

?實(shí)現(xiàn)了ILoadBalancer接口定義的負(fù)載均衡器應(yīng)該具備以下操作:

addServers(List newServers):向負(fù)載均衡器中增加新的服務(wù)實(shí)例列表。該實(shí)現(xiàn)將原本已經(jīng)維護(hù)的所有服務(wù)實(shí)例清單allServerList和新傳入的服務(wù)實(shí)例清單newServers都加入到newList中,然后再調(diào)用setServersList(List lsrv)方法對(duì)newList進(jìn)行處理。在BaseLoadBalancer中的默認(rèn)實(shí)現(xiàn)會(huì)用新的列表覆蓋舊的列表。后面幾個(gè)擴(kuò)展實(shí)現(xiàn)類對(duì)于服務(wù)實(shí)例清單的更新的優(yōu)化都是通過對(duì)setServersList(List lsrv)重寫來實(shí)現(xiàn)的。

Server chooseServer(Object key):挑選一個(gè)具體的服務(wù)實(shí)例,上面介紹IRule的時(shí)候已經(jīng)說過,不再重說。

markServerDown(Server server):用來標(biāo)記某個(gè)服務(wù)實(shí)例暫停服務(wù)

List getReachableServers():獲取可用的服務(wù)實(shí)例列表

List getAllServers():獲取所有的服務(wù)實(shí)例列表

DynamicServerListLoadBalancer

?DynamicServerListLoadBalancer該類繼承于BaseLoadBalancer類,它是對(duì)基礎(chǔ)負(fù)載均衡器的擴(kuò)展。

?在該負(fù)載均衡器,實(shí)現(xiàn)了服務(wù)實(shí)例清單在運(yùn)行期的動(dòng)態(tài)更新能力;同時(shí),它還具備了對(duì)服務(wù)實(shí)例清單的過濾功能,我們可以通過過濾器來選擇性的獲取一批服務(wù)實(shí)例清單。

?下面看一下負(fù)載均衡器增加了哪些內(nèi)容。

ServerList

?通過查看源碼,發(fā)現(xiàn)增加了一個(gè)關(guān)于服務(wù)列表的操作對(duì)象ServerList serverListImpl,其中T是一個(gè)Server的子類,即代表了一個(gè)具體的服務(wù)實(shí)例的擴(kuò)展類。其中ServerList的定義如下:

public interface ServerList {

    public List getInitialListOfServers();
    
    public List getUpdatedListOfServers();   

}

?該抽象接口定義了兩個(gè)抽象方法,如下:

List getInitialListOfServers():用于獲取初始化的服務(wù)實(shí)例清單

List getUpdatedListOfServers():用于獲取更新的服務(wù)實(shí)例清單

?該抽象接口的實(shí)現(xiàn)類有很多,因?yàn)樵撠?fù)載均衡器中需要實(shí)現(xiàn)服務(wù)實(shí)例的動(dòng)態(tài)更新,那么就需要Ribbon具備訪問Eureka服務(wù)注冊(cè)中心獲取服務(wù)實(shí)例的能力,在DynamicServerListLoadBalancer默認(rèn)的ServerList是DomainExtractingServerList(默認(rèn)的實(shí)現(xiàn)是在EurekaRibbonClientConfiguration),源碼如下:

package org.springframework.cloud.netflix.ribbon.eureka;

@Configuration
public class EurekaRibbonClientConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public ServerList ribbonServerList(IClientConfig config, Provider 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的源碼可以看出,該類中有一個(gè)ServerList list,通過查看DomainExtractingServerList的構(gòu)造函數(shù),DomainExtractingServerList中的ServerList對(duì)象就是從上面的代碼中傳過來的DiscoveryEnabledNIWSServerList,源碼如下:

package org.springframework.cloud.netflix.ribbon.eureka;

public class DomainExtractingServerList implements ServerList {

    private ServerList list;
    private final RibbonProperties ribbon;

    private boolean approximateZoneFromHostname;

    public DomainExtractingServerList(ServerList list,
            IClientConfig clientConfig, boolean approximateZoneFromHostname) {
        this.list = list;
        this.ribbon = RibbonProperties.from(clientConfig);
        this.approximateZoneFromHostname = approximateZoneFromHostname;
    }

    @Override
    public List getInitialListOfServers() {
        List servers = setZones(this.list
                .getInitialListOfServers());
        return servers;
    }

    @Override
    public List getUpdatedListOfServers() {
        List servers = setZones(this.list
                .getUpdatedListOfServers());
        return servers;
    }

}

?同時(shí),通過上面的源碼還可以看出,getInitialListOfServers()和getUpdatedListOfServers()方法的實(shí)現(xiàn)其實(shí)交給DiscoveryEnabledNIWSServerList來實(shí)現(xiàn)的,下面看一下DiscoveryEnabledNIWSServerList中這兩個(gè)方法的實(shí)現(xiàn)

package com.netflix.niws.loadbalancer;

public class DiscoveryEnabledNIWSServerList extends AbstractServerList{

    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 eurekaClientProvider;

    @Override
    public List getInitialListOfServers(){
        return obtainServersViaDiscovery();
    }

    @Override
    public List getUpdatedListOfServers(){
        return obtainServersViaDiscovery();
    }

    private List obtainServersViaDiscovery() {
        List serverList = new ArrayList();

        if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
            logger.warn("EurekaClient has not been initialized yet, returning an empty list");
            return new ArrayList();
        }

        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 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從服務(wù)注冊(cè)中心獲取到具體的服務(wù)實(shí)例(InstanceInfo)列表,首頁獲取到EurekaClient,然后更具邏輯服務(wù)名(vipAddress),獲取服務(wù)實(shí)例,將服務(wù)實(shí)例狀態(tài)為UP(正常服務(wù))的實(shí)例轉(zhuǎn)換為DiscoveryEnabledServer對(duì)象,最終放在一個(gè)列表里返回。

?在獲取到ServerList之后,DomainExtractingServerList會(huì)調(diào)用自身的setZones方法,源碼如下:

    private List setZones(List servers) {
        List 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列表中的元素,轉(zhuǎn)換成DiscoveryEnabledServer的子類對(duì)象DomainExtractingServer,在該類對(duì)象的構(gòu)造函數(shù)中將為服務(wù)實(shí)例對(duì)象設(shè)置一些必要的屬性,如id,zone,isAliveFlag,readToServer等。

ServerListUpdate

?在DynamicServerListLoadBalancer類中有如下一段代碼,ServerListUpdater對(duì)象的實(shí)現(xiàn)就是對(duì)ServerList的更新

    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();
        }
    };

?下面看一下ServerListUpdater接口,該類內(nèi)部還定義了一個(gè)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();
}

?下面是該接口方法的介紹

void doUpdate():該方法的實(shí)現(xiàn)內(nèi)容就是對(duì)ServerList的具體更新操作

void start(UpdateAction updateAction):啟動(dòng)更新服務(wù)器,傳入的UpdateAction對(duì)象為更新操作的具體實(shí)現(xiàn)

void stop():停止更新服務(wù)器

String getLastUpdate():獲取最近的更新時(shí)間戳

long getDurationSinceLastUpdateMs():獲取上一次更新到現(xiàn)在的時(shí)間間隔,單位ms

int getNumberMissedCycles():獲取錯(cuò)過的更新周期數(shù)

int getCoreThreads():獲取核心線程數(shù)

?下面看一下ServerListUpdater的具體實(shí)現(xiàn)類

PollingServerListUpdater:動(dòng)態(tài)服務(wù)列表更新的默認(rèn)策略,DynamicServerListLoadBalancer負(fù)載均衡器中的默認(rèn)實(shí)現(xiàn)就是該類,它通過定時(shí)任務(wù)的方式進(jìn)行服務(wù)列表的更新。

EurekaNotificationServerListUpdater:該更新器可以用于DynamicServerListLoadBalancer負(fù)載均衡器,但是它的觸發(fā)機(jī)制與PollingServerListUpdater不同,它需要利用Eureka的事件監(jiān)聽器來驅(qū)動(dòng)服務(wù)列表的更新操作。

?下面看一下PollingServerListUpdater的實(shí)現(xiàn),我們從start函數(shù)看起

    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");
        }
    }

?通過上述代碼可以看出大致邏輯,創(chuàng)建了一個(gè)Runnable線程任務(wù),在線程中調(diào)用了UpdateAction的doUpdate()方法,最后再啟動(dòng)定時(shí)任務(wù),initialDelayMs默認(rèn)值1000ms,refreshIntervalMs默認(rèn)值是30*1000ms,也就是說更新服務(wù)實(shí)例在初始化之后延遲1s后開始執(zhí)行,并以30s為周期重復(fù)執(zhí)行。

ServerListFilter

?下面我們回顧一下UpdateAction中doUpdate()方法的具體實(shí)現(xiàn),源碼如下:

    public void updateListOfServers() {
        List servers = new ArrayList();
        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);
    }

?在上述源碼可以看出,首先是調(diào)用了ServerList的getUpdatedListOfServers方法,這是用來從Eureka Server獲取正常的服務(wù)實(shí)例列表。在獲取完服務(wù)實(shí)例列表以后,我們會(huì)調(diào)用filter.getFilteredListOfServers(servers),此處的filter就是我們所要找的ServerListFilter。

?ServerListFilter接口非常簡單,僅僅有一個(gè)List getFilteredListOfServers(List servers)方法,用于實(shí)現(xiàn)對(duì)服務(wù)列表的過濾,下面看一下它的主要實(shí)現(xiàn)類:

?在上面的圖中,ZonePreferenceServerListFilter的實(shí)現(xiàn)是Spring Cloud Ribbon中對(duì)Netflix Ribbon的擴(kuò)展實(shí)現(xiàn),其他都是Netflix Ribbon中的原生實(shí)現(xiàn)類。下面我們這些類的特點(diǎn)。

AbstractServerListFilter
package com.netflix.loadbalancer;

public abstract class AbstractServerListFilter implements ServerListFilter {

    private volatile LoadBalancerStats stats;
    
    public void setLoadBalancerStats(LoadBalancerStats stats) {
        this.stats = stats;
    }
    
    public LoadBalancerStats getLoadBalancerStats() {
        return stats;
    }

}

?該類是一個(gè)抽象過濾器,在這里定義了過濾時(shí)需要的一個(gè)重要依據(jù)對(duì)象LoadBalancerStats,該對(duì)象存儲(chǔ)了關(guān)于負(fù)載均衡器的一些屬性和統(tǒng)計(jì)信息等。

ZoneAffinityServerListFilter

?該過濾器基于區(qū)域感知(Zone Affinity)的方式實(shí)現(xiàn)服務(wù)實(shí)例的過濾,它會(huì)根據(jù)提供服務(wù)的實(shí)例所處的區(qū)域(Zone)與消費(fèi)者自身所處區(qū)域(Zone)進(jìn)行比較,過濾掉那些不是同處一個(gè)區(qū)域的實(shí)例。

    public List getFilteredListOfServers(List servers) {
        if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){
            List filteredServers = Lists.newArrayList(Iterables.filter(
                    servers, this.zoneAffinityPredicate.getServerOnlyPredicate()));
            if (shouldEnableZoneAffinity(filteredServers)) {
                return filteredServers;
            } else if (zoneAffinity) {
                overrideCounter.increment();
            }
        }
        return servers;
    }

?從上面的源碼可以看出,對(duì)于服務(wù)實(shí)例列表的過濾是通過Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate())來實(shí)現(xiàn)的,其中判斷依據(jù)由ZoneAffinityPredicate實(shí)現(xiàn)服務(wù)實(shí)例與消費(fèi)者的Zone比較。

?在比較過后,并不是立即返回過濾之后的ServerList。而是通過shouldEnableZoneAffinity方法來判斷是否要啟用區(qū)域感知的功能。下面看一下shouldEnableZoneAffinity的實(shí)現(xiàn):

    private boolean shouldEnableZoneAffinity(List 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;
            }
            
        }
    }

?通過查看源碼可以看出,它調(diào)用了LoadBalancerStats的getZoneSnapshot方法來獲取這些過濾后的同區(qū)域?qū)嵗幕A(chǔ)指標(biāo)(包含實(shí)例數(shù)量、斷路由器斷開數(shù)、活動(dòng)請(qǐng)求數(shù)、實(shí)例平均負(fù)載等),然后根據(jù)一系列的算法求出下面的幾個(gè)評(píng)價(jià)值并與設(shè)置的閥值進(jìn)行對(duì)比,如果有一個(gè)條件符合,就不啟用區(qū)域感知過濾的服務(wù)實(shí)例清單。

?上述算法實(shí)現(xiàn)為集群出現(xiàn)區(qū)域故障時(shí),依然可以依靠其他區(qū)域的實(shí)例進(jìn)行正常服務(wù)提供了完善的高可用保障。

blackOutServerPercentage:故障實(shí)例百分比(斷路由器斷開數(shù)/實(shí)例數(shù)量)>=0.8

activeReqeustsPerServer:實(shí)例平均負(fù)載>=0.6

availableServers:可用實(shí)例數(shù)量(實(shí)例數(shù)量-斷路器斷開數(shù))<2

DefaultNIWSServerListFilter

?該過濾器完全繼承自ZoneAffinityServerListFilter,是默認(rèn)的NIWS(Netflix Internal Web Service)過濾器。

ServerListSubsetFilter

?該過濾器繼承自ZoneAffinityServerListFilter,適合擁有大規(guī)模服務(wù)集群(上百或更多)的系統(tǒng)。該過濾器可以產(chǎn)生一個(gè)區(qū)域感知結(jié)果的子集列表,同時(shí)還能夠通過比較服務(wù)實(shí)例的通信失敗數(shù)量和并發(fā)連接數(shù)來判定該服務(wù)是否健康來選擇性地從服務(wù)實(shí)例列表中剔除那些相對(duì)不夠健康的實(shí)例。該過濾器的實(shí)現(xiàn)主要有以下三步:

1.獲取區(qū)域感知的過濾結(jié)果,作為候選的服務(wù)實(shí)例清單。

2.從當(dāng)前消費(fèi)者維護(hù)的服務(wù)實(shí)例子集中剔除那些相對(duì)不夠健康的實(shí)例(同時(shí)將這些實(shí)例從候選清單中剔除,防止第三步的時(shí)候又被選入),不健康的標(biāo)準(zhǔn)如下:

?a. 服務(wù)實(shí)例的并發(fā)連接數(shù)超過客戶端配置的值,默認(rèn)為0,配置參數(shù)為..ServerListSubsetFilter.eliminationConnectionThresold

?b. 服務(wù)實(shí)例的失敗數(shù)超過客戶端配置的值,默認(rèn)為0,配置參數(shù)為..ServerListSubsetFilter.eliminationFailureThresold。

?c. 如果按符合上面任一規(guī)則的服務(wù)實(shí)例剔除后,剔除比例小于客戶端默認(rèn)配置的百分比,默認(rèn)為10%,配置參數(shù)為..ServerListSubsetFilter.forceEliminatePercent,那么就先對(duì)剩下的實(shí)例列表進(jìn)行健康排序,再從最不健康的實(shí)例進(jìn)行剔除,直到達(dá)到配置的剔除百分比。

3.在完成剔除后,清單已經(jīng)少了至少10%的服務(wù)實(shí)例,最后通過隨機(jī)的方式從候選清單中選出一批實(shí)例加入到清單中,以保持服務(wù)實(shí)例子集與原來的數(shù)量一致,默認(rèn)的實(shí)例自己數(shù)量為20,配置參數(shù)為..ServerListSubsetFilter.size。

ZonePreferenceServerListFilter

?Spring Cloud整合時(shí)新增的過濾器。若使用Spring Cloud整合Eureka和Ribbon時(shí)會(huì)默認(rèn)使用該過濾器。它實(shí)現(xiàn)了通過配置或者Eureka實(shí)例元數(shù)據(jù)的所屬區(qū)域(Zone)來過濾出同區(qū)域的服務(wù)實(shí)例。下面看一下源碼:

    @Override
    public List getFilteredListOfServers(List servers) {
        List output = super.getFilteredListOfServers(servers);
        if (this.zone != null && output.size() == servers.size()) {
            List local = new ArrayList<>();
            for (Server server : output) {
                if (this.zone.equalsIgnoreCase(server.getZone())) {
                    local.add(server);
                }
            }
            if (!local.isEmpty()) {
                return local;
            }
        }
        return output;
    }

?通過源碼分析可以得出以下幾個(gè)步驟:

首先通過父類的ZoneAffinityServerListFilter過濾器來獲得區(qū)域感知的服務(wù)實(shí)例列表

遍歷獲取的服務(wù)實(shí)例列表,取出根據(jù)消費(fèi)者配置預(yù)設(shè)的區(qū)域Zone來進(jìn)行過濾

過濾的結(jié)果如果是空直接返回區(qū)域感知的服務(wù)實(shí)例列表,如果不為空則返回過濾后的結(jié)果

ZoneAwareLoadBalancer

?ZoneAwareLoadBalancer負(fù)載均衡器是對(duì)DynamicServerListLoadBalancer的擴(kuò)展。

?在DynamicServerListLoadBalancer中,并沒有對(duì)chooseServer函數(shù)進(jìn)行重寫,因此會(huì)采用BaseLoadBalancer中chooseServer,使用RoundRobinRule規(guī)則,以線性輪詢的方式來選擇調(diào)用的服務(wù)實(shí)例,該算法實(shí)現(xiàn)簡單并沒有區(qū)域(Zone)的概念,所以會(huì)把所有實(shí)例視為一個(gè)Zone下的節(jié)點(diǎn)看待,這樣就會(huì)周期性的產(chǎn)生跨區(qū)域(Zone)訪問的情況,由于跨區(qū)域會(huì)產(chǎn)生更高的延遲,這些跨區(qū)域的實(shí)例主要以用來防止區(qū)域性故障實(shí)現(xiàn)高可用為目的,不能作為常規(guī)的訪問實(shí)例。

?ZoneAwareLoadBalancer可以有效的避免DynamicServerListLoadBalancer的問題。下面我們來看一下是如何避免這個(gè)問題的。

?首先,在ZoneAwareLoadBalancer中并沒有重寫setServerList,說明實(shí)現(xiàn)服務(wù)實(shí)例清單的更新主邏輯沒有修改。但是ZoneAwareLoadBalancer中重寫了setServerListForZones(Map> zoneServersMap)函數(shù)。

?下面我們先看一下DynamicServerListLoadBalancer中setServerListForZones中的實(shí)現(xiàn):

    @Override
    public void setServersList(List lsrv) {
        super.setServersList(lsrv);
        List serverList = (List) lsrv;
        Map> serversInZones = new HashMap>();
        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 servers = serversInZones.get(zone);
                if (servers == null) {
                    servers = new ArrayList();
                    serversInZones.put(zone, servers);
                }
                servers.add(server);
            }
        }
        setServerListForZones(serversInZones);
    }

    protected void setServerListForZones(
            Map> zoneServersMap) {
        LOGGER.debug("Setting server list for zones: {}", zoneServersMap);
        getLoadBalancerStats().updateZoneServerMapping(zoneServersMap);
    }

?通過分析源碼可以看出,setServerListForZones的調(diào)用位于更新服務(wù)實(shí)例清單setServersList函數(shù)的最后,在setServerListForZones的實(shí)現(xiàn)中,首先獲取了LoadBalancerStats對(duì)象,然后調(diào)用其updateZoneServerMapping方法,下面我們看一下該方法的具體實(shí)現(xiàn):

    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> map) {
        upServerListZoneMap = new ConcurrentHashMap>(map);
        // make sure ZoneStats object exist for available zones for monitoring purpose
        for (String zone: map.keySet()) {
            getZoneStats(zone);
        }
    }

?通過上述源碼可以看出,setServerListForZones方法的主要作用是根據(jù)按區(qū)域(Zone)分組的實(shí)例列表,為負(fù)載均衡器中的LoadBalancerStats對(duì)象創(chuàng)建ZoneStats并放入Map zoneStatsMap集合中,每一個(gè)區(qū)域?qū)?yīng)一個(gè)ZoneStats,它用于存儲(chǔ)每個(gè)Zone的一些狀態(tài)和統(tǒng)計(jì)信息。

?下面我們看一下ZoneAwareLoadBalancer負(fù)載均衡器中setServerListForZones方法的實(shí)現(xiàn):

    @Override
    protected void setServerListForZones(Map> zoneServersMap) {
        super.setServerListForZones(zoneServersMap);
        if (balancers == null) {
            balancers = new ConcurrentHashMap();
        }
        for (Map.Entry> 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 existingLBEntry: balancers.entrySet()) {
            if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {
                existingLBEntry.getValue().setServersList(Collections.emptyList());
            }
        }
    }

?首先創(chuàng)建了一個(gè)ConcurrentHashMap類型的balancers對(duì)象,它將用來存儲(chǔ)每個(gè)Zone區(qū)域?qū)?yīng)的負(fù)載均衡器。具體的負(fù)載均衡器的創(chuàng)建則是在下面的第一個(gè)循環(huán)中調(diào)用getLoadBalancer方法來完成,在創(chuàng)建負(fù)載均衡器的時(shí)候同時(shí)會(huì)創(chuàng)建它的規(guī)則(如果當(dāng)前實(shí)現(xiàn)中沒有IRule,就創(chuàng)建一個(gè)AvailabilityFilteringRule規(guī)則,如果已經(jīng)有實(shí)例,則克隆一個(gè))。

?在創(chuàng)建完負(fù)載均衡器之后馬上調(diào)用setServersList方法為其設(shè)置對(duì)應(yīng)Zone區(qū)域的實(shí)例清單。

?第二個(gè)循環(huán)是對(duì)Zone區(qū)域中實(shí)例清單的檢查,看看是否有Zone區(qū)域下已經(jīng)沒有實(shí)例了,是的話就將balancers中對(duì)應(yīng)Zone區(qū)域的實(shí)例列表清空,該操作的作用是為了后續(xù)選擇節(jié)點(diǎn)時(shí),防止過時(shí)的Zone區(qū)域統(tǒng)計(jì)信息干擾具體實(shí)例的選擇算法。

?下面我們?cè)倏匆幌仑?fù)載均衡器是如何挑選服務(wù)實(shí)例,來實(shí)現(xiàn)對(duì)區(qū)域的識(shí)別的:

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

?通過源碼可以看出,只有當(dāng)負(fù)載均衡器中維護(hù)的實(shí)例所屬的Zone區(qū)域的個(gè)數(shù)大于1的時(shí)候才會(huì)執(zhí)行這里的選擇策略,否則還是將使用父類的實(shí)現(xiàn)。當(dāng)Zone區(qū)域的個(gè)數(shù)大于1的時(shí)候,它的實(shí)現(xiàn)步驟如下:

1.調(diào)用ZoneAvoidanceRule中的靜態(tài)方法createSnapshot(lbStats),為當(dāng)前負(fù)載均衡器中所有的Zone區(qū)域分別創(chuàng)建快照,保存在在Map zoneSnapshot中,這些快照中的數(shù)據(jù)將用于后續(xù)的算法。

2.調(diào)用ZoneAvoidanceRule中的靜態(tài)方法getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()),來獲取可用的Zone區(qū)域集合,在該函數(shù)中會(huì)通過Zone區(qū)域快照中的統(tǒng)計(jì)數(shù)據(jù)來實(shí)現(xiàn)可用區(qū)的挑選

?a.首先會(huì)剔除符合這些規(guī)則的Zone區(qū)域:所屬實(shí)例數(shù)為0的Zone區(qū)域;Zone區(qū)域內(nèi)實(shí)例的平均負(fù)載小于0,或者實(shí)例故障率(斷路由器斷開次數(shù)/實(shí)例數(shù))大于等于閥值(默認(rèn)值為0.99999)

?b.然后根據(jù)Zone區(qū)域的實(shí)例平均負(fù)載計(jì)算出最差的Zone區(qū)域,這里的最差指的是實(shí)例平均負(fù)載最高的Zone區(qū)域

?c.如果在上面的過程中沒有符合剔除要求的區(qū)域,同時(shí)實(shí)例最大平均負(fù)載小于閥值(默認(rèn)20%),就直接返回所有Zone區(qū)域?yàn)榭捎脜^(qū)域。否則,從最壞Zone區(qū)域集合中隨機(jī)選擇一個(gè),將它從可用Zone區(qū)域集合中剔除。

3.當(dāng)獲得的可用Zone區(qū)域集合不為空,并且個(gè)數(shù)小于Zone區(qū)域總數(shù),就隨機(jī)選擇一個(gè)Zone區(qū)域

4.在確定了某個(gè)Zone區(qū)域后,則獲取了對(duì)應(yīng)Zone區(qū)域的負(fù)載均衡器,并調(diào)用chooseServer來選擇具體的服務(wù)實(shí)例,而在chooseServer中將使用IRule接口的choose方法來選擇具體的服務(wù)實(shí)例。在這里,IRule接口的實(shí)現(xiàn)會(huì)采用ZoneAvoidanceRule來挑選具體的服務(wù)實(shí)例。

后續(xù)

?后面會(huì)介紹負(fù)載均衡策略的源碼分析,請(qǐng)繼續(xù)關(guān)注!!!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76735.html

相關(guān)文章

  • Spring Cloud實(shí)戰(zhàn)(三)-Spring Cloud Netflix Ribbon

    摘要:概要什么是實(shí)戰(zhàn)整合實(shí)現(xiàn)負(fù)載均衡是什么是一個(gè)客戶端負(fù)載均衡的組件什么是負(fù)載均衡負(fù)載均衡就是分發(fā)請(qǐng)求流量到不同的服務(wù)器目前的實(shí)現(xiàn)有軟件和硬件負(fù)載均衡分為兩種服務(wù)器端負(fù)載均衡如上圖所示服務(wù)器端負(fù)載均衡是對(duì)客戶透明的用戶請(qǐng)求到服務(wù)器真正的服務(wù)器是由 概要 什么是Spring Cloud Netflix Ribbon? 實(shí)戰(zhàn):整合Ribbon實(shí)現(xiàn)負(fù)載均衡 Spring Cloud Netfl...

    wangbinke 評(píng)論0 收藏0
  • Spring Cloud 參考文檔(客戶端負(fù)載衡器Ribbon

    摘要:客戶端負(fù)載均衡器是一個(gè)客戶端負(fù)載均衡器,可以讓你對(duì)和客戶端的行為進(jìn)行大量控制,已經(jīng)使用了,因此,如果你使用,此部分也適用。 客戶端負(fù)載均衡器:Ribbon Ribbon是一個(gè)客戶端負(fù)載均衡器,可以讓你對(duì)HTTP和TCP客戶端的行為進(jìn)行大量控制,F(xiàn)eign已經(jīng)使用了Ribbon,因此,如果你使用@FeignClient,此部分也適用。 Ribbon中的一個(gè)核心概念是命名客戶端,每個(gè)負(fù)載均...

    Songlcy 評(píng)論0 收藏0
  • SpringCloud(第 006 篇)電影微服務(wù),使用 Ribbon 在客戶端進(jìn)行負(fù)載均衡

    摘要:第篇電影微服務(wù),使用在客戶端進(jìn)行負(fù)載均衡一大致介紹是發(fā)布的云中間層服務(wù)開源項(xiàng)目,主要功能是提供客戶端負(fù)載均衡算法。而被注解后,能過用負(fù)載均衡,主要是維護(hù)了一個(gè)被注解的列表,并給列表中的添加攔截器,進(jìn)而交給負(fù)載均衡器去處理。 SpringCloud(第 006 篇)電影微服務(wù),使用 Ribbon 在客戶端進(jìn)行負(fù)載均衡 - 一、大致介紹 1、Ribbon 是 Netflix 發(fā)布的云中間層...

    nodejh 評(píng)論0 收藏0
  • 一起學(xué)習(xí)使用Spring Cloud Netflix之Ribbon

    摘要:本例中介紹如何使用來完成服務(wù)調(diào)用并實(shí)現(xiàn)負(fù)載均衡。即,對(duì)于注冊(cè)中心而言,生產(chǎn)者和調(diào)用者都是端。文件配置如下在文件中,我們將應(yīng)用命名為,端口為,表示注冊(cè)中心地址。 前言 Ribbon是Spring Cloud體系中完成負(fù)載均衡的重要組件。Spring Cloud體系中有兩種完成服務(wù)調(diào)用的組件,一種是Ribbon+RestTemplate,另一種Feign。Feign默認(rèn)使用的也是Ribbo...

    nidaye 評(píng)論0 收藏0
  • Spring Cloud Ribbon

    摘要:客戶端負(fù)載均衡需要客戶端自己維護(hù)自己要訪問的服務(wù)實(shí)例清單,這些服務(wù)清單來源于注冊(cè)中心在使用進(jìn)行服務(wù)治理時(shí)。使用從負(fù)載均衡器中挑選出的服務(wù)實(shí)例來執(zhí)行請(qǐng)求內(nèi)容。 客戶端負(fù)載均衡Spring Cloud Ribbon ?Spring Cloud Ribbon是一個(gè)基于HTTP和TCP的客戶端負(fù)載均衡工具,基于Netflix Ribbon實(shí)現(xiàn)。 目錄 客戶端負(fù)載均衡(本文重點(diǎn)) 源碼分析(本...

    fasss 評(píng)論0 收藏0
  • SpringCloud(第 008 篇)電影微服務(wù),使用配置文件配置 Ribbon 在客戶端進(jìn)行負(fù)載

    摘要:第篇電影微服務(wù),使用配置文件配置在客戶端進(jìn)行負(fù)載均衡調(diào)度算法一大致介紹通過配置來設(shè)置客戶端進(jìn)行負(fù)載均衡的調(diào)度算法通過兩種代碼調(diào)用方式來測(cè)試客戶端負(fù)載均衡算法二實(shí)現(xiàn)步驟添加引用包模塊客戶端發(fā)現(xiàn)模塊 SpringCloud(第 008 篇)電影微服務(wù),使用 application.yml 配置文件配置 Ribbon 在客戶端進(jìn)行負(fù)載均衡調(diào)度算法 - 一、大致介紹 1、通過 applicat...

    wangjuntytl 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<