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
RibbonClient規範,一個規範就對應一種類型的RibbonClient。 規範怎麼制訂呢?ide
此兩個註解都會引入一個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屬性,用來替換全部非自定義的客戶端的默認組件
每一個微服務都在調用多個微服務。調用不一樣微服務的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。 那麼會建立三個上下文:
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 默認的組件
在與Eureka一塊兒使用的時候,RibbonEurekaAutoConfiguration 使用@RibbonClients
註解引入EurekaRibbonClientConfiguration配置類對RibbonClient默認配置的部分組件進行覆蓋。
@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}
複製代碼
EurekaRibbonClientConfiguration 配置類會覆蓋:
RibbonLoadBalancerClient 就是負載均衡客戶端了。
經過此客戶端,咱們能夠傳入服務id,從springClientFactory選擇出對應配置的上下文。使用適用於當前服務的負載均衡組件集,來實現負載均衡的目的。
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
複製代碼
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
從工廠內獲取負載均衡器,上文配置類說過此處的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;
}
}
}
複製代碼
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是如何存儲全部服務的呢?
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
);
複製代碼
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獲取服務列表的。
updateListOfServers 方法中獲取到服務列表後,並無直接返回,而是經過 ServerListFilter進行了過濾
此時默認的是ZonePreferenceServerListFilter ,會過濾出同區域的服務實例, 也就是區域優先
servers = filter.getFilteredListOfServers(servers);
複製代碼
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 具備負載均衡的能力的呢?
看下篇文章