Eureka 源碼解析 —— Eureka-Client 初始化(一)之 EurekaInstanceConfig

摘要: 原創出處 http://www.iocoder.cn/Eureka/eureka-client-init-first/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 Eureka 1.8.X 版本

  1. 創建 EurekaInstanceConfig對象
  2. 使用 EurekaInstanceConfig對象 創建 InstanceInfo對象
  3. 使用 EurekaInstanceConfig對象 + InstanceInfo對象 創建 ApplicationInfoManager對象
  4. 創建 EurekaClientConfig對象
  5. 使用 ApplicationInfoManager對象 + EurekaClientConfig對象 創建 EurekaClient對象

考慮到整個初始化的過程中涉及的配置特別多,拆分成三篇文章:

  1. 【本文】(一)EurekaInstanceConfig
  2. (二)EurekaClientConfig
  3. (三)EurekaClient

下面我們來看看每個的實現。

推薦 Spring Cloud 書籍

2. EurekaInstanceConfig

com.netflix.appinfo.EurekaInstanceConfig,Eureka 應用實例配置接口。在下文你會看到 EurekaClientConfig 接口,兩者的區別如下:

  • EurekaInstanceConfig,重在應用實例,例如,應用名、應用的端口等等。此處應用指的是,Application Consumer 和 Application Provider。
  • EurekaClientConfig,重在 Eureka-Client,例如, 連接的 Eureka-Server 的地址、獲取服務提供者列表的頻率、註冊自身爲服務提供者的頻率等等。

2.1 類關係圖

EurekaInstanceConfig 整體類關係如下圖:

  • 本文只解析紅圈部分類。
  • EurekaArchaius2ClientConfig 基於 Netflix Archaius 2.x 實現,目前還在開發中,因此暫不解析。
  • CloudInstanceConfig、Ec2EurekaArchaius2InstanceConfig 基於亞馬遜 AWS,大多數讀者和我對 AWS 都不瞭解,因此暫不解析。

2.2 配置屬性

點擊 EurekaInstanceConfig 查看配置屬性簡介,已經添加中文註釋,可以對照着英文註釋一起理解。這裏筆者摘出部分較爲重要的屬性:

  • #getLeaseRenewalIntervalInSeconds() :租約續約頻率,單位:秒。應用不斷通過按照該頻率發送心跳給 Eureka-Server 以達到續約的作用。當 Eureka-Server 超過最大頻率未收到續約(心跳),契約失效,進行應用移除。應用移除後,其他應用無法從 Eureka-Server 獲取該應用。
  • #getLeaseExpirationDurationInSeconds() :契約過期時間,單位:秒。
  • #getDataCenterInfo() :數據中心信息。com.netflix.appinfo.DataCenterInfo,數據中心信息接口,目前較爲簡單,標記所屬數據中心名。一般情況下,我們使用 Name.MyOwn。接口實現代碼如下:

    public interface DataCenterInfo {
    /**
    * 數據中心名枚舉
    */
    enum Name {
    Netflix,
    Amazon,
    MyOwn
    }
    /**
    * @return 歸屬的數據中心名
    */
    Name getName();
    }
  • #getNamespace() :配置命名空間,默認使用 eureka。以 eureka-client.properties 舉個例子:

    eureka.name=eureka
    eureka.port=8080
    eureka.vipAddress=eureka.mydomain.net
    • 每個屬性最前面eureka 即是配置命名空間,一般情況無需修改。

  • TODO[0004]:健康檢查


  • #isInstanceEnabledOnit() :應用初始化後是否開啓。在「3. InstanceInfo」詳細解析。

2.3 AbstractInstanceConfig

com.netflix.appinfo.AbstractInstanceConfig,Eureka 應用實例配置抽象基類,主要實現一些相對通用的配置,實現代碼如下:


public abstract class AbstractInstanceConfig implements EurekaInstanceConfig {
/
* 契約過期時間,單位:秒
/
private static final int LEASE_EXPIRATION_DURATION_SECONDS = 90;
/
租約續約頻率,單位:秒。
/
private static final int LEASE_RENEWAL_INTERVAL_SECONDS = 30;
/
應用 https 端口關閉
/
private static final boolean SECURE_PORT_ENABLED = false;
/
應用 http 端口開啓
/
private static final boolean NON_SECURE_PORT_ENABLED = true;
/
應用 http 端口
/
private static final int NON_SECURE_PORT = 80;
/
應用 https 端口
/
private static final int SECURE_PORT = 443;
/
應用初始化後開啓
/
private static final boolean INSTANCE_ENABLED_ON_INIT = false;
/
主機信息
* key:主機 IP 地址
* value:主機名
/
private static final Pair<String, String> hostInfo = getHostInfo();
/*
* 數據中心信息
*/
private DataCenterInfo info = new DataCenterInfo() {
@Override
public Name getName() {
return Name.MyOwn;
}
};
private static Pair<String, String> getHostInfo() {
Pair<String, String> pair;
try {
InetAddress localHost = InetAddress.getLocalHost();
pair = new Pair<String, String>(localHost.getHostAddress(), localHost.getHostName());
} catch (UnknownHostException e) {
logger.error("Cannot get host info", e);
pair = new Pair<String, String>("", "");
}
return pair;
}
// .... 省略 setting / getting 方法
}
  • #getHostInfo() 方法,獲取本地服務器的主機名和主機 IP 地址。如果主機有多網卡或者虛擬機網卡,這塊要小心,解決方式如下:


2.4 PropertiesInstanceConfig

com.netflix.appinfo.PropertiesInstanceConfig,基於配置文件的 Eureka 應用實例配置抽象基類,實現代碼如下:


public abstract class PropertiesInstanceConfig extends AbstractInstanceConfig implements EurekaInstanceConfig {
/
* 命名空間
/
protected final String namespace;
/
配置文件對象
/
protected final DynamicPropertyFactory configInstance;
/*
* 應用分組
* 從 環境變量 獲取
*/
private String appGrpNameFromEnv;
public PropertiesInstanceConfig() {
this(CommonConstants.DEFAULT_CONFIG_NAMESPACE);
}
public PropertiesInstanceConfig(String namespace) {
this(namespace, new DataCenterInfo() {
@Override
public Name getName() {
return Name.MyOwn;
}
});
}
public PropertiesInstanceConfig(String namespace, DataCenterInfo info) {
super(info);
// 設置 namespace,爲 "." 結尾
this.namespace = namespace.endsWith(".")
? namespace
: namespace + ".";
// 從 環境變量 獲取 應用分組
appGrpNameFromEnv = ConfigurationManager.getConfigInstance()
.getString(FALLBACK_APP_GROUP_KEY, Values.UNKNOWN_APPLICATION);
// 初始化 配置文件對象
this.configInstance = Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
}
@Override
public String getAppGroupName() {
return configInstance.getStringProperty(namespace + APP_GROUP_KEY, appGrpNameFromEnv).get().trim();
}
}
  • configInstance 屬性,配置文件對象,基於 Netflix Archaius 1.x 實現配置文件的讀取。在 com.netflix.appinfo.PropertyBasedInstanceConfigConstants 可以看到配置文件的每個屬性 KEY 。
  • appGrpNameFromEnv 屬性,應用分組,從環境變量中獲取。從 #getAppGroupName() 方法中,可以看到優先還是從配置文件讀取。設置方法如下:

    System.setProperty(FALLBACK_APP_GROUP_KEY, "app_gropu_name");
    • FALLBACK_APP_GROUP_KEY,私有靜態變量,實際得使用 NETFLIX_APP_GROUP
    • com.netflix.config.ConfigurationManager 可以從環境變量獲取到值。

  • 調用 Archaius1Utils#initConfig(...) 方法,初始化讀取的配置文件對象,實現代碼如下:


    public final class Archaius1Utils {
    private static final Logger logger = LoggerFactory.getLogger(Archaius1Utils.class);
    private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
    private static final String EUREKA_ENVIRONMENT = "eureka.environment";
    public static DynamicPropertyFactory initConfig(String configName) {
    // 配置文件對象
    DynamicPropertyFactory configInstance = DynamicPropertyFactory.getInstance();
    // 配置文件名
    DynamicStringProperty EUREKA_PROPS_FILE = configInstance.getStringProperty("eureka.client.props", configName);
    // 配置文件環境
    String env = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT, "test");
    ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);
    // 將配置文件加載到環境變量
    String eurekaPropsFile = EUREKA_PROPS_FILE.get();
    try {
    ConfigurationManager.loadCascadedPropertiesFromResources(eurekaPropsFile);
    } catch (IOException e) {
    logger.warn(
    "Cannot find the properties specified : {}. This may be okay if there are other environment "
    + "specific properties or the configuration is installed with a different mechanism.",
    eurekaPropsFile);
    }
    return configInstance;
    }
    }
    • 從環境變量 eureka.client.props,獲取配置文件名。如果未配置,使用參數 configName,即 CommonConstants.CONFIG_FILE_NAME ( "eureka-client" )。
    • 從環境變量 eureka.environment ( EUREKA_ENVIRONMENT ),獲取配置文件環境。
    • 調用 ConfigurationManager#loadCascadedPropertiesFromResources(...) 方法,讀取配置文件到環境變量,首先讀取 ${eureka.client.props}</code> 對應的配置文件;然後讀取 <code>${eureka.client.props}-${eureka.environment} 對應的配置文件。若有相同屬性,進行覆蓋。


2.5 MyDataCenterInstanceConfig

com.netflix.appinfo.MyDataCenterInstanceConfig,非 AWS 數據中心的 Eureka 應用實例配置實現類,實現代碼如下:


public class MyDataCenterInstanceConfig extends PropertiesInstanceConfig implements EurekaInstanceConfig {
public MyDataCenterInstanceConfig() {
}
public MyDataCenterInstanceConfig(String namespace) {
super(namespace);
}
public MyDataCenterInstanceConfig(String namespace, DataCenterInfo dataCenterInfo) {
super(namespace, dataCenterInfo);
}
}

2.6 小結

一般情況下,使用 MyDataCenterInstanceConfig 配置 Eureka 應用實例。

在 Spring-Cloud-Eureka 裏,直接基於 EurekaInstanceConfig 接口重新實現了配置類,實際邏輯差別不大,在TODO[0007] :《Spring-Cloud-Eureka-Client》詳細解析。

3. InstanceInfo

com.netflix.appinfo.InstanceInfo應用實例信息。Eureka-Client 向 Eureka-Server 註冊該對象信息。註冊成功後,可以被其他 Eureka-Client 發現

本文僅分享 InstanceInfo 的初始化。InstanceInfo 裏和註冊發現相關的屬性和方法,暫時跳過。

com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider,基於 EurekaInstanceConfig 創建 InstanceInfo 的工廠,實現代碼如下:

1: @Singleton
2: public class EurekaConfigBasedInstanceInfoProvider implements Provider<InstanceInfo> {
3: private static final Logger LOG = LoggerFactory.getLogger(EurekaConfigBasedInstanceInfoProvider.class);
4:
5: private final EurekaInstanceConfig config;
6:
7: private InstanceInfo instanceInfo;
8:
9: @Inject(optional = true)
10: private VipAddressResolver vipAddressResolver = null;
11:
12: @Inject
13: public EurekaConfigBasedInstanceInfoProvider(EurekaInstanceConfig config) {
14: this.config = config;
15: }
16:
17: @Override
18: public synchronized InstanceInfo get() {
19: if (instanceInfo == null) {
20: // Build the lease information to be passed to the server based on config
21: // 創建 租約信息構建器,並設置屬性
22: LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
23: .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
24: .setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
25:
26: // 創建 VIP地址解析器
27: if (vipAddressResolver == null) {
28: vipAddressResolver = new Archaius1VipAddressResolver();
29: }
30:
31: // Builder the instance information to be registered with eureka server
32: // 創建 應用實例信息構建器
33: InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);
34:
35: // 應用實例編號
36: // set the appropriate id for the InstanceInfo, falling back to datacenter Id if applicable, else hostname
37: String instanceId = config.getInstanceId();
38: DataCenterInfo dataCenterInfo = config.getDataCenterInfo();
39: if (instanceId == null || instanceId.isEmpty()) {
40: if (dataCenterInfo instanceof UniqueIdentifier) {
41: instanceId = ((UniqueIdentifier) dataCenterInfo).getId();
42: } else {
43: instanceId = config.getHostName(false);
44: }
45: }
46:
47: // 獲得 主機名
48: String defaultAddress;
49: if (config instanceof RefreshableInstanceConfig) {
50: // Refresh AWS data center info, and return up to date address
51: defaultAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(false);
52: } else {
53: defaultAddress = config.getHostName(false);
54: }
55: // fail safe
56: if (defaultAddress == null || defaultAddress.isEmpty()) {
57: defaultAddress = config.getIpAddress();
58: }
59:
60: // 設置 應用實例信息構建器 的 屬性
61: builder.setNamespace(config.getNamespace())
62: .setInstanceId(instanceId)
63: .setAppName(config.getAppname())
64: .setAppGroupName(config.getAppGroupName())
65: .setDataCenterInfo(config.getDataCenterInfo())
66: .setIPAddr(config.getIpAddress())
67: .setHostName(defaultAddress) // 主機名
68: .setPort(config.getNonSecurePort())
69: .enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled())
70: .setSecurePort(config.getSecurePort())
71: .enablePort(PortType.SECURE, config.getSecurePortEnabled())
72: .setVIPAddress(config.getVirtualHostName()) // VIP 地址
73: .setSecureVIPAddress(config.getSecureVirtualHostName())
74: .setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
75: .setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl())
76: .setASGName(config.getASGName())
77: .setHealthCheckUrls(config.getHealthCheckUrlPath(),
78: config.getHealthCheckUrl(), config.getSecureHealthCheckUrl());
79:
80: // 應用初始化後是否開啓
81: // Start off with the STARTING state to avoid traffic
82: if (!config.isInstanceEnabledOnit()) {
83: InstanceStatus initialStatus = InstanceStatus.STARTING;
84: LOG.info("Setting initial instance status as: " + initialStatus);
85: builder.setStatus(initialStatus);
86: } else {
87: LOG.info("Setting initial instance status as: {}. This may be too early for the instance to advertise "
88: + "itself as available. You would instead want to control this via a healthcheck handler.",
89: InstanceStatus.UP);
90: }
91:
92: // 設置 應用實例信息構建器 的 元數據( Metadata )集合
93: // Add any user-specific metadata information
94: for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {
95: String key = mapEntry.getKey();
96: String value = mapEntry.getValue();
97: builder.add(key, value);
98: }
99:
100: // 創建 應用實例信息
101: instanceInfo = builder.build();
102:
103: // 設置 應用實例信息 的 租約信息
104: instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
105: }
106: return instanceInfo;
107: }
108:
109: }

  • 該類實現 javax.inject.Provider 接口,設置 InstanceInfo 的生成工廠。感興趣的同學,可以點擊《Google-Guice入門介紹》搜索 Provider 關鍵字。目前處於試驗階段,未完成。
  • EurekaConfigBasedInstanceInfoProvider(config) 構造方法,設置生成 InstanceInfo 的 EurekaInstanceConfig 配置。
  • 調用 #get() 方法,根據 EurekaInstanceConfig 創建 InstanceInfo。InstanceInfo 的絕大數屬性和 EurekaInstanceConfig 是一致的 。實現代碼如下:

    • 第 21 至 24 行 :創建租約信息構建器( com.netflix.appinfo.LeaseInfo.Builder ),並設置 renewalIntervalInSecs / durationInSecs 屬性。
    • 第 26 至 29 行 :創建 VIP地址解析器( com.netflix.appinfo.providers.VipAddressResolver )。實現代碼如下:

      // VipAddressResolver.java
      public interface VipAddressResolver {
      String resolveDeploymentContextBasedVipAddresses(String vipAddressMacro);
      }
      public class Archaius1VipAddressResolver implements VipAddressResolver {
      private static final Pattern VIP_ATTRIBUTES_PATTERN = Pattern.compile("\\$\\&#123;(.*?)\\&#125;"</span>);</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">resolveDeploymentContextBasedVipAddresses</span><span class="params">(String vipAddressMacro)</span> </span>&#123;</div><div class="line"> <span class="keyword">if</span> (vipAddressMacro == <span class="keyword">null</span>) &#123;</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> &#125;</div><div class="line"> String result = vipAddressMacro;</div><div class="line"> <span class="comment">// 替換表達式</span></div><div class="line"> Matcher matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);</div><div class="line"> <span class="keyword">while</span> (matcher.find()) &#123;</div><div class="line"> String key = matcher.group(<span class="number">1</span>);</div><div class="line"> String value = DynamicPropertyFactory.getInstance().getStringProperty(key, <span class="string">""</span>).get();</div><div class="line"></div><div class="line"> logger.debug(<span class="string">"att:"</span> + matcher.group());</div><div class="line"> logger.debug(<span class="string">", att key:"</span> + key);</div><div class="line"> logger.debug(<span class="string">", att value:"</span> + value);</div><div class="line"> logger.debug(<span class="string">""</span>);</div><div class="line"></div><div class="line"> result = result.replaceAll(<span class="string">"\\$\\{" + key + "\\}", value);
      matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);
      }
      return result;
      }
      }
      • 使用 #resolveDeploymentContextBasedVipAddresses() 方法,將 VIP地址 裏的 ${(.*?)}</code> 查找配置文件裏的鍵值進行替換。例如,<code>${eureka.env}.domain.com,查找配置文件裏的鍵 ${eureka.env} 對應值進行替換。TODO[0005]:調試下來,發現 Archaius 已經替換,等到找到答案修改此處。

    • 第 32 至 33 行 :創建應用實例信息構建器( com.netflix.appinfo.InstanceInfo.Builder )。


    • 第 35 至 45 行 :獲得應用實例編號( instanceId )。
    • 第 47 至 58 行 :獲得主機名。
    • 第 60 至 78 行 :設置應用實例信息構建器的屬性。
    • 第 80 至 90 行 :應用初始化後是否開啓。
    • 第 92 至 98 行 :設置應用實例信息構建器的元數據( Metadata )集合。
    • 第 100 至 101 行 :創建應用實例信息( com.netflix.appinfo.InstanceInfo )。
    • 第 103 至 104 行 :設置應用實例信息的租約信息( com.netflix.appinfo.InstanceInfo )。


4. ApplicationInfoManager

com.netflix.appinfo.ApplicationInfoManager,應用信息管理器。實現代碼如下:


public class ApplicationInfoManager {
/
* 單例
/
private static ApplicationInfoManager instance = new ApplicationInfoManager(null, null, null);
/
狀態變更監聽器
/
protected final Map<String, StatusChangeListener> listeners;
/
應用實例狀態匹配
/
private final InstanceStatusMapper instanceStatusMapper;
/
應用實例信息
/
private InstanceInfo instanceInfo;
/*
* 應用實例配置
*/
private EurekaInstanceConfig config;
// ... 省略其它構造方法
public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo, OptionalArgs optionalArgs) {
this.config = config;
this.instanceInfo = instanceInfo;
this.listeners = new ConcurrentHashMap<String, StatusChangeListener>();
if (optionalArgs != null) {
this.instanceStatusMapper = optionalArgs.getInstanceStatusMapper();
} else {
this.instanceStatusMapper = NO_OP_MAPPER;
}
// Hack to allow for getInstance() to use the DI'd ApplicationInfoManager
instance = this;
}
// ... 省略其它方法
}
  • listeners 屬性,狀態變更監聽器集合。在《Eureka 源碼解析 —— 應用實例註冊發現 (一)之註冊》「2.1 應用實例信息複製器」有詳細解析。
  • instanceStatusMapper 屬性,應用實例狀態匹配。實現代碼如下:

    // ApplicationInfoManager.java
    public static interface InstanceStatusMapper {
    }
    private static final InstanceStatusMapper NO_OP_MAPPER = new InstanceStatusMapper() {
    @Override
    public InstanceStatus map(InstanceStatus prev) {
    return prev;
    }
    };
    • #map 方法,根據傳入 pre 參數,轉換成對應的應用實例狀態。
    • 默認情況下,使用 NO_OP_MAPPER 。一般情況下,不需要關注該類。

發佈了22 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章