【Spring Cloud】源码-Eureka客户端如何加载Eureka服务注册中心列表

这部分源码涉及到两个类:

1. com.netflix.discovery.endpoint.EndpointUtils

 

2.  org.springframework.cloud.netflix.eureka.EurekaClientConfigBean

  

我断点跟踪使用的客户端配置文件:

spring.application.name=hello-service
server.port=8001


eureka.client.region=shanghai
eureka.client.availability-zones.shanghai=theBund,disney
eureka.client.service-url.disney=http://peer1:1111/eureka/
eureka.client.service-url.theBund=http://peer2:1112/eureka/

解读开始:

 

/**
     * Get the list of all eureka service urls from properties file for the eureka client to talk to.
     *
     * @param clientConfig the clientConfig to use
     * @param instanceZone The zone in which the client resides
     * @param preferSameZone true if we have to prefer the same zone as the client, false otherwise
     * @return an (ordered) map of zone -> list of urls mappings, with the preferred zone first in iteration order
     */
    public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
        Map<String, List<String>> orderedUrls = new LinkedHashMap<>();
        String region = getRegion(clientConfig);        //   #1
        String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());  //   #2
        if (availZones == null || availZones.length == 0) {
            availZones = new String[1];
            availZones[0] = DEFAULT_ZONE;
        }
        logger.debug("The availability zone for the given region {} are {}", region, Arrays.toString(availZones));
        int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);   //   #3

        String zone = availZones[myZoneOffset];
        List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);    //   #4
        if (serviceUrls != null) {
            orderedUrls.put(zone, serviceUrls);
        }
        int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 : (myZoneOffset + 1);    //   #5
        while (currentOffset != myZoneOffset) {
            zone = availZones[currentOffset];
            serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
            if (serviceUrls != null) {
                orderedUrls.put(zone, serviceUrls);
            }
            if (currentOffset == (availZones.length - 1)) {
                currentOffset = 0;
            } else {
                currentOffset++;
            }
        }

        if (orderedUrls.size() < 1) {
            throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
        }
        return orderedUrls;
    }

根据类注释可以知道,这个工具类主要是用来从资源文件中获取所有eureka服务端的url,以便提供给eureka客户端访问使用;观察方法签名:

        参数1是资源文件对象,参数2是客户端所在Zone(若有设置了多个,则选用第一个zone),参数3含义为是否偏好处于同一Zone的Eureka服务端(参数为eureka.client.prefer-same-zone-eureka,默认为true);

        该方法的返回值就是客户端所维护的eureka服务注册中心的URL列表,是一个map集合,其中KEY为zone的名字,VALUE是元素为string的list,存储着key对应的serverUrls。

进入该方法后,根据上面的说明以及我的配置可知,参数1instanceZone是我配置的第一个Zone,即“theBund”,参数3preferSameZone的值为true。运行到#1处,getRegion()方法为从配置文件获取设置的region,代码很短,如下:

 /**
     * Get the region that this particular instance is in.
     *
     * @return - The region in which the particular instance belongs to.
     */
    public static String getRegion(EurekaClientConfig clientConfig) {
        String region = clientConfig.getRegion();
        if (region == null) {
            region = DEFAULT_REGION;
        }
        region = region.trim().toLowerCase();
        return region;
    }

通过上面代码,可以看到如果我不设置region,那么默认是DEFAULT_REGION(值为“default”),同时,根据返回值也可以知道一个微服务(eureka客户端)只能对应一个region。

在获取了region后,接着往下看,运行到#2处:

String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion())这行,这行代码的主要作用是从配置文件中根据上面获取的region获取可用的zone,getAvailabilityZones()的源码也很短,如下:

   public String[] getAvailabilityZones(String region) {
        String value = (String)this.availabilityZones.get(region);
        if (value == null) {
            value = "defaultZone";
        }

        return value.split(",");
    }

其中,value的值为“theBund,disney”,对应的是配置文件中配置的eureka.client.service-url的值,返回值是一个list,下文中会用zoneList引用。

回过头来继续看EndpointUtils类,在#2处获取了region对应的zone(s)后,接下来惯例为空赋默认值,这里的默认值为DEFAULT_ZONE(值为“default”),注意,此处的默认值与getAvailabilityZones()方法给的缺省值(值为“defaultZone”)不同,不知道为啥用不同的值呵呵。

这样,region与对应的zone(s)都已经读取出来了,那么接下来我们就可以加载每个zone对应的serverUrl了。运行到#3处,这里的getZoneOffset()方法是获取在加载zoneList(上面获取的region对应的zone列表)的起始位置,这一段的源码很短,但是逻辑很有意思,所以也粘下来:

/**
     * Gets the zone to pick up for this instance.
     */
    private static int getZoneOffset(String myZone, boolean preferSameZone, String[] availZones) {
        for (int i = 0; i < availZones.length; i++) {
            if (myZone != null && (availZones[i].equalsIgnoreCase(myZone.trim()) == preferSameZone)) {
                return i;
            }
        }
        logger.warn("DISCOVERY: Could not pick a zone based on preferred zone settings. My zone - {}," +
                " preferSameZone- {}. Defaulting to " + availZones[0], myZone, preferSameZone);

        return 0;
    }

至此,我们先将参数捋一下:

myZone:theBund

preferSameZone:true

availZones:{“theBund”,”disney”}

其中myZone是程序获取的第一个zone,肯定处于availZones的第一下标位置,因此,如果参数eureka.client.preferSameZone为true的话,那么返回的索引一定是0;如果为false,那么一定是1。

回过头来,由于我没有设置eureka.client.prefer-same-zone-eureka,缺省为true,所以返回0(赋值给myZoneOffset);接下来的代码含义就是,从myZoneOffset开始,读取availZones列表封装进Map中(即返回值map)。关键步骤#4就是根据zone值总配置文件中回去该zone对应的serverUrl,这段的源码没什么意思,但是也粘出来吧:

public List<String> getEurekaServerServiceUrls(String myZone) {
        String serviceUrls = (String)this.serviceUrl.get(myZone);
        if (serviceUrls == null || serviceUrls.isEmpty()) {
            serviceUrls = (String)this.serviceUrl.get("defaultZone");
        }

        if (!StringUtils.isEmpty(serviceUrls)) {
            String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
            List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
            String[] var5 = serviceUrlsSplit;
            int var6 = serviceUrlsSplit.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                String eurekaServiceUrl = var5[var7];
                if (!this.endsWithSlash(eurekaServiceUrl)) {
                    eurekaServiceUrl = eurekaServiceUrl + "/";
                }

                eurekaServiceUrls.add(eurekaServiceUrl);
            }

            return eurekaServiceUrls;
        } else {
            return new ArrayList();
        }
    }

我们找到判断空赋默认值的那行,如果传入的zone为null,那么程序会获取zone为“defaultZone”的serverUrl,然后我们根据返回值可以知道,同一个zone可以配置多个server。

获取到了zone对应的serverUrl后,此时我的serviceUrls(源码变量)是{“http://peer2:1112/eureka/”},接下来的代码是将zone作为key,将serviceUrls作为value装入返回值map中。OK,现在代码已经成功加载了第一个zone的serverUrl。

最后一步,下面的代码作用是加载剩余zone的serverUrl,这段代码逻辑比较好玩,可以看一下#5处这行代码:

int currentOffset=myZoneOffset == (availZones.length – 1)? 0 : (myZzoneOffset + 1)

这行代码就是从myZoneOffset下一个下标开始将zone的serverUrl放到返回值map中,而接下来的while循环则是从currentOffset开始依次遍历剩下的zone,依次获取每个zone对应的serverUrl,我们举个例子:

假设现在zoneList中还有3个zone,那么假如eureka.client.prefer-same-zone-eureka为true:

那么加载顺序是:

currentOffset:1    myZoneOffset:0

currentOffset:2    myZoneOffset:0

currentOffset:3    myZoneOffset:0

如果eureka.client.prefer-same-zone-eureka为false,那么加载顺序是:

 

currentOffset:2    myZoneOffset:1

currentOffset:3    myZoneOffset:1

currentOffset:0    myZoneOffset:1

这段如果不懂的话可以在纸上模拟写一下顺序哈,自己理解一下。

最后,返回orderedUrls,作为客户端加载的eureka服务端地址列表。

至此,完毕。

 

纯属自己分析,可能会有一些地方理解错误,真诚希望各路兄弟指出有误之处。

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/candy_rainbow/article/details/80809026
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞