Spring Cloud Ribbon负载均衡策略(IRule接口)

Ribbon负载均衡策略

在这里插入图片描述
可以看到 Ribbon 中实现了非常多的选择策略,下面来详细解读一下 IRule 接口的各个实现。

AbstractLoadBalancerRule

负载均衡的策略的抽象类,在该抽象类中定义了负载均衡器 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;
    }      
}

RandomRule

该策略实现了从服务实力清单中随机选择一个服务实例的功能。

@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

RoundRobinRule

该策略实现了按照线型轮询的方式一次选择每个服务实例的功能。

其详细结构与 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;
}

RetryRule

该策略实现了一个具备重试机制的实例选择功能。从下边的实现中我们可以看到,在其内部还定义了一个 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 的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果,他的实现主要有三个核心内容。

定时任务

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

}

该函数的实现主要分为两个步骤:

  • 根据 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 中的权重值,其中第一个实例的下限默认为零。所以,根据上面实例的权重计算结果,我们可以得到每个实例的权重区间。

  • 实例 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;
}

从源码中我们可以看到,选择实例的核心过程就两步:

  • 生成一个 [0, 最大权重值) 区间内的随机数。

  • 遍历权重列表,比较权重值与随机数的大小,如果权重值大于等于随机数,就拿当前权重列表的索引值去服务实例列表中获取具体的实例。

    这就是在上一节中提到的服务实例会根据权重区间挑选的原理,而权重区间边界的开闭原则根据算法,正常每个区间为 (x, y] 的形式,但是第一个实例和最后一个实例为什么不同呢?由于随机数的最小值可以为0,所以第一个实例的下限是闭区间,同时随机数的最大值娶不到最大权重值,所以最后一个实例的上线是开区间。

若继续以上面的数据为例进行服务实例的选择,则该方法会从 [0, 690) 区间中选出一个随机数,比如选出的随机数为230,由于该值位于第二个区间,所以此时就会选择实例B来进行请求。

ClientConfigEnabledRoundRobinRule

该策略较为特殊,我们一般不直接使用它,因为他本身并没有实现什么特殊的处理逻辑,正如下面的源码所示,在它的内部定义了一个 RoundRobinRule 策略,而 choose 函数的实现也正是使用了 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");
        }
    }

}

BestAvailableRule

该策略继承自 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,所以他们都会具有这样的特性。

PredicateBasedRule

这是一个抽象策略,它也继承了 ClientConfigEnabledRoundRobinRule,从其命名中可以猜出这是一个基于 Predicate 实现的策略,PredicateGoogle Guava Collection 工具对集合进行过滤的条件接口。

如下面的源码所示,它定义了一个抽象函数 getPredicate 来获取 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 集合工具的朋友可能会感到困惑,这个 apply 在 AbstractServerPredicate 中找不到它的定义,那么它是如何实现过滤的呢?实际上,AbstractServerPredicate 实现了 com.google.common.base.Predicate 接口,而 apply 方法是该接口中的定义,主要用来实现过滤条件的判断逻辑,它输入的参数则是过滤条件需要用到的一些信息(比如源码中的 new PredicateKey(loadBalancerKey, server)),它传入了关于实例的统计信息和负载均衡器的选择算法,所以这里的 chooseRoundRobinAfterFiltering 函数只是定义了一个模板策略:“先过滤清单,再轮询选择”。对于如何过滤,需要我们在 AbstractServerPredicate 的子类中实现 apply 方法来确定具体的过滤策略。

后面我们将要介绍的两个策略就是基于此抽象策略实现,只是它们使用了不同的 Predicate 实现来完成过滤逻辑以达到不同的实例选择效果。

Google Guava Collections 是一个对 Java Collections Framework 增强和扩展的开源项目。虽然 Java Collections Framework 已经能够满足我们大多数情况下使用集合的要求,但是当遇到一些特殊的情况时我们的代码会比较冗长且容易出错。Guava Collections 可以帮助我们让集合操作代码更为简短精炼并大大增强代码的可读性。

AvailabilityFilteringRule

该策略继承自上边介绍的抽象策略 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的32次幂 - 1,该配置可通过参数 <clientName>.<nameSpace>.ActiveConnectionsLimit 来修改。

这两项内容中只要有一个满足 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

该策略我们在介绍复杂均衡器 ZoneAvoidanceRule 时已经提到过,它也是 PredicateBasedRule 的具体实现类。在之前的介绍中主要针对 ZoneAvoidanceRule 中用于选择 Zone 区域策略的一些静态函数,比如 createSnapshotgetAvailableZones

在这里我们将详细看看 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 中,他的处理逻辑如下所示:

  • 使用主过滤条件对所有实例过滤并返回过滤后的实例清单。
  • 依次使用次过滤条件列表中的过滤条件对所有实例进行过滤。
  • 每次过滤之后(包括主过滤条件和次过滤条件),都需要判断下面两个条件,只要有一个要求不符合就继续使用下一个过滤条件进行过滤,将最终符合两个条件的结果返回供线性轮询算法选择:
    • 过滤后的实例总数 >= 最小过滤是隶属(minimalFilteredSevers,默认为 1)。
    • 过滤后的实例比例 > 最小过滤百分比(minimalFilteredPercentage,默认为 0)。
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值