Springcloud源碼閱讀4-Ribbon負載均衡(下)

關聯閱讀:

SpringCloud源碼閱讀0-SpringCloud必備知識java

SpringCloud源碼閱讀1-EurekaServer源碼的祕密spring

SpringCloud源碼閱讀2-Eureka客戶端的祕密緩存

SpringCloud源碼閱讀3-Ribbon負載均衡(上)app

配置文件

同其餘微服務組件與spring整合過程同樣,Ribbon也有一個自動配置文件。 RibbonAutoConfiguration負載均衡

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
	//加載配置規範
	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>();
	//加載飢餓屬性。
	@Autowired
	private RibbonEagerLoadProperties ribbonEagerLoadProperties;
	// Ribbon特徵類
	@Bean
	public HasFeatures ribbonFeature() {
		return HasFeatures.namedFeature("Ribbon", Ribbon.class);
	}
	// 客戶端生產工廠
	@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
	//負載均衡客戶端
	@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}
	.......
	.......
}
複製代碼

下面講講配置文件中所包含的知識點。dom

RibbonClientSpecification:

RibbonClient規範,一個規範就對應一種類型的RibbonClient。 規範怎麼制訂呢?ide

  • @RibbonClients : 針對所有服務指定規範的。
  • @RibbonClient: 針對部分指定規範的。

此兩個註解都會引入一個RibbonClientConfigurationRegistrar類。 從其名字,咱們也能夠看出,這是一個用來註冊客戶端配置的註冊類。函數

RibbonClientConfigurationRegistrar會把 @RibbonClients 與 @RibbonClient 註解對應的配置類,註冊爲一個RibbonClientSpecification類的Bean.微服務

  • 對應的配置類做爲構造函數的參數,傳入。
  • 針對的服務名,做爲構造參數傳入。

這樣就獲得了RibbonClientSpecification 規範列表。post

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		builder.addConstructorArgValue(name);//客戶端名稱
		builder.addConstructorArgValue(configuration);//對應的配置類。
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}
複製代碼

Ribbon負載均衡(上)一節說過,@RibbonClients 和 @RibbonClient 用來自定義客戶端組件,替換默認的組件。

因此所謂規範的不一樣,其實就是表如今 個別組件的不一樣

注意: @RibbonClients 的value屬性,能夠用來配置@RibbonClient的複數 @RibbonClients(value = {@RibbonClient(name = "xxx",configuration = XxxRibbonConfig.class),@RibbonClient(name = "demo",configuration = DemoRibbonConfig.class) })

@RibbonClients 的defaultConfiguration屬性,用來替換全部非自定義的客戶端的默認組件

SpringClientFactory:

每一個微服務都在調用多個微服務。調用不一樣微服務的RibbonClient配置可能不一樣。SpringClientFactory根據不一樣的RibbonClient規範(RibbonClientSpecification),爲不一樣的微服務建立子上下文。來存儲不一樣規範的RibbonClient 組件Bean。 以此達到個性化並存的目的。

從代碼中,能夠看出,SpringClientFactory 會傳入List configurations

@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
複製代碼

舉例說明: user服務須要調用, A B C三個微服務,使用@RibbonClient(name = "A", configuration = AConfiguration.class)針對A服務 自定義了IPing 爲MyIPing。 那麼會建立三個上下文:

  • A的上下文,使用A.RibbonClientSpecification 規範建立, IPing 對應的Bean是 MyMyIPing
  • B的上下文,使用default.RibbonClientSpecification 規範建立,IPing 對應的Bean是DummyPing
  • C的上下文,使用default.RibbonClientSpecification 規範建立,IPing 對應的Bean是DummyPing
RibbonClientConfiguration

SpringClientFactory 初始化向其父類,傳遞RibbonClientConfiguration配置類作爲RibbonClient默認的配置。

public SpringClientFactory() {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}

public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
}
複製代碼

RibbonClientConfiguration 配置類中註冊的就是Ribbon 默認的組件

在這裏插入圖片描述

EurekaRibbonClientConfiguration

在與Eureka一塊兒使用的時候,RibbonEurekaAutoConfiguration 使用@RibbonClients註解引入EurekaRibbonClientConfiguration配置類對RibbonClient默認配置的部分組件進行覆蓋。

@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
複製代碼

EurekaRibbonClientConfiguration 配置類會覆蓋:

  • DiscoveryEnabledNIWSServerList 替換 ribbonServerList , 默認安裝一個DomainExtractingServerList代理DiscoveryEnabledNIWSServerList
  • NIWSDiscoveryPing 替換 (IPing) DummyPing

RibbonLoadBalancerClient

RibbonLoadBalancerClient 就是負載均衡客戶端了。

經過此客戶端,咱們能夠傳入服務id,從springClientFactory選擇出對應配置的上下文。使用適用於當前服務的負載均衡組件集,來實現負載均衡的目的。

@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}
複製代碼

負載均衡原理

路由與負載

LoadBalancerClient

RibbonLoadBalancerClient#choose方法。經過傳入服務名,從多個副本中找出一個服務,以達到負載均衡的目的。

@Override
	public ServiceInstance choose(String serviceId) {
		Server server = getServer(serviceId);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}
protected Server getServer(String serviceId) {
		return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
		if (loadBalancer == null) {
			return null;
		}
		return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
複製代碼

RibbonLoadBalancerClient#choose 方法調用loadBalancer.chooseServer

ILoadBalancer: 負載均衡器

從工廠內獲取負載均衡器,上文配置類說過此處的Bean 是ZoneAwareLoadBalancer

protected ILoadBalancer getLoadBalancer(String serviceId) {
		return this.clientFactory.getLoadBalancer(serviceId);
}
複製代碼

ZoneAwareLoadBalancer#chooseServer方法

ZoneAwareLoadBalancer public Server chooseServer(Object key) {
。。。
//默認一個區域的狀況下直接調用父類的chooseServer(key)
 if (availableZones != null &&  availableZones.size() < zoneSnapshot.keySet().size()) {
                String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
                logger.debug("Zone chosen: {}", zone);
                if (zone != null) {
                    BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
                    server = zoneLoadBalancer.chooseServer(key);
                }
 }
 。。。。
}
BaseLoadBalancer public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
                return null;
            }
        }
}
複製代碼
IRule: 負載均衡策略

rule.choose(key) 根據策略從服務列表中選擇一個出來。

默認的IRule是ZoneAvoidanceRule。

choose 方法在其父類PredicateBasedRule中

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;
        }       
    }
複製代碼

能夠看出先執行 ILoadBalancer#getAllServers 獲取全部服務,傳入的策略執行方法中,選擇一個服務。

獲取與更新服務

那麼ILoadBalancer.allServerList是如何存儲全部服務的呢?

ServerListUpdater: 服務更新

ZoneAvoidanceRule的直接父類DynamicServerListLoadBalancer:

在初始化屬性時,會初始化UpdateAction 屬性。UpdateAction 是一個ServerListUpdater的一個內部接口,此處初始化了一個匿名實現類。

protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
        @Override
        public void doUpdate() {
            updateListOfServers();//更新服務列表。
        }
    };
複製代碼

在初始化構造方法時:默認建立(ServerListUpdater)PollingServerListUpdater()

而且調用restOfInit(clientConfig),接着調用enableAndInitLearnNewServersFeature(); 方法。

public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);//執行updateAction動做。
}
複製代碼

最終在PollingServerListUpdater中會有一個定時調度,此定時調度會定時執行UpdateAction 任務,來更新服務列表。默認會在任務建立後1秒後開始執行,而且上次執行完成與下次執行開始之間的間隔,默認30秒。

scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS
);
複製代碼
ServerList 服務列表

UpdateAction#doUpdate() 會調用updateListOfServers() 執行服務列表的更新。

@VisibleForTesting
    public void updateListOfServers() {
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
            servers = serverListImpl.getUpdatedListOfServers();//獲取全部服務列表
            LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                    getIdentifier(), servers);

            if (filter != null) {
                servers = filter.getFilteredListOfServers(servers);//過濾服務列表
                LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
            }
        }
        updateAllServerList(servers);
    }
複製代碼

此時serverListImpl 實現類是DiscoveryEnabledNIWSServerList DiscoveryEnabledNIWSServerList#getUpdatedListOfServers 執行obtainServersViaDiscovery方法

private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
 //獲取 DiscoveryClient 
 EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
 //獲取服務列表 
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
                    ....
}
複製代碼

eurekaClientProvider其實就是對DiscoveryManager的一個代理。DiscoveryManager 在Eureka客戶端的祕密說過,就是對DiscoveryClient的管理者。

eurekaClient.getInstancesByVipAddress 最終調用DiscoveryClient.localRegionApps獲取服務列表。

DiscoveryClient.localRegionApps 在Eureka客戶端的祕密已經說過是客戶端服務列表的緩存。

今後,咱們也能夠看出,Ribbon在與Eureka一塊兒使用時,是從DiscoveryClient獲取服務列表的。

ServerListFilter 服務列表過濾

updateListOfServers 方法中獲取到服務列表後,並無直接返回,而是經過 ServerListFilter進行了過濾

此時默認的是ZonePreferenceServerListFilter ,會過濾出同區域的服務實例, 也就是區域優先

servers = filter.getFilteredListOfServers(servers);
複製代碼
IPing: 檢查服務狀態

updateListOfServers 方法中執行完過濾後,最後還作了一個操做updateAllServerList。

updateAllServerList(servers);

 protected void updateAllServerList(List<T> ls) {
        // other threads might be doing this - in which case, we pass
        if (serverListUpdateInProgress.compareAndSet(false, true)) {
            try {
                for (T s : ls) {
                    s.setAlive(true); // set so that clients can start using these
                }
                setServersList(ls);
                super.forceQuickPing();
            } finally {
                serverListUpdateInProgress.set(false);
            }
        }
    }
複製代碼

updateAllServerList 中最終的一步,就是ping操做,用於檢測服務時候存活。此時默認是DummyPing ,

public boolean isAlive(Server server) {
        return true;//默認永遠是存活狀態。
    }
複製代碼

Ping任務實際上是有一個定時任務存在的:

BaseLoadBalancer 負載均衡器,在初始化時會建立一個定時任務NFLoadBalancer-PingTimer-以10秒的間隔定時去執行Ping任務

public BaseLoadBalancer() {
        this.name = DEFAULT_NAME;
        this.ping = null;
        setRule(DEFAULT_RULE);
        setupPingTask();
        lbStats = new LoadBalancerStats(DEFAULT_NAME);
    }
複製代碼

至此: Ribbon負載均衡的工做原理輪廓就展示出來了, 由於本文的目的在於闡述Ribbon的工做原理。具體向IRule 的具體策略細節,不在本文範圍內,之後找機會再說。

總結

當Ribbon與Eureka一塊兒使用時,Ribbon會從Eureka客戶端的緩存中取服務列表。

咱們在使用Ribbon的時候,並無直接使用RibbonLoadBalancerClient ,而是經常使用Resttemplate+@LoadBalanced來發送請求,那@LoadBalanced是如何讓Resttemplate 具備負載均衡的能力的呢?

看下篇文章

相關文章
相關標籤/搜索