リボン負荷分散戦略
Ribbon
多くの選択戦略が実装されていることがわかります。IRule
インターフェースの各実装について詳しく説明しましょう。
抽象ロードバランサールール
ロード バランサ オブジェクトが定義されているロード バランシング ストラテジの抽象クラスILoadBalancer
。このオブジェクトは、特定の実装でサービス ストラテジを選択するときに、割り当ての基礎としてロード バランサに保持されている情報を取得でき、それに応じて設計されています。特定のシナリオに対して効率的な戦略を実装します。
/**
* Class that provides a default implementation for setting and getting load balancer
* 该类提供用户设置和获取负载均衡器的默认实现
* @author stonse
*
*/
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;
}
}
ランダムルール
この戦略は、サービス強度リストからサービス インスタンスをランダムに選択する機能を実装します。
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
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) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* 服务器列表在被维护的情况下,可能会出现为null,释放CPU资源再重试
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
IRole
インターフェイスの関数実装がchoose(Object key)
このクラスに委任されていることがわかりますchoose(ILoadBalancer lb, Object key)
。このメソッドはロード バランサー オブジェクトのパラメーターを追加します。特定の実装の観点から、受信ロード バランサーを使用して利用可能なインスタンス リストupList
とすべてのインスタンス リストを取得しallList
、rand.nextInt(serverCount)
関数を通じて乱数を取得し、その乱数をupList
インデックスとして使用して特定の実装を返します。同時に、特定の選択ロジックはwhile(server == null)
ループ内にあります。選択ロジックの実装によれば、通常の状況では、選択ごとにサービス インスタンスが選択される必要があります。無限ループが発生してサービス インスタンスを取得できない場合は、同時実行の可能性が高いですBug
。
ラウンドロビンルール
この戦略は、線形ポーリング方式で各サービス インスタンスを一度に選択する機能を実装します。
詳細な構造は とRandomRule
非常に似ていますが、ループ条件が異なります。これは、可能リストから得られる、いわゆる論理的な違いです。ループ条件から、カウント変数が追加されていることがわかります。count
これは各ループ値の後に累積されます。つまり、選択がserver
10 回未満の場合、試行は終了し、警告メッセージが出力されます。 . No up servers available from load balancer: ...
. リニア ポーリングの実装はAtomicInteger nextServerCyclicCounter
オブジェクトを通じて実装され、インスタンスが選択されるたびに関数を呼び出すことによって増分が達成されますincrementAndGetModulo
。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
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) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
再試行ルール
この戦略は、再試行メカニズムを備えたインスタンス選択機能を実装します。以下の実装から、オブジェクトも内部的に定義されておりIRule
、インスタンスがデフォルトで使用されていることがわかりますRoundRobinRule
。このメソッドではchoose
、内部で定義された戦略を繰り返し試行する戦略が実装されており、期間内に特定のサービス インスタンスを選択できた場合はそのサービス インスタンスが返され、選択できなかった場合は、設定された試行終了時刻がしきい値 ( ) となります。閾値をmaxRetryMillis 参数定义的值 + choose方法开始中的时间戳
超えた場合はリターンしますnull
。
public class RetryRule extends AbstractLoadBalancerRule {
IRule subRule = new RoundRobinRule();
long maxRetryMillis = 500;
...
/*
* Loop if necessary. Note that the time CAN be exceeded depending on the
* subRule, because we're not spawning additional threads and returning
* early.
*/
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)) {
/* pause and retry hoping it's transient */
Thread.yield();
} else {
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
...
}
WeightedResponseTimeRule
この戦略は のRoundRobinRule
拡張であり、インスタンスの実行ステータスに基づいた重み計算を追加し、より良い割り当て効果を達成するために重みに基づいてインスタンスを選択するもので、その実装には主に 3 つの核となる内容があります。
スケジュールされたタスク
WeightedResponseTimeRule
ポリシーが初期化されるとserverWeightTimer.schedule(new DynamicServerWeightTask(), 0, serverWeightTaskTimerInterval)
、スケジュールされたタスクが開始され、各サービス インスタンスの重みが計算されます。このタスクはデフォルトで 30 秒ごとに実行されます。
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
内の各重み値の位置は、ロード バランサによって維持されるサービス インスタンスのリスト内のすべての例の位置に対応します。
インスタンスの重みを維持する計算プロセスはmaintainWeight
、次のコードに示すように、関数によって行われます。
public void maintainWeights() {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return;
}
if (serverWeightAssignmentInProgress.get()) {
return; // Ping in progress - nothing to do
} else {
serverWeightAssignmentInProgress.set(true);
}
try {
logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
LoadBalancerStats stats = nlb.getLoadBalancerStats();
if (stats == null) {
// no statistics, nothing to do
return;
}
//所有示例的平均响应时间总和
double totalResponseTime = 0;
for (Server server : nlb.getAllServers()) {
// 如果服务实例的状态快照不在缓存汇总,这里会进行自动加载
ServerStats ss = stats.getSingleServerStat(server);
totalResponseTime += ss.getResponseTimeAvg();
}
// weight for each server is (sum of responseTime of all servers - responseTime)
// so that the longer the response time, the less the weight and the less likely to be chosen
Double weightSoFar = 0.0;
// create new list and hot swap the reference
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);
}
}
この関数の実装は主に 2 つのステップに分かれています。
- に記録された各インスタンスの統計に従って
LoadBalancerStats
、すべての例のプラットフォーム応答時間が累積され、合計平均応答時間を取得しますtotalResponseTime
。これは後続の計算で使用されます。 - ロードバランサに保持されているインスタンスリストの重みを最初から順に計算します。計算ルールは であり、 は
weightSoFar + totalResponseTime - 实例的平均响应时间
ゼロに初期化され、計算された各重みは次の計算のためにweightSoFar
累積する必要があります。weightSoFar
この計算プロセスを理解するために簡単な例を見てみましょう。4 つのインスタンス A、B、C、D があるとします。それらの平均応答時間は 10、40、60、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:
60 + (230 - 100) = 690
なお、ここでの重み値は各インスタンスの重み範囲の上限を表すものであり、特定のインスタンスの優先度を表すものではないため、データが大きいほど選択される確率が高くなるわけではありません。では、体重間隔とは何でしょうか?上記の計算を例にとると、実際には、これら 4 つのインスタンスに対して 4 つの異なる間隔が構築されます。各インスタンスの間隔の下限は、前のインスタンスの間隔の上限であり、各インスタンスの間隔の上限は、は間隔の上限です。
List accumulatedWeights
重み値は。最初のインスタンスの下限はデフォルトで 0 になります。したがって、上記のインスタンスの重み計算結果に基づいて、各インスタンスの重みの範囲を取得できます。
- 例A:
[0, 220]
- 例B:
(220, 410]
- 例 C:
(410, 560]
- 例 D:
(560, 690)
インスタンスの各間隔の幅が、合計平均応答時間 - インスタンスの平均応答時間であることを確認するのは難しくありません。したがって、インスタンスの平均応答時間が短いほど、重み間隔の幅は大きくなります。重み間隔の幅が大きいほど、選択される確率は高くなります。これらの間隔の境界の開始と終了はどのように決定されるのでしょうか? なぜそんなに定期的ではないのでしょうか?これについては、以下の選択アルゴリズムの例を通じて説明します。
インスタンスの選択
WeightedResponseTimeRule
選択したインスタンスの実装は以前紹介したアルゴリズム構造と同様ですが、そのテーマとなるアルゴリズムは以下の通りです(ループ本体と一部の判定等の処理は省略しています)。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
// get hold of the current reference in case it is changed from the other thread
List<Double> currentWeights = accumulatedWeights;
if (Thread.interrupted()) {
return null;
}
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);
// 如果最后一个实例的权重值小于0.001,则采用父类实现的线型轮询的策略
if (maxTotalWeight < 0.001d) {
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
// 如果最后一个实例的权重值大于等于0.001,就产生一个[0, maxTotalWeight)的随机数
double randomWeight = random.nextDouble() * maxTotalWeight;
int n = 0;
for (Double d : currentWeights) {
// 遍历维护的权重清单,若权重大于等于随机得到的数值,就选择这个实例
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);
}
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Next.
server = null;
}
return server;
}
ソース コードから、インスタンスを選択する中心的なプロセスは 2 つのステップであることがわかります。
-
[0, 最大权重值)
範囲内の乱数を生成します。 -
重みリストを走査し、重み値を乱数と比較します。重み値が乱数以上の場合は、現在の重みリストのインデックス値を使用して、サービス インスタンス リスト内の特定のインスタンスを取得します。
これは、前のセクションで説明したサービス インスタンスが重み間隔に基づいて選択されるという原則であり、重み間隔境界の開閉の原則はアルゴリズムに基づいています。通常、各間隔は の形式になりますが、なぜ最初のインスタンスと最後のインスタンスは異なりますか
(x, y]
? 毛織物ですか? 乱数の最小値は 0 になる可能性があるため、最初のインスタンスの下限は閉区間になりますが、同時に乱数の最大値は最大重み値に達することができないため、最後のインスタンスの上限は閉区間になります。インスタンスは開いた間隔です。
サービス インスタンスを選択するための例として上記のデータを引き続き使用する場合、このメソッドは[0, 690)
間隔から乱数を選択します。たとえば、選択された乱数は 230 です。この値は 2 番目の間隔にあるため、選択されます。インスタンス B がリクエストを行います。
ClientConfigEnabledRoundRobinRule
この戦略は非常に特殊です。特別な処理ロジックが実装されていないため、通常は直接使用しません。以下のソース コードに示すように、内部で戦略が定義されており、choose 関数の実装でも の線形関数が使用されますRoundRobinRule
。RoundRobinRule
ポーリング メカニズムなので、実装される関数は実際には と同じですRoundRobinRule
。
choose
この戦略を直接使用することはありませんが、この戦略を継承することで、スレッド ポーリング メカニズムがデフォルトで実装されます。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");
}
}
}
BestAvailableルール
この戦略は から継承されていますClientConfigEnabledRoundRobinRule
。実装では、ロード バランサーの統計オブジェクトを挿入し、LoadBalancerStats
特定のchoose
アルゴリズムでLoadBalancerStats
保存されたインスタンス統計を使用して、要件を満たすインスタンスを選択します。次のソース コードから、それがトラバースしていることがわかります。バランサに保持されているすべてのサービス インスタンスは、失敗したインスタンスをフィルタリングして、同時リクエスト数が最も少ないインスタンスを見つけるため、この戦略の特徴は、最もアイドル状態のインスタンスを選択することです。
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;
}
}
同時に、アルゴリズムの中心となる基盤は統計オブジェクトであるためloadBalancerStats
、それが空の場合、戦略は実行できません。loadBalancerStats
したがって、ソースコードから、 が空の場合、親クラスで線形ポーリング戦略が採用されることがわかります。 を紹介したようにClientConfigEnabledRoundRobinRule
、そのサブクラスは、高度な戦略を実装するための要件を満たせない場合にそれを使用できます。戦略。この後紹介する戦略も全て を継承しているはずなClientConfigEnabledRoundRobinRule
ので、全てこのような特徴を持つことになります。
述語ベースのルール
これは抽象的な戦略であり、これも継承しています。その名前から、これはコレクションをフィルタリングするためのツールの条件付きインターフェイスである実装に基づいた戦略ClientConfigEnabledRoundRobinRule
であると推測できます。Predicate
Predicate
Google Guava Collection
以下のソース コードに示すように、オブジェクトの実装をgetPredicate
取得するための抽象関数が定義されており、その関数では、関数を通じて特定のサービス インスタンスが選択されます。この関数の名前から、その基本ロジックを大まかに推測できます。まず、サブクラスに実装されたロジックを通じていくつかのサービス インスタンスをフィルタリングし、次に線形ポーリング方式でフィルタリングされたインスタンスのリストから 1 つを選択します。AbstractServerPredicate
choose
AbstractServerPredicate
chooseRoundRobinAfterFiltering
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
で呼び出されるメソッドは、最初にchooseRoundRobinAfterFiltering
内部定義された関数を通じてgetEligibleServers
候補インスタンスのリストを取得します (フィルタリングが実装されています) 。返されたリストが空の場合は、リストが存在しないことを示すために使用されます。それ以外の場合は、リニア ポーリングが使用されます。Optional.absent()
候補リストからインスタンスを取得します。
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {
...
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;
}
}
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()));
}
...
}
getEligibleServers
全体的なロジックを理解した後、フィルタリング機能を実装する関数を詳しく見てみましょう。ソース コードから見ると、その実装構造はシンプルかつ明確であり、サービス リストを走査することにより、このthis.apply
メソッドを使用してインスタンスを保持する必要があるかどうかを判断し、保持する必要がある場合はそのインスタンスを結果リストに追加します。
Google Guava Collections
この定義は AbstractServerPredicate には見つからないため、apply
どのようにフィルタリングを実装するのでしょうか? 実はAbstractServerPredicateはcom.google.common.base.Predicate
インタフェースを実装しており、このインタフェース内にapplyメソッドが定義されており、主にフィルタ条件の判定ロジックを実装するために使用されており、入力するパラメータはフィルタが設定する何らかの情報(ソースコード内の情報など)です。インスタンスnew PredicateKey(loadBalancerKey, server)
に関する統計情報とロードバランサー選択アルゴリズムが渡されるため、この関数はchooseRoundRobinAfterFiltering
「最初にリストをフィルターし、次に選択のためにポーリングする」というテンプレート戦略を定義するだけです。フィルタリングの方法については、AbstractServerPredicate
サブクラスにメソッドを実装して、apply
特定のフィルタリング戦略を決定する必要があります。
後で紹介する 2 つの戦略は、この抽象的な戦略に基づいて実装されていますが、異なるPredicate
実装を使用してフィルタリング ロジックを完成させ、異なるインスタンス選択効果を実現します。
Google Guava Collections
は、 を強化および拡張するオープンソース プロジェクトですJava Collections Framework
。ほとんどの場合、コレクションを使用するための要件を満たすことができますがJava Collections Framework
、特殊な状況が発生した場合、コードが冗長になり、エラーが発生しやすくなります。Guava Collections
これは、コレクションのオペレーション コードをより短く簡潔にし、コードの可読性を大幅に向上させるのに役立ちます。
可用性フィルタリングルール
この戦略は、上で紹介した抽象戦略を継承しているPredicateBasedRule
ため、「最初にリストをフィルターし、次に選択をポーリングする」という基本的な処理ロジックも継承しており、フィルター条件が使用されますAvailabilityPredicate
。
public class AvailabilityPredicate extends AbstractServerPredicate {
...
@Override
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
上記のソース コードから、メインのフィルタリング ロジックがメソッド内にあり、主にサービス インスタンスの 2 つのコンテンツを決定することがわかります。
- それは障害、つまりサーキットブレーカーが作動していて切断されているかどうかです。
- この例の同時リクエストの数はしきい値を超えています。デフォルトは です
2的32次幂 - 1
。この構成は<clientName>.<nameSpace>.ActiveConnectionsLimit
パラメータを使用して変更できます。
これら 2 つの項目のいずれかが満たされていればapply
それが返されますfalse
(ノードに障害がある可能性がある、または負荷が高すぎることを示します)。どちらも満たされない場合は true を返します。
この戦略では、上記のフィルター方法の実装に加えて、choose
戦略に対していくつかの改善と最適化も行われているため、親クラスの実装はそのバックアップ オプションにすぎず、その具体的な実装は次のとおりです。
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);
}
ご覧のとおり、親クラスのように、最初にすべてのノードを走査してフィルター処理を行ってから、フィルター処理されたコレクション内のインスタンスを選択するわけではありません。代わりに、最初にインスタンスを直線的に選択します。次に、フィルタリング条件を使用して、インスタンスが要件を満たしているかどうかを判断します。グループ化が遅い場合は、インスタンスを直接使用します。要件を満たしていない場合は、次のインスタンスを選択して、要件を満たしているかどうかを確認します。このサイクルは継続します。このプロセスを 10 回繰り返します。それでも要件を満たすインスタンスが見つからない場合は、親クラスの実装計画が使用されます。
簡単に言うと、この戦略は線形抽象化を通じて使用可能なアイドル状態のインスタンスを直接見つけようとし、毎回すべての例を走査する親クラスのオーバーヘッドを最適化します。
ゾーン回避ルール
この戦略については、複雑なイコライザーを紹介するときにZoneAvoidanceRule
すでに説明しましたが、 のPredicateBasedRule
特定の実装クラスでもあります。前回の紹介では、ゾーンの地域ZoneAvoidanceRule
戦略を選択するために使用されるいくつかの静的関数 ( など)に主に焦点を当てましたcreateSnapshot
。getAvailableZones
ZoneAvoidanceRule
ここでは、これがサービス インスタンスのフィルター条件としてどのように実装されるかを詳しく見ていきます。以下のソース コードの判断からわかるようにZoneAvoidanceRule
、CompositePredicate
サービス インスタンスのリストをフィルタリングするために使用されます。これは結合フィルタ条件です。そのコンストラクタでは、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 は実装時にそのような最適化のための関数をAvailabilityFilteringRule
書き換えなかったので、「最初にライトをフィルターし、次に選択のためにポーリングする」という親クラスのメインのフィルター ロジックに完全に従いました。choose
このうち、明度をフィルタリングする条件は、前述ZoneAvoidancePredicate
のAvailabilityPredicate
フィルタリング条件を組み合わせたもので、 が主フィルタ条件、 が副フィルタ条件ですCompositePredicate
。ソース コード スニペットから、メイン フィルター条件と一連のセカンダリ フィルター条件リストをCompositePredicate
定義していることがわかります。そのため、複数のセカンダリ フィルター リストを持つことができ、ストレージを使用するため、セカンダリ フィルター条件は順次実行されることに基づいています。AbstractServerPredicate delegate
List<AbstractServerPredicate> fallbacks
List
public class CompositePredicate extends AbstractServerPredicate {
private AbstractServerPredicate delegate;
private List<AbstractServerPredicate> fallbacks = Lists.newArrayList();
private int minimalFilteredServers = 1;
private float minimalFilteredPercentage = 0;
@Override
public boolean apply(@Nullable PredicateKey input) {
return delegate.apply(input);
}
...
/**
* 从主过滤条件获取筛选过的服务器,如果筛选过的服务器达不到指定要求,则用次过滤条件进行筛选
*/
@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;
}
}
フィルタリング結果を取得する実装関数のgetEligibleServers
処理ロジックは以下のとおりです。
- メインフィルターを使用してすべてのインスタンスをフィルターし、フィルターされたインスタンスのリストを返します。
- フィルター リストのフィルター条件を順番に使用して、すべてのインスタンスをフィルターします。
- 各フィルタリング(一次フィルタ条件と二次フィルタ条件を含む)の後に、次の 2 つの条件を判定する必要があり、どちらかの要件が満たされない限り、次のフィルタ条件を使用してフィルタリングが行われ、最終的に 2 つの条件を満たす結果が得られます。条件は線形ポーリング アルゴリズムの選択に返されます。
- フィルタリング後のインスタンスの合計数 >= 最小フィルタリングはメンバーシップ (
minimalFilteredSevers
、デフォルトは 1) です。 - フィルタリングされたインスタンスの割合 > フィルタリングされた最小割合 (
minimalFilteredPercentage
、デフォルトは 0)。
- フィルタリング後のインスタンスの合計数 >= 最小フィルタリングはメンバーシップ (