“不积跬步,无以至千里。”
众所周知,在springcloud体系中,Ribbon组件的功能是充当了负载均衡的作用,那么具体完成这个工作的是其内部哪个组件?
答案是ILoadBalancer
。
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
从这两行源码中也可以看出,每个被调服务都会对应一个ILoadBalancer
而且里面会包含服务列表,这篇文章就来重点分析一下Ribbon是怎么跟Eureka整合的
首先要搞明白的是这个负载均衡组件通过一个 getLoadBalancer
方法是怎么拿到的,这里可以先给一个答案:从spring工厂中获取的,而且是特定的工厂,即每个被调用服务会有一个自己的spring工厂,然后从中获取需要的组件,例如这个负载均衡的组件就包含其中
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
这个 clientFactory
就是 SpringClientFactory
类型的一个实例
可以看一下这个类的注释
A factory that creates client, load balancer and client configuration instances. It creates a Spring ApplicationContext per client name, and extracts the beans that it needs from there.
这段话意思就是说,这是一个创建客户端,负载均衡器和客户端配置实例的一个工厂。
他为每个客户端名称(指的就是每个被调服务的服务名称)创建一个Spring ApplicationContext(当然就是Spring容器了),然后从工厂中提取需要的Bean,当然也包括我们需要的负载均衡组件的Bean了
ILoadBalancer loadBalancer = getLoadBalancer(serviceId)
也就是说,我们需要的 ILoadBalancer
是从Spring工厂中获取的,那么是什么时候被放进去的?
这种东西,不是我们自己配置的,只能是自动配置,RibbonClientConfiguration
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
使用的就是这个 ZoneAwareLoadBalancer
,而且这个配置类还包含了IRule
、IPing
之类的组件
进入 ZoneAwareLoadBalancer
的构造流程具体看看
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
这里调用了父类 DynamicServerListLoadBalancer
的构造方法,并且把参数全部传递了下去
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
很坑爹,这里就只有一个方法可以看看 restOfInit
,猜测一下,一些核心的逻辑可能会在这里面
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
updateListOfServers()
这个方法看名字,更新server列表,就知道肯定有搞头,肯定是跟eureka那边打交道,拿到服务列表
@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);
}
ok,现在我们回顾一下,顺便大胆猜想,就是在创建ZoneAwareLoadBalancer
实例的时候,通过调用其父类DynamicServerListLoadBalancer
的构造函数,调用了restOfInit()
方法,调用了updateListOfServers()
方法,并通过这个方法,从eureka client那里获取到被调服务的server list。
servers = serverListImpl.getUpdatedListOfServers()
那么核心就是这个 serverListImpl
,其实是 ServerList 的一个实例
这个 ServerList 其实是构造 ZoneAwareLoadBalancer 的时候,直接在方法中声明的,而且我们又没有手动配置它,那么就是在某个XXConfiguration 中自动配置到Spring容器里面的
EurekaRibbonClientConfiguration
,真是一通好找,最终在一个Ribbon和Eureka整合的配置类中找到了这个自动配置。。。
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
return this.propertiesFactory.get(ServerList.class, config, serviceId);
}
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(
discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
在构造方法里面,将 DiscoveryEnabledNIWSServerList
赋值给其内部的一个 list 变量
private ServerList<DiscoveryEnabledServer> list;
public DomainExtractingServerList(ServerList<DiscoveryEnabledServer> list,
IClientConfig clientConfig, boolean approximateZoneFromHostname) {
this.list = list;
this.ribbon = RibbonProperties.from(clientConfig);
this.approximateZoneFromHostname = approximateZoneFromHostname;
}
发现实际上跟eureka整合的 ServerList 提供的getUpdatedServerList()
方法,是调用的DiscoveryEnabledNIWSServerList
的 getUpdatedServerList() 方法:
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers() {
List<DiscoveryEnabledServer> servers = setZones(this.list
.getUpdatedListOfServers());
return servers;
}
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
return obtainServersViaDiscovery();
}
看到这里,不用说你也猜得到,从eureka client中获取注册表的逻辑一定在这唯一的
obtainServersViaDiscovery
方法中
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
logger.warn("EurekaClient has not been initialized yet, returning an empty list");
return new ArrayList<DiscoveryEnabledServer>();
}
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
// if targetRegion is null, it will be interpreted as the same region of client
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
if(shouldUseOverridePort){
if(logger.isDebugEnabled()){
logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
}
// copy is necessary since the InstanceInfo builder just uses the original reference,
// and we don't want to corrupt the global eureka copy of the object which may be
// used by other clients in our system
InstanceInfo copy = new InstanceInfo(ii);
if(isSecure){
ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
}else{
ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
}
}
DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);
des.setZone(DiscoveryClient.getZone(ii));
serverList.add(des);
}
}
if (serverList.size()>0 && prioritizeVipAddressBasedServers){
break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
}
}
}
return serverList;
}
logger.warn(“EurekaClient has not been initialized yet, returning an empty list”);
这行代码是在说,如果eureka client还没被初始化,就返回一个空的server list,很合理,显然就是从eureka client的本地缓存去拿server list,结果你eureka client都还没有初始化,肯定是不行的。
List listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
vipAddress
,就是服务名称,这行代码,就是根据服务名称去eureka client那边从本地缓存中拿到一个InstanceInfo
的集合,当然这里面就包含我们需要的服务列表,因为每一个 InstanceInfo 都有自己的ip和port嘛
我们可以把断点打在这里看一下,验证猜想
显然,一切都如我们所猜测的那样
这里多说一点,看源码,适当的猜测是必不可少的,这就要求,你对这个技术,要起码有一个底层原理、核心流程有一个大体的把握,去进行大胆的猜想,我把它称之为最优解猜想,然后通过看静态代码或者打断点去验证猜想,才会豁然开朗,当然有些框架,比如ByteTCC,Hystrix这种,它会在底层弄很多后台调度任务,你去debug断点调试下,可能会出现莫名的问题,超时等等,反而不利于我们阅读,所以看静态源码的功力才是硬核。
后面会遍历拿到的这个 List<InstanceInfo>
把每个 InstanceInfo 封装成一个 DiscoveryEnabledServer
,放到一个list中,并返回,然后后面就是一些琐碎的代码,把返回的serverlist填充到ZoneAwareLoadBalancer
里面去,像这种没什么营养的代码,就不贴出来了,都是些体力活,其实最终会把server list设置到 BaseLoadBalancer(DynamicServerListLoadBalancer的父类,ZoneAwareLoadBalancer的爷爷类)
的 allServerList
变量里面去。
protected volatile List allServerList = Collections.synchronizedList(new ArrayList());
到这里,你只要记住ZoneAwareLoadBalancer
里面的server list就是从eureka client本地获取的就够了。