02深度解析Spring Cloud Ribbon---LoadBalancerAutoConfiguration拦截器注入

一、LoadBalancerAutoConfiguration介绍

上一章我们已经分析了,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);
				}
			};
		}
	}
	}

上面的代码都加了一些备注, 总结一下

1、LoadBalancerAutoConfiguration 的注入满足条件

⑴存在RestTemplate.class
⑵需要提前注入 LoadBalancerClient 的Bean ,这里 需要 提前 注入配置类 RibbonAutoConfiguration,如下图, RibbonAutoConfiguration 上面也提升 了 需要在 LoadBalancerAutoConfiguration.class 之前注入,完全吻合.
此外,LoadBalancerClient 的唯一实现类 是 RibbonLoadBalancerClient
在这里插入图片描述

2、 LoadBalancerAutoConfiguration 内容

LoadBalancerAutoConfiguration 里面的注入:

  1. 生成了一个 LoadBalancerInterceptor 拦截器 ,参数为 LoadBalancerClient 和 LoadBalancerRequestFactory
  2. 注入了一个 RestTemplateCustomizer Bean , 这个的作用就是 给 带有 @LoadBalanced 的RestTemplate 添加拦截器 ,这个Bean 后续 会被 afterSingletonsInstantiated 调用到
  3. 生成了一个transformers 的Bean ,这个没有实现类,可以自己实现
  4. 生成了一个 loadBalancerRequestFactory 的Bean ,这个主要是用于createRequest()新建一个LoadBalancerRequest
  5. 这里是重点 ,注入了一个 带有 @LoadBalanced 的restTemplate ,为啥 能收集到所有 带有@LoadBalanced 注解的呢,这是由于 里面有一个 @Qualifier ,这个可以自己去了解一下,这里确实不错,厉害
  6. 实例了一个 SmartInitializingSingleton ,主要是为了 在其他的Bean 注入之后,进行 对 RestTemplate 添加拦截器.

3、小结

LoadBalancerAutoConfiguration 这个自动配置类 注入完成之后,就会将 带有 @LoadBalanced 注解的 RestTemplate 具有负载均衡能力了.

二、LoadBalancerInterceptor的相关逻辑

1. intercept 拦截内容

上面已经分析了 RestTemplate 具有了 负载均衡的能力了,接下来我们看一下具体的拦截器 如何实现 负载均衡的.,首先看一下代码如下:
在这里插入图片描述
从上面源码可以看出 :

  1. 获取请求链接
  2. 获取ServiceName ,这就是 为什么 请求的链接里面 输入 ip:port会报错
  3. 根据serviceName 到LoadBalancerClient 接口里面运行 execute

接下来我们分析一下这个 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);
}

2. RibbonLoadBalancerClient 具体内容

接下来就看一下 具体的实现类里面的 逻辑,代码如下: 在这里插入图片描述
在这里插入图片描述

从这里 我们也可以看出 一共有以下步骤:

  1. 选择负载均衡器 ILoadBalancer
  2. 通过负载均衡器选择一个 应用实例server ,这里 涉及到负载均衡器ILoadBalancer, 这块的知识点详细 在下面第三点分析,我们这里先把这个流程走通</>
  3. 将 应用实例server 封装成对应的ribbonServer
  4. 进一步调用 execute ,这里其实 就要回调 接口函数request.apply(serviceInstance);
  5. 这里的 request.apply(serviceInstance); 就是 上面 传过来的 LoadBalancerRequest 接口,会进入到 如下具体方法执行,如下图
    在这里插入图片描述
    在这里插入图片描述
    *** 5.1 这里就是构建一个 request (通过 ServiceRequestWrapper )这里一定要进入ServiceRequestWrapper 看一下,这个类重写了 getURI() 方法,这个方法就是 将 原来的 http://服务名 ===> http://ip:port ,这个方法 后续 要被调用的
    **** 5.2 然后 再运行 execution.execute(serviceRequest, body); 迭代调用本身 (感觉也是另外一种递归),这里就解释了上一章 里面的 那个 if (this.iterator.hasNext()) 这里 是一个 循环,对于这种写法,我只能说一句 ‘我擦,流弊’
  6. 这样 一个 负载均衡的 拦截器主要流程 就 结束了,下面我们继续里面的每一个步骤的细节.

3. ILoadBalancer 分析

上面说到 第一步就是选择负载均衡器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 类,又做了一定的改进.

3.1 ILoadBalancer 的默认注入和 依赖属性

在分析 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 更新的操作

3.1.1 定位RibbonClientConfiguration

我们是如何定位到 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的配置类

3.2 AbstractLoadBalancer 类

下面继续 说下 实现类里面的方法:
**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();    
}

3.3 BaseLoadBalancer 类

** BaseLoadBalancer 类:这个类是 默认实现,首先我们看一下有哪些属性和方法:

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

3.3.2 PingTask() 介绍

这里就是主要就是 更新 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() ,逻辑都比较简单,就不多提了。
在这里插入图片描述

3.3.3 BaseLoadBalancer 方法介绍

  1. addServers(List newServers) ---- 这个就是 想原来的 allServerList 和 新的 newServers都加到 一个集合里面, 再将老的 allServerList 指向新的 list .
  2. getServerList(boolean availableOnly) -----根据 availableOnly 返回 是全部的 serverList 还是 可用的, 全部的就是allServerList,可用的就是upServerList
  3. List getServerList(ServerGroup serverGroup) — 就是根据 serverGroup 来返回 对应的数据
  4. markServerDown(Server server)---- 将server 的 活跃Flag(isAliveFlag) 改为false

3.4 DynamicServerListLoadBalancer介绍

3.4.1 DynamicServerListLoadBalancer 属性

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

上面是一些主要属性,我们就一个一个 的看一下

3.4.1.1 ServerList 介绍
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() ,我们分析一下 ,主要就是:

  1. 获取Eureka 客户端,如果没有则报错
  2. 通过Eureka 客户端 获取 listOfInstanceInfo 服务器实例
  3. 将服务器实例 封装成DiscoveryEnabledServer 类型
  4. 设置区域 ,便于以后选择同一个Zone 进行调用(这里涉及 Region 和 Zone 相关的概念)
  5. 返回服务器列表
3.4.1.2 ServerListFilter 介绍

接口 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 将负载均衡器使用的服务器数量限制为所有服务器的子集。通过比较网络故障和并发连接,收回相对不正常服务器的能力。具体的逻辑如下:

  1. 获取过滤过一遍的 服务器列表
  2. 获取到自己维护的currentSubset 服务器列表集
  3. 遍历一下 currentSubset ,剔除掉已经下线的,获取不在 过滤服务器列表之内的
  4. 同时对剩下的 进行过滤,判断 的条件如下:
    4.1. 服务器的最近的一个的窗口的访问次数> 设置的次数(默认是0)
    4.2. 服务器访问失败的次数 > 设置的次数(默认是0)
  5. 获取需要保留的设置的子集(默认20个),以及剔除百分比(默认0.1f)
  6. 判断一下,如果 剩下的数量 > 设置的需要保留的 或者 剔除的百分比 对应的数量 还没有达到,那就要继续剔除
  7. 这是 就对剩下的服务器 按照健康排序,然后剔除掉相应的
  8. 如果 剔除之后,保留的数量 达不到 默认保留的数量(默认20),这时候candidates.removeAll(newSubSet); 就是 将新的服务器加进来,如果数量还不够,重新回滚到zoneAffinityFiltered ,再去取对应的数量的数据 ,代码就不贴了

ZonePreferenceServerListFilter 这里的getFilteredListOfServers() 逻辑 也比较简单:

  1. 获取到过滤这之后的服务器列表集
    2.一个一个的遍历,判断 服务器的 Zone 和调用者的 Zone 是否一样,如果一样,就收集
  2. 返回,如果为空,就返回 父类过滤的,代码如下:
	@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;
	}
3.4.1.3 ServerStats 介绍

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;
        }
    }
}
3.4.1.4 ServerListUpdater 介绍

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) 方法的主要逻辑为:

  1. 新建一个 EurekaEventListener() ,
  2. 重写里面的onEvent(EurekaEvent event) 事件,具体内容是 启动一个线程 去 更新具体的内容
  3. updateAction.doUpdate(); 而真正的内容 是在调用方,即DynamicServerListLoadBalancer 类里面的 updateAction方法
  4. 设置最近一次更新时间

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");
        }
    }
3.4.1.5 IRule介绍

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)
3.4.1.5.1 ZoneAvoidanceRule 相关的方法介绍

㈠ 方法:Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats)
先获取所有的可用区域,然后 在对每一个区域创建快照 ,存入Map<String, ZoneSnapshot>

㈡ 方法:String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
Set chooseFrom)

就是从 chooseFrom 随机取一个,但是实现逻辑 个人感觉 复杂了,还加了 服务器个数的 判断,为啥不直接 set 转list ,然后random.nextInt 不就可以了.

㈢ 方法: Set getAvailableZones(
Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
double triggeringBlackoutPercentage)

这段代码的逻辑 主要流程如下:
Ⅰ 获取 所有的的区域集合,如果Size =1 ,直接返回
Ⅱ 开始 对每一个区域下的情况开始计算 ,满足 如下 条件的 就剔除:

    ❶ 该区域下面的 服务器实例数为 0 
    ❷ 不可用服务器实例数 /  总数   > 触发的百分比 (默认值 为0.99999d)
    ❸ 负载小于 0 ,就是 服务器列表全坏的情况

Ⅲ 同时还收集 较高负载的 集合Set worstZones, 负载越高越差, 并 获到 负载最高的区域
Ⅳ 如果 所有区域中 的 最高负载 都没有超过触发点(默认是 0.2d),并且 所有的区域都正常(没有剔除过),那就直接返回所有区域
Ⅴ 否则 ,那就从 负载较高的 区域 worstZones 中 随机去掉一个,然后返回

㈣ 方法:getAvailableZones(LoadBalancerStats lbStats,
double triggeringLoad, double triggeringBlackoutPercentage)
这个方法就是 上面两个方法的结合,先创建区域快照,然后选择 可用区域

3.4.1.5.2 AvailabilityFilteringRule

◆ AvailabilityFilteringRule 继承了 PredicateBasedRule 类,从写了choose 方法,
首先 通过 roundRobinRule 轮询 一个server ,在 通过 predicate (这里的具体实现类是AvailabilityPredicate )判断 是否 符合条件,符合条件 就返回此server ,不符合继续轮询 获取server 然后再判断,如果 连续10次都不符合,那就调用父类的choose 方法.

AvailabilityPredicate 的apply 的判断是否 符合的逻辑 还是 stat 里面 那一块 ,

  1. 此server 是否在禁止访问期间
  2. 实例请求数量是否 大于 阈值 (默认 Integer.MAX_VALUE)
    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;
    }

3.4.2 DynamicServerListLoadBalancer 方法介绍

DynamicServerListLoadBalancer 继承了 BaseLoadBalancer,动态获取候选服务器列表的功能。
这里介绍 三个方法 ,并 分析一下主要干了啥,就不贴代码和备注了

方法 restOfInit(IClientConfig clientConfig) 主要做了以下事情:

  1. 先保存 enablePrimingConnections 原先的值,然后 将 enablePrimingConnections 暂时设置为false,这是为了 防止 启动两个异步线程去 运行 相关的提前httpClient 的请求,因为在 BaseLoadBalancer的初始化的过程中也会有 enablePrimingConnections 相关的判断
  2. 通过 enableAndInitLearnNewServersFeature(); 开启一个 定时任务去 定时 更新 serverList 信息(上面已经分析过了,初始化1S 之后),这里的服务列表信息更新实例就是默认的 PollingServerListUpdater实例,updateAction 方法就是 DynamicServerListLoadBalancer类里面的 updateListOfServers();
  3. 立刻 运行updateListOfServers(); // 这里这么做应该是考虑到 上面有1秒的延时问题
  4. 再将 enablePrimingConnections 这里 改为 原来的值

方法 ** setServersList(List lsrv) 主要逻辑如下 :

  1. 调用父类的 super.setServersList(lsrv); 进行相关逻辑的处理
  2. 在 缓存 serverStatsCache 里面 创建一下 (如果没有)
  3. 建立一个 Map<String, List> 的 映射关系 key(Zone) --> Value( 对应的服务器列表)
  4. 调用 setServerListForZones 方法 进行 更新upServerListZoneMap ,并且确保每一个Zone 信息都在zoneStatsMap 里面

方法 ** 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);
    }
  1. 获取到可用服务器,这里serverListImpl 的注入 分情况而定,如果是纯Ribbon ,注入的是ConfigurationBasedServerList ,如果 结合了 Eureka ,那就是 DomainExtractingServerList,里面是DiscoveryEnabledNIWSServerList ,里面定义了一个 list . 具体获取的逻辑 上面都介绍过了

  2. 在 通过 ServerListFilter过滤一下 服务器列表,这里的 ServerListFilter 过滤器 的默认实现是 ZonePreferenceServerListFilter,getFilteredListOfServers的具体实现 上面也介绍过了,这里略

  3. 更新一下所有的服务器信息,其中 会 Ping 一下(如果满足Ping 条件),调用 super.forceQuickPing();

3.4.3 DynamicServerListLoadBalancer 小结

  1. DynamicServerListLoadBalancer 类 对 Zone相关的其实没有处理,DynamicServerListLoadBalancer 类里面的 setServerListForZones 主要就是 更新了 一下 key(zone)->value(serverlist )的Map,以及保存了 区域Zone的信息
  2. DynamicServerListLoadBalancer 类 没有重写chooseServer 方法,调用的还是BaseLoadBalancer的chooseServer 方法,也就是 走的new RoundRobinRule() 轮询的策略.

3.5 ZoneAwareLoadBalancer介绍

ZoneAwareLoadBalancer 负载均衡器 又继承了 DynamicServerListLoadBalancer ,对DynamicServerListLoadBalancer 进行了扩展,主要 重写了setServerListForZones(Map<String, List> zoneServersMap)、chooseServer(Object key) 这两个方法。

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());
            }
        }
    }   
  1. 还是调到 父类的setServerListForZones(zoneServersMap); 将key(zone)—>value(serverlist)和 zone 信息更新
  2. 将所有的区域相关的信息 更新到缓存 balancers里面(key 为 区域Zone,value 为 BaseLoadBalancer) ,以区域划分,一个区域对应一个 BaseLoadBalancer,再将区域下面的ServerList 填充进去.
  3. 再次校验一下,将那些被过滤掉的Zone ,下面对应的 serverList 置为空.
    //分析来,DynamicServerListLoadBalancer 其实已经将 zone—>serverList 做了划分,但是没有后续的操作了,这也是ZoneAwareLoadBalancer 先调用 super.setServerListForZones(zoneServersMap); 在此基础上 进一步 扩展

我们再分析一下 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);
        }
    }

上面代码的整个逻辑如下:

  1. 判断 是否开启 区域过滤, 并且 可用区域的 个数 > 1 ,否则 调用父类的 chooseServer
  2. 调用 ZoneAvoidanceRule.createSnapshot ,创建 区域快照,保存到 Map<String, ZoneSnapshot> zoneSnapshot里面
  3. 再次调用 ZoneAvoidanceRule.getAvailableZones 进行 区域过滤,这里面的逻辑 详细 见 上面ZoneAvoidanceRule.getAvailableZones 里面的解释
  4. 如果 可用 区域 availableZones 不为空,并且 小于 所有快照区域的大小,那就从 可用区域中随机选一个区域 zone
  5. 根据 选择出来的 zone 获取对应的BaseLoadBalancer ,然后在根据对应的IRule 获取server 服务器实例
  6. 如果 服务器实例为Null(没有获取到 服务器实例) ,那就调用 父类的 super.chooseServer(key);

三、运行的流程图

在这里插入图片描述

四、 小结

本来介绍了 Ribbon 相关的源码和 相关的调用过程,里面的内容比较多,如有哪里不对,麻烦指出,谢谢.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章