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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取 2000+ 道 Java 面试题

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

  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服务端地址列表。

至此,完毕。

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


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【Spring Cloud】源码-Eureka客户端如何加载Eureka服务注册中心列表

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏