SpringCloud元件 & 原始碼剖析:Eureka服務註冊方式流程全面分析

恆宇少年發表於2019-03-04

SpringCloud元件:Eureka服務註冊是採用主機名還是IP地址?文章中我們講到了服務註冊的幾種註冊方式,那麼這幾種註冊方式的原始碼是怎麼實現的呢?我們帶著這一個疑問來閱讀本章內容能夠讓你更深入瞭解這塊的知識點!!!

本章目標

分析每一種服務註冊方式原始碼執行流程。

構建專案

本章以分析原始碼為主,所以不去新建立專案來講解相關內容,我們使用SpringCloud元件:Eureka服務註冊是採用主機名還是IP地址?原始碼作為註冊服務SpringCloud元件:搭建Eureka服務註冊中心原始碼作為服務註冊中心,還是按照之前的執行流程:

  1. 啟動服務註冊中心
  2. 啟動本章服務專案
  3. 檢視服務列表,服務註冊方式

配置資訊獲取執行流程

在開始講解本章註冊方式之前,我們需要了解整體的配置資訊獲取的流程資訊,這樣才可以分析指定的註冊方式執行流程。

第一步:例項化EurekaInstanceConfigBean配置實體

在專案啟動時由於依賴spring-cloud-starter-netflix-eureka-client內通過配置spring.factories檔案來讓專案啟動時自動載入並例項化org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration配置類,EurekaClientAutoConfiguration內會自動例項化EurekaInstanceConfigBean並且自動繫結eureka.instance開頭的配置資訊(具體為什麼會自動對映可以去了解下@ConfigurationProperties註解作用),部分原始碼如下所示:

......
public class EurekaClientAutoConfiguration {
    //省略部分原始碼
    @Bean
    @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search        = SearchStrategy.CURRENT)
    public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,													    ManagementMetadataProvider managementMetadataProvider) {
      //省略部分原始碼
      // 傳遞
      EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
      // 省略部分原始碼
    }
    //省略部分原始碼
}
複製程式碼

EurekaClientAutoConfiguration#eurekaInstanceConfigBean方法只有滿足@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)表示式後才會去例項化,並且把例項化物件放入到IOC容器內容,BeanIdeurekaInstanceConfigBean,也就是方法的名稱。
EurekaClientAutoConfiguration#eurekaInstanceConfigBean方法中有這麼一行程式碼我們可以進行下一步的分析

// 通過有參建構函式例項化EurekaInstanceConfigBean配置實體
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
複製程式碼

通過呼叫EurekaInstanceConfigBean(InetUtils inetUtils)建構函式來進行例項化EurekaInstanceConfigBean物件,在這個建構函式內也有一些例項化的工作,原始碼如下:

public EurekaInstanceConfigBean(InetUtils inetUtils) {
    this.inetUtils = inetUtils;
    this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
    this.ipAddress = this.hostInfo.getIpAddress();
    this.hostname = this.hostInfo.getHostname();
}
複製程式碼
第二步:InetUtils#findFirstNonLoopbackHostInfo獲取主機基本資訊

在建構函式EurekaInstanceConfigBean(InetUtils inetUtils)原始碼實現內hostInfo主機資訊通過了InetUtils#findFirstNonLoopbackHostInfo方法來進行例項化,我們來看看這個方法的具體實現邏輯,它會自動讀取系統網路卡列表然再進行迴圈遍歷查詢正在UP狀態的網路卡資訊,如果沒有查詢到網路卡資訊,則使用預設的HostNameIpAddress配置資訊,原始碼如下所示:

public HostInfo findFirstNonLoopbackHostInfo() {
    InetAddress address = findFirstNonLoopbackAddress();
    if (address != null) {
        return convertAddress(address);
    }
    HostInfo hostInfo = new HostInfo();
    hostInfo.setHostname(this.properties.getDefaultHostname());
    hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
    return hostInfo;
}

public InetAddress findFirstNonLoopbackAddress() {
    InetAddress result = null;
    try {
        int lowest = Integer.MAX_VALUE;
        for (Enumeration<NetworkInterface> nics = NetworkInterface
                .getNetworkInterfaces(); nics.hasMoreElements();) {
            NetworkInterface ifc = nics.nextElement();
            if (ifc.isUp()) {
                log.trace("Testing interface: " + ifc.getDisplayName());
                if (ifc.getIndex() < lowest || result == null) {
                    lowest = ifc.getIndex();
                }
                else if (result != null) {
                    continue;
                }

                // @formatter:off
                if (!ignoreInterface(ifc.getDisplayName())) {
                    for (Enumeration<InetAddress> addrs = ifc
                            .getInetAddresses(); addrs.hasMoreElements();) {
                        InetAddress address = addrs.nextElement();
                        if (address instanceof Inet4Address
                                && !address.isLoopbackAddress()
                                && isPreferredAddress(address)) {
                            log.trace("Found non-loopback interface: "
                                    + ifc.getDisplayName());
                            result = address;
                        }
                    }
                }
                // @formatter:on
            }
        }
    }
    catch (IOException ex) {
        log.error("Cannot get first non-loopback address", ex);
    }

    if (result != null) {
        return result;
    }

    try {
        return InetAddress.getLocalHost();
    }
    catch (UnknownHostException e) {
        log.warn("Unable to retrieve localhost");
    }

    return null;
}
複製程式碼

預設的HostNameIpAddress屬性配置資訊在InetUtilsProperties配置實體類內,如果不進行設定則直接使用預設值,如果你想更換預設值,那麼你可以在application.yml配置檔案內通過設定spring.cloud.inetutils.defaultHostnamespring.cloud.inetutils.defaultIpAddress進行修改預設值,原始碼如下所示:

public class InetUtilsProperties {
	public static final String PREFIX = "spring.cloud.inetutils";

	/**
	 * The default hostname. Used in case of errors.
	 */
	private String defaultHostname = "localhost";

	/**
	 * The default ipaddress. Used in case of errors.
	 */
	private String defaultIpAddress = "127.0.0.1";
}
複製程式碼
第三步:EurekaInstanceConfigBean#getHostName方法實現

getHostName是一個Override的方法,繼承於com.netflix.appinfo.EurekaInstanceConfig介面,該方法有個boolean型別的引數refresh來判斷是否需要重新整理重新獲取主機網路基本資訊,當傳遞refresh=false並且在application.yml配置檔案內並沒有進行手動設定eureka.instance.hostname以及eureka.instance.ip-address引數則會根據eureka.instance.prefer-ip-address設定的值進行返回資訊,原始碼如下所示:

@Override
public String getHostName(boolean refresh) {
    if (refresh && !this.hostInfo.override) {
        this.ipAddress = this.hostInfo.getIpAddress();
        this.hostname = this.hostInfo.getHostname();
    }
    return this.preferIpAddress ? this.ipAddress : this.hostname;
}
複製程式碼

預設註冊方式原始碼分析

由於在例項化EurekaInstanceConfigBean配置實體類時,建構函式進行了獲取第一個非迴環主機資訊,預設的hostName以及ipAddress引數則是會直接使用InetUtils#findFirstNonLoopbackHostInfo方法返回的相對應的值。

IP優先註冊方式原始碼分析

EurekaInstanceConfigBean#getHostName方法直接呼叫本類過載方法getHostName(boolean refresh)並且傳遞引數為false,根據第三步原始碼我們就可以看到:

return this.preferIpAddress ? this.ipAddress : this.hostname;
複製程式碼

如果eureka.instance.prefer-ip-address引數設定了true就會返回eureka.instance.ip-address的值,這樣我們就可以從中明白為什麼主動設定eureka.instance.ip-address引數後需要同時設定eureka.instance.prefer-ip-address引數才可以生效。

指定IP、HostName原始碼分析

我們通過application.yml配置檔案進行設定eureka.instance.hostname以及eureka.instance.ip-address後會直接替換原預設值,在EurekaInstanceConfigBean#getHostName中也是返回的this.hostnamethis.ipAddress所以在這裡設定後會直接生效作為返回的配置值。

總結

我們通過原始碼進行分析服務註冊方式執行流程,這樣在以後進行配置eureka.instance.hostnameeureka.instance.prefer.ip-addresseureka.instance.ip-address三個配置資訊時就可以根據優先順序順序達到預期的效果,避免沒有必要的錯誤出現。

原始碼位置

有問題要問?

如果你有技術相關的問題想要諮詢恆宇少年,請去部落格首頁左側導航欄,點選知識星球微信掃碼加入我的星球。

與恆宇少年面對面

如果你喜歡恆宇少年的相關文章,那麼就去微信公眾號(恆宇少年)關注我吧!!!
當然你也可以去 SpringCloud碼雲原始碼 專案底部掃描微信公眾號二維碼關注我,感謝閱讀!!!

學習目錄推薦

開源資訊

這段時間一直在編寫開源的相關框架,致力於公司使用的框架升級以及開源計劃,將公司使用到的工具以及外掛進行升級重構並且開源。

  • 程式碼生成器(Code-Builder)
    code-builder程式碼生成器根據你提供的模板檔案(目前支援freemarker)自動生成實體類,可以很大很有效的提高開發效率。
    Gitee地址gitee.com/hengboy/cod…
    Github地址github.com/hengyuboy/c…
  • 持久化框架(MyBatis-Enhance)
    mybatis-enhance是一個對mybatis框架的增強封裝,提供一系列的內部方法來完成單表資料的操作,多表資料提供DSL方式進行操作。
    Gitee地址gitee.com/hengboy/myb…
    Github地址github.com/hengyuboy/m…
  • 自動分頁外掛
    MyBatis-Pageable是一款自動化分頁的外掛,基於MyBatis內部的外掛Interceptor攔截器編寫完成,攔截Executor.query的兩個過載方法計算出分頁的資訊以及根據配置的資料庫Dialect自動執行不同的查詢語句完成總數量的統計。
    Gitee地址gitee.com/hengboy/myb…

相關文章