Ribbon源码深度刨析-(3)整合Eureka

“不积跬步,无以至千里。”

众所周知,在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,而且这个配置类还包含了IRuleIPing之类的组件

进入 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嘛

我们可以把断点打在这里看一下,验证猜想

ribbon从eureka client获取InstanceInfo

显然,一切都如我们所猜测的那样

这里多说一点,看源码,适当的猜测是必不可少的,这就要求,你对这个技术,要起码有一个底层原理、核心流程有一个大体的把握,去进行大胆的猜想,我把它称之为最优解猜想,然后通过看静态代码或者打断点去验证猜想,才会豁然开朗,当然有些框架,比如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本地获取的就够了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值