Spring cloud Ribbon 客戶端負載均衡詳解(三)負載均衡策略

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

Spring cloud Ribbon 客戶端負載均衡詳解(三)負載均衡策略

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

AbstractLoadBalancerRule

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

<code>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;
}
}/<code>

RandomRule

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

<code>@Override
public Server choose(Object key) {
\treturn choose(getLoadBalancer(), key);
}

public Server choose(ILoadBalancer lb, Object key) {
\t...
\tServer server = null;
\twhile (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);
\t\tif (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
\t server = null;
Thread.yield();
}
return server;
}/<server>/<server>/<code>

RoundRobinRule

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

<code>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;
}/<server>/<server>/<code>

RetryRule

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

<code>public class RetryRule extends AbstractLoadBalancerRule {
\tIRule subRule = new RoundRobinRule();
\tlong maxRetryMillis = 500;
\t...
\tpublic Server choose(ILoadBalancer lb, Object key) {
\t\tlong requestTime = System.currentTimeMillis();
\t\tlong deadline = requestTime + maxRetryMillis;
\t\tServer answer = null;
\t\tanswer = subRule.choose(key);
\t\tif (((answer == null) || (!answer.isAlive()))
\t\t\t\t&& (System.currentTimeMillis() < deadline)) {
\t\t\tInterruptTask task = new InterruptTask(deadline
\t\t\t\t\t- System.currentTimeMillis());
\t\t\twhile (!Thread.interrupted()) {
\t\t\t\tanswer = subRule.choose(key);
\t\t\t\tif (((answer == null) || (!answer.isAlive()))
\t\t\t\t\t\t&& (System.currentTimeMillis() < deadline)) {
\t\t\t\t\tThread.yield();
\t\t\t\t} else {
\t\t\t\t\tbreak;
\t\t\t\t}
\t\t\t}
\t\t\ttask.cancel();
\t\t}
\t\tif ((answer == null) || (!answer.isAlive())) {
\t\t\treturn null;
\t\t} else {
\t\t\treturn answer;
\t\t}
\t}
\t...
}/<code>

WeightedResponseTimeRule

該策略是對RoundRobinRule的擴展,增加了根據實例的運行情況來計算權重,並根據權重來挑選實例,以達到更優的分配效果,它的實現主要有三個核心內容:

定時任務

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

<code>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);
}
}
}/<code>

權重計算

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

維護實例權重的計算過程通過maintainWeights函數實現,具體如下源碼所示:

<code>public void maintainWeights() {
ILoadBalancer lb = getLoadBalancer();
\t...
try {
logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
LoadBalancerStats stats = nlb.getLoadBalancerStats();
\t\t...
\t\t// 計算所有實例的平均響應時間的總和: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();
}
\t\t// 逐個計算每個實例的權重: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);
}
}/<double>/<double>/<code>

該函數的實現主要分為兩個步驟:

  • 根據LoadBalancerStats中記錄的每個實例的統計信息,累加所有實例的平均響應時間,得到總平均響應時間totalResponseTime,該值會用於後續的計算。
  • 為負載均衡器中維護的實例清單逐個計算權重(從第一個開始),計算規則為: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

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

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

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

實例選擇

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

<code>public Server choose(ILoadBalancer lb, Object key) {
\t...
List<double> currentWeights = accumulatedWeights;
\t\t...
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);
\t\tif (maxTotalWeight < 0.001d) {
\t// 如果最後一個實例的權重值小於0.001,則採用父類實現的線性輪詢的策略
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
\t// 如果最後一個實例的權重值大於等於0.001,就產生一個[0, maxTotalWeight)的隨機數
double randomWeight = random.nextDouble() * maxTotalWeight;
int n = 0;
for (Double d : currentWeights) {\t// 遍歷維護的權重清單,若權重大於等於隨機得到的數值,就選擇這個實例
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);

}
\t...
return server;
}/<server>/<double>/<code>

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

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

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

ClientConfigEnabledRoundRobinRule

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

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

<code>public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {

RoundRobinRule roundRobinRule = new RoundRobinRule();
\t...
@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");
}
}
}/<code>

BestAvailableRule

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

<code>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;
}
}/<server>/<code>

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

PredicateBasedRule

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

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

<code>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;
}
}
}/<server>/<code>

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

<code>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;
}
}
}/<server>/<server>/<server>/<server>/<server>/<server>/<predicatekey>/<code>

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

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

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

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

AvailabilityFilteringRule

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

<code>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;
}
}/<code>

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

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

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

<code>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);
}/<code>

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

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

ZoneAvoidanceRule

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

<code>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);
}
...
}/<code>

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

<code>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;
}

}/<abstractserverpredicate>/<server>/<server>/<server>/<abstractserverpredicate>/<code>

再來看看獲取過濾結果的實現函數getEligibleServers中,它的處理邏輯如下:

  • 使用主過濾條件對所有實例過濾並返回過濾後的實例清單
  • 依次使用次過濾條件列表中的過濾條件對主過濾條件的結果進行過濾
  • 每次過濾之後(包括主過濾條件和次過濾條件),都需要判斷下面兩個條件,只要有一個符合就不再進行過濾,將當前結果返回供線性輪詢算法選擇:過濾後的實例總數 >= 最小過濾實例數(minimalFilteredServers,默認為1)過濾後的實例比例 > 最小過濾百分比(minimalFilteredPercentage,默認為0)

參考:《Spring cloud 微服務實戰》書籍。



分享到:


相關文章: