Spring Cloud源碼分析(二)Ribbon(續(xù))

因文章長度限制,故分為兩篇。上一篇:《Spring Cloud源碼分析(二)Ribbon》

負載均衡策略

通過上一篇對Ribbon的源碼解讀,我們已經(jīng)對Ribbon實現(xiàn)的負載均衡器以及其中包含的服務實例過濾器、服務實例信息的存儲對象、區(qū)域的信息快照等都有了深入的認識和理解,但是對于負載均衡器中的服務實例選擇策略只是講解了幾個默認實現(xiàn)的內(nèi)容,而對于IRule的其他實現(xiàn)還沒有詳細的解讀,下面我們來看看在Ribbon中共提供了那些負載均衡的策略實現(xiàn)。

如上圖所示,我們可以看到在Ribbon中實現(xiàn)了非常多的選擇策略,其中也包含了我們在前面內(nèi)容中提到過的:RoundRobinRuleZoneAvoidanceRule。下面我們來詳細的解讀一下IRule接口的各個實現(xiàn)。

AbstractLoadBalancerRule

負載均衡策略的抽象類,在該抽象類中定義了負載均衡器ILoadBalancer對象,該對象能夠在具體實現(xiàn)選擇服務策略時,獲取到一些負載均衡器中維護的信息來作為分配依據(jù),并以此設計一些算法來實現(xiàn)針對特定場景的高效策略。

public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {

    private ILoadBalancer lb;

    @Override
    public void setLoadBalancer(ILoadBalancer lb){
        this.lb = lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer(){
        return lb;
    }
}

RandomRule

該策略實現(xiàn)了從服務實例清單中隨機選擇一個服務實例的功能。它的具體實現(xiàn)如下,可以看到IRule接口的choose(Object key)函數(shù)實現(xiàn),委托給了該類中的choose(ILoadBalancer lb, Object key),該方法增加了一個負載均衡器對象的參數(shù)。從具體的實現(xiàn)上看,它會使用傳入的負載均衡器來獲得可用實例列表upList和所有實例列表allList,并通過rand.nextInt(serverCount)函數(shù)來獲取一個隨機數(shù),并將該隨機數(shù)作為upList的索引值來返回具體實例。同時,具體的選擇邏輯在一個while (server == null)循環(huán)之內(nèi),而根據(jù)選擇邏輯的實現(xiàn),正常情況下每次選擇都應該能夠選出一個服務實例,如果出現(xiàn)死循環(huán)獲取不到服務實例時,則很有可能存在并發(fā)的Bug。

@Override
public Server choose(Object key) {
    return choose(getLoadBalancer(), key);
}

public Server choose(ILoadBalancer lb, Object key) {
    ...
    Server server = null;
    while (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();
        int serverCount = allList.size();
        if (serverCount == 0) {
            return null;
        }
        int index = rand.nextInt(serverCount);
        server = upList.get(index);
        if (server == null) {
            Thread.yield();
            continue;
        }
        if (server.isAlive()) {
            return (server);
        }
        server = null;
        Thread.yield();
    }
    return server;
}

RoundRobinRule

該策略實現(xiàn)了按照線性輪詢的方式依次選擇每個服務實例的功能。它的具體實現(xiàn)如下,其詳細結構與RandomRule非常類似。除了循環(huán)條件不同外,就是從可用列表中獲取所謂的邏輯不同。從循環(huán)條件中,我們可以看到增加了一個count計數(shù)變量,該變量會在每次循環(huán)之后累加,也就是說如果一直選擇不到server超過10次,那么就會結束嘗試,并打印一個警告信息No available alive servers after 10 tries from load balancer: ...。而線性輪詢的實現(xiàn)則是通過AtomicInteger nextServerCyclicCounter對象實現(xiàn),每次進行實例選擇時通過調(diào)用incrementAndGetModulo函數(shù)實現(xiàn)遞增。

public Server choose(ILoadBalancer lb, Object key) {
    ...
    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        List<Server> reachableServers = lb.getReachableServers();
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();
        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }
        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);
        if (server == null) {
            Thread.yield();
            continue;
        }
        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }
        server = null;
    }
    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

RetryRule

該策略實現(xiàn)了一個具備重試機制的實例選擇功能。從下面的實現(xiàn)中我們可以看到,在其內(nèi)部還定義了一個IRule對象,默認使用了RoundRobinRule實例。而在choose方法中的則實現(xiàn)了對內(nèi)部定義的策略進行反復嘗試的策略,若期間能夠選擇到具體的服務實例就返回,若選擇不到就根據(jù)設置的嘗試結束時間為閾值(maxRetryMillis參數(shù)定義的值 + choose方法開始執(zhí)行的時間戳),當超過該閾值后就返回null。

public class RetryRule extends AbstractLoadBalancerRule {
    IRule subRule = new RoundRobinRule();
    long maxRetryMillis = 500;
    ...
    public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + maxRetryMillis;
        Server answer = null;
        answer = subRule.choose(key);
        if (((answer == null) || (!answer.isAlive()))
                && (System.currentTimeMillis() < deadline)) {
            InterruptTask task = new InterruptTask(deadline
                    - System.currentTimeMillis());
            while (!Thread.interrupted()) {
                answer = subRule.choose(key);
                if (((answer == null) || (!answer.isAlive()))
                        && (System.currentTimeMillis() < deadline)) {
                    Thread.yield();
                } else {
                    break;
                }
            }
            task.cancel();
        }
        if ((answer == null) || (!answer.isAlive())) {
            return null;
        } else {
            return answer;
        }
    }
    ...
}

WeightedResponseTimeRule

該策略是對RoundRobinRule的擴展,增加了根據(jù)實例的運行情況來計算權重,并根據(jù)權重來挑選實例,以達到更優(yōu)的分配效果,它的實現(xiàn)主要有三個核心內(nèi)容:

定時任務

WeightedResponseTimeRule策略在初始化的時候會通過serverWeightTimer.schedule(new DynamicServerWeightTask(), 0, serverWeightTaskTimerInterval)啟動一個定時任務,用來為每個服務實例計算權重,該任務默認30秒執(zhí)行一次。

class DynamicServerWeightTask extends TimerTask {
    public void run() {
        ServerWeight serverWeight = new ServerWeight();
        try {
            serverWeight.maintainWeights();
        } catch (Throwable t) {
            logger.error("Throwable caught while running DynamicServerWeightTask for " + name, t);
        }
    }
}

權重計算

在源碼中我們可以輕松找到用于存儲權重的對象:List<Double> accumulatedWeights = new ArrayList<Double>(),該List中每個權重值所處的位置對應了負載均衡器維護的服務實例清單中所有實例在清單中的位置。

維護實例權重的計算過程通過maintainWeights函數(shù)實現(xiàn),具體如下源碼所示:

public void maintainWeights() {
    ILoadBalancer lb = getLoadBalancer();
    ...
    try {
        logger.info("Weight adjusting job started");
        AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
        LoadBalancerStats stats = nlb.getLoadBalancerStats();
        ...
        // 計算所有實例的平均響應時間的總和:totalResponseTime
        double totalResponseTime = 0;
        for (Server server : nlb.getAllServers()) {
            // this will automatically load the stats if not in cache
            ServerStats ss = stats.getSingleServerStat(server);
            totalResponseTime += ss.getResponseTimeAvg();
        }
        // 逐個計算每個實例的權重:weightSoFar + totalResponseTime - 實例的平均響應時間
        Double weightSoFar = 0.0;
        List<Double> finalWeights = new ArrayList<Double>();
        for (Server server : nlb.getAllServers()) {
            ServerStats ss = stats.getSingleServerStat(server);
            double weight = totalResponseTime - ss.getResponseTimeAvg();
            weightSoFar += weight;
            finalWeights.add(weightSoFar);
        }
        setWeights(finalWeights);
    } catch (Throwable t) {
        logger.error("Exception while dynamically calculating server weights", t);
    } finally {
        serverWeightAssignmentInProgress.set(false);
    }
}

該函數(shù)的實現(xiàn)主要分為兩個步驟:

  • 根據(jù)LoadBalancerStats中記錄的每個實例的統(tǒng)計信息,累加所有實例的平均響應時間,得到總平均響應時間totalResponseTime,該值會用于后續(xù)的計算。
  • 為負載均衡器中維護的實例清單逐個計算權重(從第一個開始),計算規(guī)則為:weightSoFar + totalResponseTime - 實例的平均響應時間,其中weightSoFar初始化為零,并且每計算好一個權重需要累加到weightSoFar上供下一次計算使用。totalResponseTime則的上計算結果。

舉個簡單的例子來理解這個計算過程:假設有4個實例A、B、C、D,他們的平均響應時間為:10、40、80、100,所以總響應時間是10 + 40 + 80 + 100 = 230,每個實例的權重為總響應時間與實例自身的平均響應時間的差的累積獲得,所以實例A、B、C、D的權重分別為:

  • 實例A:230 - 10 = 220
  • 實例B:220 + (230 - 40)= 410
  • 實例C:410 + (230 - 80)= 560
  • 實例D:560 + (230 - 100)= 690

需要注意的是,這里的權重值只是表示了各實例權重區(qū)間的上限,并非某個實例的優(yōu)先級,所以不是數(shù)值越大被選中的概率就越大。那么什么是權重區(qū)間呢?以上面例子的計算結果為例,它實際上是為這4個實例構建了4個不同的區(qū)間,每個實例的區(qū)間下限是上一個實例的區(qū)間上限,而每個實例的區(qū)間上限則是我們上面計算并存儲于List accumulatedWeights中的權重值,其中第一個實例的下限默認為零。所以,根據(jù)上面示例的權重計算結果,我們可以得到每個實例的權重區(qū)間:

  • 實例A:[0, 220]
  • 實例B:(220, 410]
  • 實例C:(410, 560]
  • 實例D:(560,690)

我們不難發(fā)現(xiàn),實際上每個區(qū)間的寬度就是:總的平均響應時間 - 實例的平均響應時間,所以實例的平均響應時間越短、權重區(qū)間的寬度越大,而權重區(qū)間的寬度越大被選中的概率就越高。可能很多讀者會問,這些區(qū)間邊界的開閉是如何確定的呢?為什么不那么規(guī)則?下面我們會通過實例選擇算法的解讀來解釋。

實例選擇

WeightedResponseTimeRule選擇實例的實現(xiàn)與之前介紹的算法結構類似,下面是它主體的算法(省略了循環(huán)體和一些判斷等處理):

public Server choose(ILoadBalancer lb, Object key) {
    ...
        List<Double> currentWeights = accumulatedWeights;
        ...
        List<Server> allList = lb.getAllServers();
        int serverCount = allList.size();
        if (serverCount == 0) {
            return null;
        }
        int serverIndex = 0;
        // 獲取最后一個實例的權重
        double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
        if (maxTotalWeight < 0.001d) {
            // 如果最后一個實例的權重值小于0.001,則采用父類實現(xiàn)的線性輪詢的策略
            server =  super.choose(getLoadBalancer(), key);
            if(server == null) {
                return server;
            }
        } else {
            // 如果最后一個實例的權重值大于等于0.001,就產(chǎn)生一個[0, maxTotalWeight)的隨機數(shù)
            double randomWeight = random.nextDouble() * maxTotalWeight;
            int n = 0;
            for (Double d : currentWeights) {   // 遍歷維護的權重清單,若權重大于等于隨機得到的數(shù)值,就選擇這個實例
                if (d >= randomWeight) {
                    serverIndex = n;
                    break;
                } else {
                    n++;
                }
            }
            server = allList.get(serverIndex);
        }
    ...
    return server;
}

從源碼中,我們可以看到,選擇實例的核心過程就兩步:

  • 生產(chǎn)一個[0, 最大權重值)區(qū)間內(nèi)的隨機數(shù)。
  • 遍歷權重列表,比較權重值與隨機數(shù)的大小,如果權重值大于等于隨機數(shù),就拿當前權重列表的索引值去服務實例列表中獲取具體實例。這就是在上一節(jié)中提到的服務實例會根據(jù)權重區(qū)間挑選的原理,而權重區(qū)間邊界的開閉原則根據(jù)算法,正常應該每個區(qū)間為(x, y]的形式,但是第一個實例和最后一個實例為什么不同呢?由于隨機數(shù)的最小取值可以為0,所以第一個實例的下限是閉區(qū)間,同時隨機數(shù)的最大值取不到最大權重值,所以最后一個實例的上限是開區(qū)間。

若繼續(xù)以上面的數(shù)據(jù)為例,進行服務實例的選擇,則該方法會從[0, 690)區(qū)間中選出一個隨機數(shù),比如選出的隨機數(shù)為230,由于該值位于第二個區(qū)間,所以此時就會選擇實例B來進行請求。

ClientConfigEnabledRoundRobinRule

該策略較為特殊,我們一般不直接使用它。因為它本身并沒有實現(xiàn)什么特殊的處理邏輯,正如下面的源碼所示,在它的內(nèi)部定義了一個RoundRobinRule策略,而choose函數(shù)的實現(xiàn)也正是使用了RoundRobinRule的線性輪詢機制,所以它實現(xiàn)的功能實際上與RoundRobinRule相同,那么定義它有什么特殊的用處呢?

雖然我們不會直接使用該策略,但是通過繼承該策略,那么默認的choose就實現(xiàn)了線性輪詢機制,在子類中做一些高級策略時通常都有可能會存在一些無法實施的情況,那么就可以通過父類的實現(xiàn)作為備選。在后文中我們將繼續(xù)介紹的高級策略均是基于ClientConfigEnabledRoundRobinRule的擴展。

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {

    RoundRobinRule roundRobinRule = new RoundRobinRule();
    ...
    @Override
    public Server choose(Object key) {
        if (roundRobinRule != null) {
            return roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException(
                    "This class has not been initialized with the RoundRobinRule class");
        }
    }
}

BestAvailableRule

該策略繼承自ClientConfigEnabledRoundRobinRule,在實現(xiàn)中它注入了負載均衡器的統(tǒng)計對象:LoadBalancerStats,同時在具體的choose算法中利用LoadBalancerStats保存的實例統(tǒng)計信息來選擇滿足要求的實例。從如下源碼中我們可以看到,它通過遍歷負載均衡器中維護的所有服務實例,會過濾掉故障的實例,并找出并發(fā)請求數(shù)最小的一個,所以該策略的特性是選出最空閑的實例。

public Server choose(Object key) {
    if (loadBalancerStats == null) {
        return super.choose(key);
    }
    List<Server> serverList = getLoadBalancer().getAllServers();
    int minimalConcurrentConnections = Integer.MAX_VALUE;
    long currentTime = System.currentTimeMillis();
    Server chosen = null;
    for (Server server: serverList) {
        ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
        if (!serverStats.isCircuitBreakerTripped(currentTime)) {
            int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
            if (concurrentConnections < minimalConcurrentConnections) {
                minimalConcurrentConnections = concurrentConnections;
                chosen = server;
            }
        }
    }
    if (chosen == null) {
        return super.choose(key);
    } else {
        return chosen;
    }
}

同時,由于該算法的核心依據(jù)是統(tǒng)計對象loadBalancerStats,當其為空的時候,該策略是無法執(zhí)行的。所以從源碼中我們可以看到,當loadBalancerStats為空的時候,它會采用父類的線性輪詢策略,正如我們在介紹ClientConfigEnabledRoundRobinRule時那樣,它的子類在無法滿足實現(xiàn)高級策略時候,可以使用線性輪詢策略的特性。后面將要介紹的策略因為也都繼承自ClientConfigEnabledRoundRobinRule,所以他們都會具有這樣的特性。

PredicateBasedRule

這是一個抽象策略,它也繼承了ClientConfigEnabledRoundRobinRule,從其命名中可以猜出他是一個基于Predicate實現(xiàn)的策略,Predicate是Google Guava Collection工具對集合進行過濾的條件接口。

如下源碼所示,它定義了一個抽象函數(shù)getPredicate來獲取AbstractServerPredicate對象的實現(xiàn),而在choose函數(shù)中,通過AbstractServerPredicatechooseRoundRobinAfterFiltering函數(shù)來選出具體的服務實例。從該函數(shù)的命名我們也大致能猜出它的基礎邏輯:先通過子類中實現(xiàn)的Predicate邏輯來過濾一部分服務實例,然后再以線性輪詢的方式從過濾后的實例清單中選出一個。

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {

    public abstract AbstractServerPredicate getPredicate();

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }
    }
}

通過下面AbstractServerPredicate的源碼片段,可以證實我們上面所做的猜測。在上面choose函數(shù)中調(diào)用的chooseRoundRobinAfterFiltering方法先通過內(nèi)部定義的getEligibleServers函數(shù)來獲取備選的實例清單(實現(xiàn)了過濾),如果返回的清單為空,則用Optional.absent()來表示不存在,反之則以線性輪詢的方式從備選清單中獲取一個實例。

public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {

    ...

    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        return Optional.of(eligible.get(nextIndex.getAndIncrement() % eligible.size()));
    }

    public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
        if (loadBalancerKey == null) {
            return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
        } else {
            List<Server> results = Lists.newArrayList();
            for (Server server: servers) {
                if (this.apply(new PredicateKey(loadBalancerKey, server))) {
                    results.add(server);
                }
            }
            return results;
        }
    }
}

在了解了整體邏輯之后,我們來詳細看看實現(xiàn)過濾功能的getEligibleServers函數(shù)。從源碼上看,它的實現(xiàn)結構非常簡單清晰,通過遍歷服務清單,使用this.apply方法來判斷實例是否需要保留,是就添加到結果列表中。

可能到這里,不熟悉Google Guava Collections集合工具的讀者會比較困惑,這個applyAbstractServerPredicate中并找不到它的定義,那么它是如何實現(xiàn)過濾的呢?實際上,AbstractServerPredicate實現(xiàn)了com.google.common.base.Predicate接口,而apply方法是該接口中的定義,主要用來實現(xiàn)過濾條件的判斷邏輯,它輸入的參數(shù)則是過濾條件需要用到的一些信息(比如源碼中的new PredicateKey(loadBalancerKey, server)),它傳入了關于實例的統(tǒng)計信息和負載均衡器的選擇算法傳遞過來的key)。既然在AbstractServerPredicate中我們未能找到apply的實現(xiàn),所以這里的chooseRoundRobinAfterFiltering函數(shù)只是定義了一個模板策略:“先過濾清單,再輪詢選擇”。對于如何過濾,則需要我們在AbstractServerPredicate的子類去實現(xiàn)apply方法來確定具體的過濾策略了。

后面我們將要介紹的兩個策略就是基于此抽象策略實現(xiàn),只是它們使用了不同的Predicate實現(xiàn)來完成過濾邏輯以達到不同的實例選擇效果。

Google Guava Collections是一個對Java Collections Framework增強和擴展的一個開源項目。雖然Java Collections Framework已經(jīng)能夠 滿足了我們大多數(shù)情況下使用集合的要求,但是當遇到一些特殊的情況我們的代碼會比較冗長且容易出錯。Guava Collections 可以幫助我們的讓集合操作代碼更為簡短精煉并大大增強代碼的可讀性。

AvailabilityFilteringRule

該策略繼承自上面介紹的抽象策略PredicateBasedRule,所以它也繼承了“先過濾清單,再輪詢選擇”的基本處理邏輯,其中過濾條件使用了AvailabilityPredicate

public class AvailabilityPredicate extends  AbstractServerPredicate {

    ...

    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) {
            return true;
        }
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }

    private boolean shouldSkipServer(ServerStats stats) {
        if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped())
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }
}

從上述源碼中,我們可以知道它的主要過濾邏輯位于shouldSkipServer方法中,它主要判斷服務實例的兩項內(nèi)容:

  • 是否故障,即斷路器是否生效已斷開
  • 實例的并發(fā)請求數(shù)大于閾值,默認值為$2^{31}$ - 1,該配置我們可通過參數(shù)..ActiveConnectionsLimit來修改
    其中只要有一個滿足apply就返回false(代表該節(jié)點可能存在故障或負載過高),都不滿足就返回true。

在該策略中,除了實現(xiàn)了上面的過濾方法之外,對于choose的策略也做了一些改進優(yōu)化,所以父類的實現(xiàn)對于它來說只是一個備用選項,其具體實現(xiàn)如下:

public Server choose(Object key) {
    int count = 0;
    Server server = roundRobinRule.choose(key);
    while (count++ <= 10) {
        if (predicate.apply(new PredicateKey(server))) {
            return server;
        }
        server = roundRobinRule.choose(key);
    }
    return super.choose(key);
}

可以看到,它并沒有像父類中那樣,先遍歷所有的節(jié)點進行過濾,然后在過濾后的集合中選擇實例。而是先線性的方式選擇一個實例,接著用過濾條件來判斷該實例是否滿足要求,若滿足就直接使用該實例,若不滿足要求就再選擇下一個實例,并檢查是否滿足要求,如此循環(huán)進行,當這個過程重復了10次還是沒有找到符合要求的實例,就采用父類的實現(xiàn)方案。

簡單的說,該策略通過線性抽樣的方式直接嘗試尋找可用且較空閑的實例來使用,優(yōu)化了父類每次都要遍歷所有實例的開銷。

ZoneAvoidanceRule

該策略我們在介紹負載均衡器ZoneAwareLoadBalancer時已經(jīng)提到過了,它也是PredicateBasedRule的具體實現(xiàn)類。在之前的介紹中主要針對ZoneAvoidanceRule中用于選擇Zone區(qū)域策略的一些靜態(tài)函數(shù),比如:createSnapshot、getAvailableZones。在這里我們將詳細的看看ZoneAvoidanceRule作為服務實例過濾條件的實現(xiàn)原理。從下面ZoneAvoidanceRule的源碼片段中我們可以看到,它使用了CompositePredicate來進行服務實例清單的過濾。這是一個組合過濾條件,在其構造函數(shù)中,它以ZoneAvoidancePredicate為主過濾條件,AvailabilityPredicate為次過濾條件初始化了組合過濾條件的實例。

public class ZoneAvoidanceRule extends PredicateBasedRule {

    ...
    private CompositePredicate compositePredicate;

    public ZoneAvoidanceRule() {
        super();
        ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
        AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
        compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
    }
    ...
}

ZoneAvoidanceRule在實現(xiàn)的時候并沒有像AvailabilityFilteringRule那樣重寫choose函數(shù)來優(yōu)化,所以它完全遵循了父類的過濾主邏輯:“先過濾清單,再輪詢選擇”。其中過濾清單的條件就是我們上面提到的以ZoneAvoidancePredicate為主過濾條件、AvailabilityPredicate為次過濾條件的組合過濾條件CompositePredicate。從CompositePredicate的源碼片段中,我們可以看到它定義了一個主過濾條件AbstractServerPredicate delegate以及一組次過濾條件列表List fallbacks,所以它的次過濾列表是可以擁有多個的,并且由于它采用了List存儲所以次過濾條件是按順序執(zhí)行的。

public class CompositePredicate extends AbstractServerPredicate {

    private AbstractServerPredicate delegate;
    private List<AbstractServerPredicate> fallbacks = Lists.newArrayList();

    private int minimalFilteredServers = 1;
    private float minimalFilteredPercentage = 0;

    @Override
    public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
        List<Server> result = super.getEligibleServers(servers, loadBalancerKey);
        Iterator<AbstractServerPredicate> i = fallbacks.iterator();
        while (!(result.size() >= minimalFilteredServers && result.size() > (int) (servers.size() * minimalFilteredPercentage))
                && i.hasNext()) {
            AbstractServerPredicate predicate = i.next();
            result = predicate.getEligibleServers(servers, loadBalancerKey);
        }
        return result;
    }

}

再來看看獲取過濾結果的實現(xiàn)函數(shù)getEligibleServers中,它的處理邏輯如下:

  • 使用主過濾條件對所有實例過濾并返回過濾后的實例清單
  • 依次使用次過濾條件列表中的過濾條件對主過濾條件的結果進行過濾
  • 每次過濾之后(包括主過濾條件和次過濾條件),都需要判斷下面兩個條件,只要有一個符合就不再進行過濾,將當前結果返回供線性輪詢算法選擇:
    • 過濾后的實例總數(shù) >= 最小過濾實例數(shù)(minimalFilteredServers,默認為1)
    • 過濾后的實例比例 > 最小過濾百分比(minimalFilteredPercentage,默認為0)
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容