1. 程式人生 > >【Spring Cloud】原始碼-Eureka客戶端如何載入Eureka服務註冊中心列表

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

至此,完畢。

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