@[TOC]
一、前言
前置Ribbon相关文章:
我们聊了以下问题:
- 为什么给RestTemplate类上加上了@LoadBalanced注解就可以使用Ribbon的负载均衡?
- SpringCloud是如何集成Ribbon的?
- Ribbon如何作用到RestTemplate上的?
- 如何获取到Ribbon的ILoadBalancer?
- ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表?
- ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息?
- 如何根据负载均衡器
ILoadBalancer
从Eureka Client获取到的List<Server>
中选出一个Server?- Ribbon如何发送网络HTTP请求?
- Ribbon如何用IPing机制动态检查服务实例是否存活?
- Ribbon负载均衡策略之随机(
RandomRule
)、轮询(RoundRobinRule
)、重试(RetryRule
)实现方式;
本文继续讨论 最佳可用规则(BestAvailableRule
)是如何实现的?
PS:Ribbon依赖Spring Cloud版本信息如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
二、BestAvailableRule
BestAvailableRule会逐个考察Server,如果Server被tripped了,则跳过;最终选择一个并发请求量最小的Server。
1、负载规则
我们知道Ribbon负载均衡算法体现在IRule的choose(Object key)方法中,所以看BestAvailableRule的choose(Object key)方法:
详细代码注释如下:
public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
// 维护了服务实例的一些状态信息
private LoadBalancerStats loadBalancerStats;
@Override
public Server choose(Object key) {
// 如果服务实例状态信息为空,则直接使用父类的choose()方法,采用RoundRobin算法
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);
// 如果服务实例被tripped了,则直接跳过当前服务实例
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
// 获取实例的并发数(当且仅当 当前时间与上次有效更改连接数的时间间隔在指定范围之内(默认10分钟))
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
// 找到并发连接最小的那个服务实例
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
// 如果遍历完所有的服务实例之后,还没有找到server,则调用父类的choose()方法,用RoundRobin算法进行选择。
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
super.setLoadBalancer(lb);
if (lb instanceof AbstractLoadBalancer) {
loadBalancerStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats();
}
}
}
方法的核心逻辑:
- 首先判断如果服务实例状态信息为空,则直接使用父类的choose()方法,采用RoundRobin算法。
- 否则:从BestAvailableRule所属的ILoadBalancer中获取服务的所有实例,记录当前时间;
- 遍历服务的每个实例,获取实例的
ServerStats
,如果实例被tripped了,则直接跳过当前服务实例;否则,获取实例的并发数(这里当且仅当 当前时间与上次有效更改连接数的时间间隔在指定范围之内(默认10分钟)),如果超过了时间范围则返回0
。循环结束后返回并发数最小的第一个实例。- 最后,如果遍历完所有的服务实例之后,还没有得到Server,则调用其父类的choose()方法,使用RoundRobin算法选择一个实例。
下面我们接着看几个细节点:如何判断服务实例被tripped?如何获取服务实例的并发数?
2、如何判断服务实例被tripped?
逻辑体现在ServerStats
的isCircuitBreakerTripped(long currentTime)
方法中:
public boolean isCircuitBreakerTripped(long currentTime) {
// 获取断路器超时时间点
long circuitBreakerTimeout = getCircuitBreakerTimeout();
// 如果断路器超时时间点 <= 0,则直接返回false。
if (circuitBreakerTimeout <= 0) {
return false;
}
// 如果断路器超时时间点 > 当前时间,则返回true,表示服务实例被tripped了;否则返回false
return circuitBreakerTimeout > currentTime;
}
方法核心逻辑:
- 判断断路器超时时间点是否大于当前时间,如果大于,则表示当前服务实例被tripped了,也就不会再被选择;否者,正常选择。
3、如何获取服务实例的并发数?
逻辑体现在ServerStats
的getActiveRequestsCount(long currentTime)
方法中:
public int getActiveRequestsCount(long currentTime) {
// 获取实例当前的并发连接数
int count = activeRequestsCount.get();
// 连接数为0,则直接返回0
if (count == 0) {
return 0;
// 如果当前时间与上次有效更改连接数的时间间隔不在指定范围之内(默认10分钟),则并发连接数设置为0,并返回0
} else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
activeRequestsCount.set(0);
return 0;
} else {
// 正常场景下返回并发连接数
return count;
}
}
AtomicInteger activeRequestsCount = new AtomicInteger(0);
private static final DynamicIntProperty activeRequestsCountTimeout =
DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);
关键点在于实例的并发数是如何维护的?下面我就接着看。
4、实例并发数的维护:
1)增加实例的并发数
在开始执行一个Rest请求时会通过ServerStats#incrementActiveRequestsCount()
方法新增一个连接数(activeRequestsCount
);
虽然是在new一个RibbonStatsRecorder
时新增的实例并发数,但是RibbonStatsRecorder内部组合的ServerStats来源于Ribbo的上下文RibbonLoadBalancerContext
,所以每次new RibbonStatsRecorder
时,ServerStats数据是共享的;
2)减少实例的并发数
当Rest请求调用外部服务执行完毕之后,会通过ServerStats#decrementActiveRequestsCount()
方法减少一个连接数(activeRequestsCount
):
RibbonStatsRecorder#recordStats(Object entity) 方法如下:
三、后续文章
下一篇文章我们接着分析Ribbon负载均衡策略之WeightedResponseTimeRule。