SpringCloud源碼-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服務端地址列表。

至此,完畢。


純屬自己分析,可能會有一些地方理解錯誤,真誠希望各路兄弟指出有誤之處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章