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 可以选择性能较好的实例进行调用,提高系统的整体性能。