BestAvailableRule 负载均衡策略会选择当前并发请求量最小的服务实例进行调用。
在运行过程中,BestAvailableRule 会持续监控各个服务实例的并发请求数量。当有新的请求到来时,它会遍历所有可用的服务实例,通过某种机制获取每个实例正在处理的请求数量信息,然后从中挑选出并发请求量最小的那个实例。
动态选择:该策略不是基于固定的规则进行选择,而是根据每个实例的实时并发情况动态地选择最佳实例。
响应速度快:由于选择的是并发量最小的实例,通常意味着该实例的负载较轻,能够更迅速地处理新的请求,从而减少请求的响应时间。
资源合理分配:有助于将请求压力更均匀地分配到各个服务实例上(因为它会选择最闲的服务器进行请求),避免某些实例负载过高而其他实例闲置的情况,实现系统资源的高效利用。
并发感知:该策略能够感知到每个服务实例的并发请求数量,以此作为选择的依据。
在 application.properties 中,使用如下配置:
user.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.BestAvailableRule
将在名为 user 的客户端上开启最低并发策略。

Ribbon 最低并发策略通过 com.netflix.loadbalancer.BestAvailableRule 类实现,代码如下:
public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
// 用于收集和维护负载均衡器的统计信息
private LoadBalancerStats loadBalancerStats;
public BestAvailableRule() {
}
// 选择最佳服务器
public Server choose(Object key) {
if (this.loadBalancerStats == null) {
// 调用父类,父类采用轮询方式
return super.choose(key);
} else {
// 从负载均衡器中获取所有可用的服务实例列表
List<Server> serverList = this.getLoadBalancer().getAllServers();
// 初始化一个整数变量,表示最小的并发连接数
// 其初始化为整数的最大值,以便在后续的遍历中能够找到更小的值进行更新
int minimalConcurrentConnections = Integer.MAX_VALUE;
// 获取当前的时间戳,用于后续判断服务实例的断路器状态
long currentTime = System.currentTimeMillis();
// 用于存储最终选择的服务实例
Server chosen = null;
// 使用迭代器遍历服务实例列表
Iterator var7 = serverList.iterator();
while(var7.hasNext()) {
Server server = (Server)var7.next();
// 通过LoadBalancerStats类获取当前服务实例的统计信息。
// 统计信息可能包括该实例的并发连接数、成功请求数、失败请求数等
ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
// 判断当前服务实例的断路器是否跳闸。如果没有跳闸,则继续考虑该实例作为候选;
// 如果跳闸了,则跳过该实例,继续下一个实例的判断
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
// 获取当前服务实例的并发连接数
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
// 判断是否为最小的并发数
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
// 如果没有选择到服务器,则调用父类 choose 方法选择
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
}
//...
}最低并发策略父类 ClientConfigEnabledRoundRobinRule 的部分代码如下:
public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {
// 默认采用轮询策略
RoundRobinRule roundRobinRule = new RoundRobinRule();
public ClientConfigEnabledRoundRobinRule() {
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 默认采用轮询策略
this.roundRobinRule = new RoundRobinRule();
}
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
this.roundRobinRule.setLoadBalancer(lb);
}
public Server choose(Object key) {
if (this.roundRobinRule != null) {
// 通过轮询策略选择服务器
return this.roundRobinRule.choose(key);
} else {
throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
}
}
}判断断路器是否跳闸(isCircuitBreakerTripped)的代码见 ServerStats 类:
public boolean isCircuitBreakerTripped(long currentTime) {
// 跳闸超时时间
long circuitBreakerTimeout = this.getCircuitBreakerTimeout();
if (circuitBreakerTimeout <= 0L) {
return false;
} else {
// 如果当前时间还在跳闸超时时间内,则认为跳闸
return circuitBreakerTimeout > currentTime;
}
}
// 用于计算断路器的超时时间
private long getCircuitBreakerTimeout() {
long blackOutPeriod = this.getCircuitBreakerBlackoutPeriod();
// lastConnectionFailedTimestamp 表示上次连接失败的时间戳
// blackOutPeriod 表示跳闸的窗口大小,如:30秒
// 断路器从最后一次失败开始,跳闸30秒
return blackOutPeriod <= 0L ? 0L : this.lastConnectionFailedTimestamp + blackOutPeriod;
}通过这种方式,Ribbon 可以在服务出现问题时,根据设定的断路器规则,自动判断是否应该阻止对特定服务实例的请求,从而提高系统的稳定性和可靠性。同时,这种机制也可以让系统在服务恢复正常后,自动解除断路器的限制,继续向该服务实例发送请求。
提高响应速度:选择并发请求量最小的实例通常意味着该实例的负载较轻,能够更快地处理请求,从而提高整体的响应速度。
优化资源利用:可以有效地将请求分配到负载较轻的服务实例上,避免某些实例负载过高而其他实例闲置的情况,优化了系统资源的利用。
统计准确性问题:准确统计每个服务实例的并发请求数量可能存在一定的难度,特别是在高并发的情况下,可能会出现统计不准确的情况。
额外开销:为了统计并发请求数量,需要额外的资源和计算开销。
对响应时间要求较高的系统:例如实时交易系统、在线游戏等,需要快速响应客户请求,BestAvailableRule 策略可以帮助选择负载较轻的服务实例,提高响应速度。
资源有限的环境:当系统的资源有限,不能承受过高的负载时,可以使用该策略将请求分配到负载较轻的实例上,以确保系统的稳定性。
服务实例性能差异较大的场景:如果服务实例的性能差异较大,BestAvailableRule 可以选择性能较好的实例进行调用,提高系统的整体性能。