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 相關的源碼和 相關的調用過程,裏面的內容比較多,如有哪裏不對,麻煩指出,謝謝.

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