上一章我们已经分析了,RestTemplate 添加了相关的拦截器,使其具有了 负载均衡的能力,而添加拦截的实现便是在 LoadBalancerAutoConfiguration里面实现的,相关的代码如下,并增加了相关的 注释
@Configuration
@ConditionalOnClass(RestTemplate.class) // 依赖 RestTemplate
@ConditionalOnBean(LoadBalancerClient.class) // 需要提前注入 LoadBalancerClient 的Bean ,这里 需要 提前 注入配置类 RibbonAutoConfiguration
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 这里是 重点,重中之重,这里 是 注入 所有带有 @LoadBalanced 的 RestTemplate
// 这是由于 @LoadBalanced 里面加入了 @Qualifier ,这个是关键
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
// 这里是等待其他的Bean 都注入完成之后,再运行afterSingletonsInstantiated
// 里面其实就是加入拦截器
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
//LoadBalancerRequestTransformer接口
// 没有任何实现类
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
//这个是一个 RequestFactory ,用于createRequest()新建一个LoadBalancerRequest
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
// 这个是在没有重试机制下注入的,看一下 ConditionalOnMissingClass,重试的后面也会讲到
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 这里就是 用 LoadBalancerRequestFactory 和 loadBalancerClient 构建一个 LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
// 这里就是 添加拦截器,等会 上面会调用到这里,对 RestTemplate进行添加拦截器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
上面的代码都加了一些备注, 总结一下
⑴存在RestTemplate.class
⑵需要提前注入 LoadBalancerClient 的Bean ,这里 需要 提前 注入配置类 RibbonAutoConfiguration,如下图, RibbonAutoConfiguration 上面也提升 了 需要在 LoadBalancerAutoConfiguration.class 之前注入,完全吻合.
此外,LoadBalancerClient 的唯一实现类 是 RibbonLoadBalancerClient
LoadBalancerAutoConfiguration 里面的注入:
LoadBalancerAutoConfiguration 这个自动配置类 注入完成之后,就会将 带有 @LoadBalanced 注解的 RestTemplate 具有负载均衡能力了.
上面已经分析了 RestTemplate 具有了 负载均衡的能力了,接下来我们看一下具体的拦截器 如何实现 负载均衡的.,首先看一下代码如下:
从上面源码可以看出 :
接下来我们分析一下这个 LoadBalancerClient ,这个是 客户端的负载均衡,只有一个唯一的实现类(RibbonLoadBalancerClient),我们看一下 LoadBalancerClient 有哪些接口,如下:
public interface ServiceInstanceChooser {
// 根据传入的serviceId,从负载均衡器中选一个对应服务的实例
ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
// 根据传入的serviceId,负载均衡选择一个实例,然后执行请求
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
// 重新构造url:把url中原来写的服务名(serverName) 换掉,变成 Ip:port
URI reconstructURI(ServiceInstance instance, URI original);
}
从这里 我们也可以看出 一共有以下步骤:
上面说到 第一步就是选择负载均衡器ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
先看一下这个接口提供了哪些方法,如下:
public interface ILoadBalancer {
// 添加服务器
public void addServers(List<Server> newServers);
// 通过 key ,从负载均衡器里面 选择一个 服务器
public Server chooseServer(Object key);
//标识通知此server 已经下线,不然负载均衡器会一直认为还活跃,直到再次PING
public void markServerDown(Server server);
// 这个方法已经废弃
@Deprecated
public List<Server> getServerList(boolean availableOnly);
//获取所有的活跃的服务器
public List<Server> getReachableServers();
//获取所有的 服务器,活跃和不活跃的
public List<Server> getAllServers();
}
再看一下 这个接口被哪些类实现了,如下:
可以看出 基础实现类是 BaseLoadBalancer 类,DynamicServerListLoadBalancer 又在其基础上进行了扩展,ZoneAwareLoadBalancer 又继承了 DynamicServerListLoadBalancer 类,又做了一定的改进.
在分析 ILoadBalancer 具体的属性和 方法之前,我们先确认一下 ,默认的 注入的是哪个实现类
提前说一下 ,是在 RibbonClientConfiguration.class这个类里面进行Bean 配置的,如下:
这个Bean 依赖的 属性比较多,我们列一下并对其解释一下,这些属性都是在RibbonClientConfiguration 进行Bean 配置 ,这个是在没有和 Eureka 结合使用的情况下,如果 使用了 Euraka ,那么可能有一些不同,具体配置如下:
名称 | 默认实现 | 结合Eureka 时配置 | 解释 |
---|---|---|---|
IClientConfig | DefaultClientConfigImpl | DefaultClientConfigImpl | 定义各种API用于初始化客户端或负载平衡器以及执行方法的客户端配置。 |
ServerList | ConfigurationBasedServerList(基于配置) | DomainExtractingServerList,里面是DiscoveryEnabledNIWSServerList | 定义用于获取服务器列表的方法的接口 |
ServerListFilter | ZonePreferenceServerListFilter | ZonePreferenceServerListFilter | 对ServerList 实例列表的过滤逻辑处理 |
IRule | ZoneAvoidanceRule | ZoneAvoidanceRule | 负载均衡选择Server 的规则 |
IPing | DummyPing | NIWSDiscoveryPing | 检验服务是否可用的方法实现 |
ServerListUpdater | PollingServerListUpdater | PollingServerListUpdater | 对ServerList 更新的操作 |
我们是如何定位到 RibbonClientConfiguration.class ,并知道这些是在这个Bean 配置类里面实现的,流程如下:
在 注入SpringClientFactory 的时候,指定了 defaultConfigType = RibbonClientConfiguration.class
所以 在获取ILoadBanacer 时如下:
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
一步步点击了代码看了一下,到最后如下:
这里便是注入 RibbonClientConfiguration这个Bean的配置类
下面继续 说下 实现类里面的方法:
**AbstractLoadBalancer 类: 这个是ILoadBalancer 的 抽象类,里面
public abstract class AbstractLoadBalancer implements ILoadBalancer {
//增加了一个服务实例分组枚举
public enum ServerGroup{
ALL, //所有服务
STATUS_UP, //正常服务
STATUS_NOT_UP // 不正常服务
}
// 调用 chooseServer 方法,入参 null
public Server chooseServer() {
return chooseServer(null);
}
//根据 服务器分组类型 获取 不同的服务器集合
public abstract List<Server> getServerList(ServerGroup serverGroup);
//获取与LoadBalancer相关的统计信息
//LoadBalancerStats 这个类比较重要,后面的服务器选择策略里面都有涉及到,后面详细讲
public abstract LoadBalancerStats getLoadBalancerStats();
}
** BaseLoadBalancer 类:这个类是 默认实现,首先我们看一下有哪些属性和方法:
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener, IClientConfigAware {
// 默认的负载均衡 策略 是 轮休
private final static IRule DEFAULT_RULE = new RoundRobinRule();
// 具体的执行Ping 的策略对象,默认是 依次轮休,量大 效率比较低
private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
private static final String DEFAULT_NAME = "default";
private static final String PREFIX = "LoadBalancer_";
protected IRule rule = DEFAULT_RULE;
protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
protected IPing ping = null;
// 定义了一个 所有服务的 集合
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
// 定义了一个 更新服务的 集合
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections
.synchronizedList(new ArrayList<Server>());
// 定义了 读写锁
protected ReadWriteLock allServerLock = new ReentrantReadWriteLock();
protected ReadWriteLock upServerLock = new ReentrantReadWriteLock();
protected String name = DEFAULT_NAME;
// 定义了一个 Timer ,每隔10S 定时运行一次
protected Timer lbTimer = null;
protected int pingIntervalSeconds = 10;
protected int maxTotalPingTimeSeconds = 5;
protected Comparator<Server> serverComparator = new ServerComparator();
protected AtomicBoolean pingInProgress = new AtomicBoolean(false);
// 定义了一个 LoadBalancerStats 统计相关信息
protected LoadBalancerStats lbStats;
private volatile Counter counter = Monitors.newCounter("LoadBalancer_ChooseServer");
private PrimeConnections primeConnections;
private volatile boolean enablePrimingConnections = false;
private IClientConfig config;
// 服务器列表变更监听
private List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<ServerListChangeListener>();
//服务器状态变更通知(具体的实现类 没有找到)
private List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<ServerStatusChangeListener>();
}
1 负载均衡策略 IRule 默认初始化了RoundRobinRule();
2. 具体执行策略 SerialPingStrategy 是一个 静态类,备注明确指出当量大 时效果不理想,里面逻辑也是一个 for 循环
3. 定义了一个 存储所有服务器实例的 集合allServerList
4. 定义了一个 存储 正常服务器实例的集合upServerList
5. 定义了一个 收集统计数据的LoadBalancerStats
6. 定义了要给 Timer 用于每隔10S去运行一次 定时任务
7. 还定义了一些其他的辅助属性,如读写锁,服务器列表监听,服务器状态监听(serverStatusListeners ), 这个状态监听 具体的实现类 没有找到,但是不影响整个流程
此外BaseLoadBalancer 还 涉及 PrimeConnections ,这个 主要是由 enablePrimingConnections 控制,
PrimeConnections 主要就是 对可用服务器列表 异步连接测试一下,做一些统计,感觉没啥用,具体的逻辑
在 PrimeConnections.class 的 primeConnectionsAsync 方法:
BaseLoadBalancer 构造函数里面 定义了一个 定时任务,每隔10S都会去 定时检测一些服务器 状态,当规则为DummyPing,是不会运行的,具体介绍在下一个小小节
public BaseLoadBalancer() {
this.name = DEFAULT_NAME;
this.ping = null;
setRule(DEFAULT_RULE);
setupPingTask();
lbStats = new LoadBalancerStats(DEFAULT_NAME);
}
这里就是主要就是 更新 upServerList 正常活跃的 服务类集合,代码如下:
void setupPingTask() {
// 判断是否可以跳过
if (canSkipPing()) {
return;
}
// 先将上一次的取消
if (lbTimer != null) {
lbTimer.cancel();
}
// 重新 new ShutdownEnabledTimer
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
// 运行,每隔10秒运行
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
// 再次强制运行一次, 可能是作者考虑 上面是 异步,没有及时,不知道,反正 感觉这里有点多余
forceQuickPing();
}
class PingTask extends TimerTask {
public void run() {
try {
// 这里会 继续 根据 IPing逻辑去校验
new Pinger(pingStrategy).runPinger();
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error pinging", name, e);
}
}
}
大致流程如下:
1 . 通过 canSkipPing() 判断是否可以跳过,ping == null || ping 是 DummyPing 类型,直接 返回
2. 先将上一次的取消,然后再重新new ,这种做法比较好
3. 然后开始运行,每隔10 S去 检测一些 服务器 的运行状态,是否UP
4. 这里 就调用了 pingerStrategy ,这里是 昨个服务器去 Check
5. Check 时 ,是调用 Ping 的具体实现isAlive() ,使用了 Eurka 的,就是走的NIWSDiscoveryPing 这个里面的方法
这里顺带说一下 IPING 的代码结构,只有一个方法 isAlive() ,逻辑都比较简单,就不多提了。
public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
// 服务器信息集合
volatile ServerList<T> serverListImpl;
// 对服务器过滤 集合
volatile ServerListFilter<T> filter;
// 服务器更新具体执行实现
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
//对服务器更新
protected volatile ServerListUpdater serverListUpdater;
}
上面是一些主要属性,我们就一个一个 的看一下
public interface ServerList<T extends Server> {
// 获取初始服务器列表
public List<T> getInitialListOfServers();
// 获取更新的服务器列表
public List<T> getUpdatedListOfServers();
}
一共就2个方法,我们在看一下具体的实现类
AbstractServerList 为 其抽象类, 里面又增加了一个 方法
getFilterImpl(IClientConfig niwsClientConfig) — 这里就是 获取一个 ServerListFilter 实例 ,默认是ZoneAffinityServerListFilter.class
DomainExtractingServerList 里面的属性
public class DomainExtractingServerList implements ServerList<DiscoveryEnabledServer> {
// 这里 又定义了一个 ServerList 类型
// 如果 是没有Eureka ,这里的类型是:ConfigurationBasedServerList
// 如果 配合 Eureka ,这里的是 : DiscoveryEnabledNIWSServerList
private ServerList<DiscoveryEnabledServer> list;
// 定义了一个 IClientConfig
private IClientConfig clientConfig;
private boolean approximateZoneFromHostname;
public DomainExtractingServerList(ServerList<DiscoveryEnabledServer> list,
IClientConfig clientConfig, boolean approximateZoneFromHostname) {
this.list = list;
this.clientConfig = clientConfig;
this.approximateZoneFromHostname = approximateZoneFromHostname;
}
}
getInitialListOfServers() 和 getUpdatedListOfServers() 这两个方法都是 返回里面的 list 对应的方法,这里的list 如果 是没有Eureka ,这里的类型是:ConfigurationBasedServerList ,如果 配合 Eureka ,这里的是 : DiscoveryEnabledNIWSServerList , ConfigurationBasedServerList ,返回的结果详见 对应的实现类的分析
private List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) {
List<DiscoveryEnabledServer> result = new ArrayList<>();
boolean isSecure = this.clientConfig.getPropertyAsBoolean(
CommonClientConfigKey.IsSecure, Boolean.TRUE);
boolean shouldUseIpAddr = this.clientConfig.getPropertyAsBoolean(
CommonClientConfigKey.UseIPAddrForServer, Boolean.FALSE);
for (DiscoveryEnabledServer server : servers) {
// 将获得的servers 进一步扩展封装成DomainExtractingServer 类型
result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr,
this.approximateZoneFromHostname));
}
return result;
}
这个方法就是将 servers 扩展为 DomainExtractingServer ,增加了一些额外的属性
ConfigurationBasedServerList 的 getInitialListOfServers() 和 getUpdatedListOfServers() 两个方法都是返回 listOfServers 里面的配置 的服务器列表
DiscoveryEnabledNIWSServerList 的getInitialListOfServers() 和 方法 getUpdatedListOfServers() 都是 调用 obtainServersViaDiscovery() ,我们分析一下 ,主要就是:
接口 ServerListFilter 只有一个 方法 ,即
public List<T> getFilteredListOfServers(List<T> servers);
看一下 相关的实现结构:
①AbstractServerListFilter 是其抽象类,额外增加了一个 负载均衡统计LoadBalancerStats
②ZoneAffinityServerListFilter 继承了AbstractServerListFilter ,这个过滤器就是主要 根据是否需要 “区域 亲和(Zone Affinity)” 来进行过滤,将服务器实例 所属的Zone 与调用者自身的所处区域(Zone)进行比较 ,过滤掉那些不是同处一个区域的实例。
@Override
public List<T> getFilteredListOfServers(List<T> servers) {
// 需要满足 servers 列表个数>1 ,并且 开启了zoneAffinity 或者zoneExclusive
if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){
// 这里进行 第一步的 过滤
List<T> filteredServers = Lists.newArrayList(Iterables.filter(
servers, this.zoneAffinityPredicate.getServerOnlyPredicate()));
// 这里继续判断 是否正在需要开启 Zone 过滤
if (shouldEnableZoneAffinity(filteredServers)) {
return filteredServers;
} else if (zoneAffinity) {
overrideCounter.increment();
}
}
return servers;
}
上面方法主要通过Iterables.filter(servers, this.zoneAffinityPredicate.getServerOnlyPredicate()) ,进行过滤,这里就是 将 所有服务器的Zone 和调用者的对比,过滤掉 不一致的.
过滤完了,还需要 继续判断是否 需要启用“区域 亲和(Zone Affinity)” ,具体逻辑在 shouldEnableZoneAffinity(filteredServers)里面,这里 有比较多的 计算指标,不要急,慢慢渗入,如下:
Ⅰ 创建快照,里面的详细逻辑 见下一个方法的解释
Ⅱ 获取对应的服务器负载情况 和服务器的总数量、不能正常使用的服务器数量
Ⅲ 判断是否满足条件,这里有三个指标:
1. 不能用的服务器数量 / 总的数量 >0.8
2. 可用服务器上平均负载 >= 0.6
3. 可用服务器数(总的服务器 - 暂停 的服务器数量)<2
满足上面 任何一个,就不开启 区域 亲和(Zone Affinity)
private boolean shouldEnableZoneAffinity(List<T> filtered) {
if (!zoneAffinity && !zoneExclusive) {
return false;
}
if (zoneExclusive) {
return true;
}
LoadBalancerStats stats = getLoadBalancerStats();
if (stats == null) {
return zoneAffinity;
} else {
logger.debug("Determining if zone affinity should be enabled with given server list: {}", filtered);
// 创建快照,里面的详细逻辑 见下一个方法
ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered);
// 获取对应的服务器负载情况
double loadPerServer = snapshot.getLoadPerServer();
// 获取服务器数量
int instanceCount = snapshot.getInstanceCount();
// 不能用的服务器数量
int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount();
/**
这里一共三个指标:
1. 不能用的服务器数量 / 总的数量 >0.8
2. 可用服务器上平均负载 >= 0.6
3. 可用服务器数(总的服务器 - 暂停 的服务器数量)<2
满足上面 任何一个,就不开启 区域 亲和(Zone Affinity)
**/
if (((double) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold.get()
|| loadPerServer >= activeReqeustsPerServerThreshold.get()
|| (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold.get()) {
logger.debug("zoneAffinity is overriden. blackOutServerPercentage: {}, activeReqeustsPerServer: {}, availableServers: {}",
new Object[] {(double) circuitBreakerTrippedCount / instanceCount, loadPerServer, instanceCount - circuitBreakerTrippedCount});
return false;
} else {
return true;
}
}
}
getZoneSnapshot 代码如下,
public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
if (servers == null || servers.size() == 0) {
return new ZoneSnapshot();
}
int instanceCount = servers.size();
int activeConnectionsCount = 0;
int activeConnectionsCountOnAvailableServer = 0;
int circuitBreakerTrippedCount = 0;
double loadPerServer = 0;
long currentTime = System.currentTimeMillis();
for (Server server: servers) {
ServerStats stat = getSingleServerStat(server);
// 判断当前时间是否能够访问当前的服务器,如果可以, 那就 同一个窗口时间内的 访问次数+1 ,不能 Trippedcount ++
if (stat.isCircuitBreakerTripped(currentTime)) {
circuitBreakerTrippedCount++;
} else {
activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
}
activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
}
// circuitBreakerTrippedCount == instanceCount ,就说明当前没有服务器可以使用
if (circuitBreakerTrippedCount == instanceCount) {
if (instanceCount > 0) {
// should be NaN, but may not be displayable on Epic
loadPerServer = -1;
}
} else {
// 说明有可用的服务器,计算对应的负载
// 可用服务器上访问的总次数 / (总的服务器数量 - 不能用的服务器数量)
loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
}
// 构造快照,把 总的服务器数量,不可用的服务器数量,所有的服务器 最近一个窗口之内访问的次数,负载 loadPerServer 传入
return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
}
③DefaultNIWSServerListFilter 完全继承ZoneAffinityServerListFilter ,没有进行扩展
④ServerListSubsetFilter 将负载均衡器使用的服务器数量限制为所有服务器的子集。通过比较网络故障和并发连接,收回相对不正常服务器的能力。具体的逻辑如下:
⑤ ZonePreferenceServerListFilter 这里的getFilteredListOfServers() 逻辑 也比较简单:
@Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
List<Server> output = super.getFilteredListOfServers(servers);
if (this.zone != null && output.size() == servers.size()) {
List<Server> local = new ArrayList<Server>();
for (Server server : output) {
if (this.zone.equalsIgnoreCase(server.getZone())) {
local.add(server);
}
}
if (!local.isEmpty()) {
return local;
}
}
return output;
}
ServerStats 的 属性和方法的介绍如下,选了一部分:
public class ServerStats {
//连接失败阈值,默认 3
private final DynamicIntProperty connectionFailureThreshold;
//触发回环断路超时因子, 默认 10
private final DynamicIntProperty circuitTrippedTimeoutFactor;
// 最大回环断路时间, 默认 30S
private final DynamicIntProperty maxCircuitTrippedTimeout;
//活动请求计数时间窗
private static final DynamicIntProperty activeRequestsCountTimeout = DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);
//最后连接失败时间
private volatile long lastConnectionFailedTimestamp;
//首次连接时间
private volatile long firstConnectionTimestamp = 0;
// 构造函数 给出默认
public ServerStats() {
connectionFailureThreshold = DynamicPropertyFactory.getInstance().getIntProperty(
"niws.loadbalancer.default.connectionFailureCountThreshold", 3);
circuitTrippedTimeoutFactor = DynamicPropertyFactory.getInstance().getIntProperty(
"niws.loadbalancer.default.circuitTripTimeoutFactorSeconds", 10);
maxCircuitTrippedTimeout = DynamicPropertyFactory.getInstance().getIntProperty(
"niws.loadbalancer.default.circuitTripMaxTimeoutSeconds", 30);
}
// 拿到 下一次 可以访问的时间,并与当前时间 对比,判断当前时间是否 可以继续访问
public boolean isCircuitBreakerTripped(long currentTime) {
long circuitBreakerTimeout = getCircuitBreakerTimeout();
if (circuitBreakerTimeout <= 0) {
return false;
}
return circuitBreakerTimeout > currentTime;
}
/**
先获取需要暂停的时间,在加上 上一次 连接失败开始的时间,就可以拿到 什么时候可以再次 访问的时间
*/
private long getCircuitBreakerTimeout() {
long blackOutPeriod = getCircuitBreakerBlackoutPeriod();
if (blackOutPeriod <= 0) {
return 0;
}
return lastConnectionFailedTimestamp + blackOutPeriod;
}
// 获取需要暂停的时间
private long getCircuitBreakerBlackoutPeriod() {
// 持续性的连接失败次数
int failureCount = successiveConnectionFailureCount.get();
// 默认连续失败的阈值 3
int threshold = connectionFailureThreshold.get();
// 如果没有达到阈值,直接返回0
if (failureCount < threshold) {
return 0;
}
//连续失败的次数和 阈值的差, 与16比较,最大 16
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
// 获取需要停止时间 为 2的 diff 次方 ,再乘以 因子 10S
int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
暂停时间和 最大回环断路时间(30S)比较,最大取30S
if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
blackOutSeconds = maxCircuitTrippedTimeout.get();
}
return blackOutSeconds * 1000L;
}
// 这里就是把 当前时间设置为 最近一次连接失败的时间
// 失败次数+1
//由于失败,总共暂停多少时间
public void incrementSuccessiveConnectionFailureCount() {
lastConnectionFailedTimestamp = System.currentTimeMillis();
successiveConnectionFailureCount.incrementAndGet();
totalCircuitBreakerBlackOutPeriod.addAndGet(getCircuitBreakerBlackoutPeriod());
}
// 这里是 获取活动窗口之内,成功访问的次数
// 如果距离上一次访问时间 已经超过一个 窗口 的时间,那就 返回0
// 窗口的默认时间是 60*10 秒
public int getActiveRequestsCount(long currentTime) {
int count = activeRequestsCount.get();
if (count == 0) {
return 0;
} else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
activeRequestsCount.set(0);
return 0;
} else {
return count;
}
}
}
ServerListUpdater 就是用于以不同方式进行动态服务器列表更新的策略,先看一下有哪些接口:
public interface ServerListUpdater {
// 接口里面内嵌了一个接口,执行里面的dopudate,来实现服务器列表更新
public interface UpdateAction {
void doUpdate();
}
// 开始服务器列表的更新,具体的 实现在 UpdateAction接口里面
void start(UpdateAction updateAction);
//停止服务器列表的更新
void stop();
// 返回上一次 更新时 的时间,以String 形式
String getLastUpdate();
//返回 从上一次开始更新经历时的 时间(毫秒)
long getDurationSinceLastUpdateMs();
//返回错过的更新周期数
int getNumberMissedCycles();
// 返回核心线程数
int getCoreThreads();
}
在看一下 ServerListUpdater 的实现结构,就两个实现类,PollingServerListUpdater 和 EurekaNotificationServerListUpdater
①EurekaNotificationServerListUpdater类
EurekaNotificationServerListUpdater 是 DynamicServerListLoadBalancer的一种服务器列表更新的实现 ,它利用eureka的事件侦听器触发LB缓存更新。此外,当收到缓存刷新的通知时,serverList上的实际更新是在单独的线程池更新的。
start(final UpdateAction updateAction) 方法的主要逻辑为:
stop() 就是 注销事件监听
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
this.updateListener = new EurekaEventListener() {
@Override
public void onEvent(EurekaEvent event) {
if (event instanceof CacheRefreshedEvent) {
if (!updateQueued.compareAndSet(false, true)) { // if an update is already queued
logger.info("an update action is already queued, returning as no-op");
return;
}
try {
refreshExecutor.submit(new Runnable() {
@Override
public void run() {
try {
// 具体实现在DynamicServerListLoadBalancer 类里面的 updateAction方法
updateAction.doUpdate();
// 记录时间
lastUpdated.set(System.currentTimeMillis());
} catch (Exception e) {
logger.warn("Failed to update serverList", e);
} finally {
updateQueued.set(false);
}
}
}); // fire and forget
} catch (Exception e) {
logger.warn("Error submitting update task to executor, skipping one round of updates", e);
updateQueued.set(false); // if submit fails, need to reset updateQueued to false
}
}
}
};
if (eurekaClient == null) {
eurekaClient = eurekaClientProvider.get();
}
if (eurekaClient != null) {
eurekaClient.registerEventListener(updateListener);
} else {
logger.error("Failed to register an updateListener to eureka client, eureka client is null");
throw new IllegalStateException("Failed to start the updater, unable to register the update listener due to eureka client being null.");
}
} else {
logger.info("Update listener already registered, no-op");
}
}
②PollingServerListUpdater 类
PollingServerListUpdater 是 服务器列表动态更新的 默认实现策略,就是调用了一个定时任务去定时执行,初始化之后 1秒 开始 运行 ,间隔周期是 30S ,同样 具体的实现 也是在 DynamicServerListLoadBalancer 类里面的 updateAction方法
private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
IRule 就是负载均衡的选择策略,先看一下IRule 的接口,就3个,主要是针对 choose(Object key) 的分析
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
再看一下 IRule 的继承结构,然后我们就分析一下每个规则的 特色:
◆ AbstractLoadBalancerRule 是IRule 的抽象类,增加了一个 ILoadBalancer lb 属性
◆ RandomRule 随机策略 就是 通过Random.nextInt(serverCount) 随机取一个 index ,在可用服务里面获取对应的Server .
自己认为这个随机策略 存在好几个问题
1. 可用服务器 的 Size 肯定是 小于 等于 所有服务器的个数,那么 server = upList.get(index); 这里不是会出现 数组越界嘛
2. 如果 出现一直取不到server 的情况,就会进去死循环了
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) {
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
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;
}
◆ RoundRobinRule 轮询策略,逻辑 就是 维护了一个 计数器nextServerCyclicCounter ,每次+1 ,依次访问下一个 , 如果连续10次获取的都是无效的server,就直接终止,不再获取server.
◆ RetryRule 重试策略,设置了一个时间段,和 一个 RoundRobinRule() 策略,即默认是maxRetryMillis = 500;就是 在 500 ms 之内,如果没有获取到就不断的依次获取server,如果时间到了 ,就由Timer 触发 将当前线程 interrupt();然后 返回 null
◆ WeightedResponseTimeRule 以响应时间作为权重,响应时间越短的服务器被选中的概率越大 的策略.
◆ ClientConfigEnabledRoundRobinRule 这个类没啥 特别,就是内置了 一个 RoundRobinRule 策略,调用 RoundRobinRule 的策略,我的疑问就是 为啥子类 不继承 RoundRobinRule ,都继承这个 干啥呢,不是多走了一步弯路
◆ BestAvailableRule策略, 这个就是 对所有的可用服务器进行过滤, 里面涉及的方法 在 serverStats 都介绍过,即 首先判断 每一台服务器 是否 还处于 暂停访问期间,如果是直接 放弃,
接着判断 获取 一个滑动窗口内的有效请求总数,取所有服务器种最小的一个,说明负载最小
◆ PredicateBasedRule策略,抽象类,调用chooseRoundRobinAfterFiltering ,从名字可以猜测出 就是通过对 server 过滤,然后 通过RoundRobin轮询策略 获取server ,最重要的就是 getEligibleServers 方法里面的 this.apply(new PredicateKey(loadBalancerKey, server)) ,这里需要 重新实现Predicate 里面的apply方法 ,也就是 要把 PredicateBasedRule 类里面的 public abstract AbstractServerPredicate getPredicate() 重新实现一下,返回一个具体的 Predicate 接口对应的实现类.
@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;
}
}
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()));
}
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;
}
}
◆ ZoneAvoidanceRule 继承了 PredicateBasedRule 类 ,这里 Predicate的 是一个组合型的过滤,是由 ZoneAvoidancePredicate 和 AvailabilityPredicate 一起组合 的 过滤 ,是一个 and 的 逻辑,然后 如果没有达到要求 ,还有 从过滤逻辑 ,直到满足条件。
public ZoneAvoidanceRule() {
super();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
具体看一下 AndPredicate 类的 apply 实现, AndPredicate.class 也是实现了Predicate ,重写了apply 方法,只要有一个不通过,那就不通过
☆☆☆ 分析 一下 CompositePredicate的方法 getEligibleServers里面的逻辑,代码如下:
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;
}
逻辑为 :
1 .使用主过滤对所有实例过滤并返回过滤后的服务器实例清单
2. 需要判断下面两个条件,只要有一个符合就不再进行从过滤,将当前结果返回供线性轮询算法选择:
1. 过滤后的服务器实例总数 >= 最小过滤服务器实例数(minimalFilteredServers,默认为1)
2. 过滤后的服务器实例比例 > 最小过滤百分比(minimalFilteredPercentage,默认为0)
㈠ 方法:Map
先获取所有的可用区域,然后 在对每一个区域创建快照 ,存入Map
㈡ 方法:String randomChooseZone(Map
Set chooseFrom)
就是从 chooseFrom 随机取一个,但是实现逻辑 个人感觉 复杂了,还加了 服务器个数的 判断,为啥不直接 set 转list ,然后random.nextInt 不就可以了.
㈢ 方法: Set getAvailableZones(
Map
double triggeringBlackoutPercentage)
这段代码的逻辑 主要流程如下:
Ⅰ 获取 所有的的区域集合,如果Size =1 ,直接返回
Ⅱ 开始 对每一个区域下的情况开始计算 ,满足 如下 条件的 就剔除:
❶ 该区域下面的 服务器实例数为 0
❷ 不可用服务器实例数 / 总数 > 触发的百分比 (默认值 为0.99999d)
❸ 负载小于 0 ,就是 服务器列表全坏的情况
Ⅲ 同时还收集 较高负载的 集合Set worstZones, 负载越高越差, 并 获到 负载最高的区域
Ⅳ 如果 所有区域中 的 最高负载 都没有超过触发点(默认是 0.2d),并且 所有的区域都正常(没有剔除过),那就直接返回所有区域
Ⅴ 否则 ,那就从 负载较高的 区域 worstZones 中 随机去掉一个,然后返回
㈣ 方法:getAvailableZones(LoadBalancerStats lbStats,
double triggeringLoad, double triggeringBlackoutPercentage) 这个方法就是 上面两个方法的结合,先创建区域快照,然后选择 可用区域
◆ AvailabilityFilteringRule 继承了 PredicateBasedRule 类,从写了choose 方法,
首先 通过 roundRobinRule 轮询 一个server ,在 通过 predicate (这里的具体实现类是AvailabilityPredicate )判断 是否 符合条件,符合条件 就返回此server ,不符合继续轮询 获取server 然后再判断,如果 连续10次都不符合,那就调用父类的choose 方法.
AvailabilityPredicate 的apply 的判断是否 符合的逻辑 还是 stat 里面 那一块 ,
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);
}
//AvailabilityPredicate 类 的apply 方法
@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;
}
DynamicServerListLoadBalancer 继承了 BaseLoadBalancer,动态获取候选服务器列表的功能。
这里介绍 三个方法 ,并 分析一下主要干了啥,就不贴代码和备注了
方法 restOfInit(IClientConfig clientConfig) 主要做了以下事情:
方法 ** setServersList(List lsrv) 主要逻辑如下 :
方法 ** updateListOfServers() 这个就是 UpdateAction 更新serverList 的 具体操作,上面提到过好几次,这里梳理一下逻辑:
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
获取到可用服务器,这里serverListImpl 的注入 分情况而定,如果是纯Ribbon ,注入的是ConfigurationBasedServerList ,如果 结合了 Eureka ,那就是 DomainExtractingServerList,里面是DiscoveryEnabledNIWSServerList ,里面定义了一个 list . 具体获取的逻辑 上面都介绍过了
在 通过 ServerListFilter过滤一下 服务器列表,这里的 ServerListFilter 过滤器 的默认实现是 ZonePreferenceServerListFilter,getFilteredListOfServers的具体实现 上面也介绍过了,这里略
更新一下所有的服务器信息,其中 会 Ping 一下(如果满足Ping 条件),调用 super.forceQuickPing();
ZoneAwareLoadBalancer 负载均衡器 又继承了 DynamicServerListLoadBalancer ,对DynamicServerListLoadBalancer 进行了扩展,主要 重写了setServerListForZones(Map
ZoneAwareLoadBalancer 从名字上就能看出来,ZoneAwareLoadBalancer 负载均衡器考虑到了 区域Zone的因素,DynamicServerListLoadBalancer 类 对区域没有处理,都是通过RoundRobinRule() 轮休策略去访问,而针对跨区域的情况,访问不同区域的延时 比较大的时候,可以选择相同的区域访问,带来性能上的提高. 我们看一下ZoneAwareLoadBalancer 做了哪些改进,先看setServerListForZones方法,大致的逻辑如下:
@Override
protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {
super.setServerListForZones(zoneServersMap);
if (balancers == null) {
balancers = new ConcurrentHashMap<String, BaseLoadBalancer>();
}
for (Map.Entry<String, List<Server>> entry: zoneServersMap.entrySet()) {
String zone = entry.getKey().toLowerCase();
getLoadBalancer(zone).setServersList(entry.getValue());
}
// check if there is any zone that no longer has a server
// and set the list to empty so that the zone related metrics does not
// contain stale data
for (Map.Entry<String, BaseLoadBalancer> existingLBEntry: balancers.entrySet()) {
if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {
existingLBEntry.getValue().setServersList(Collections.emptyList());
}
}
}
我们再分析一下 ZoneAwareLoadBalancer 的chooseServer 方法:
@Override
public Server chooseServer(Object key) {
// 判断 是否开启 以及 可用的区域是否 超过1个
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
// 获取区域快照,保存在 Map里面
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
// 获取 默认 触发实例负载 的参数,默认为0.2d
if (triggeringLoad == null) {
triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
}
// 获取 触发 服务器实例故障率的百分比,默认为0.99999d
if (triggeringBlackoutPercentage == null) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
}
// 获取到 可用区域
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
// 随机选择一个 区域
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
// 获取BaseLoadBalancer
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
// 获取服务器列表
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
return server;
} else {
logger.debug("Zone avoidance logic is not invoked.");
return super.chooseServer(key);
}
}
上面代码的整个逻辑如下:
本来介绍了 Ribbon 相关的源码和 相关的调用过程,里面的内容比较多,如有哪里不对,麻烦指出,谢谢.
支付宝 | 微信 |
---|---|
如果有帮助记得打赏哦 | 特别需要您的打赏哦 |