本文主要基於 Eureka 1.8.X 版本
-
1. 概述
-
2. EurekaInstanceConfig
-
2.1 類關係圖
-
2.2 配置屬性
-
2.3 AbstractInstanceConfig
-
2.4 PropertiesInstanceConfig
-
2.5 MyDataCenterInstanceConfig
-
2.6 小結
-
3. InstanceInfo
-
4. ApplicationInfoManager
-
666. 彩蛋
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
1. 概述
本文主要分享 Eureka-Client 自身初始化的過程,不包含 Eureka-Client 向 Eureka-Server 的註冊過程( ?後面會另外文章分享 )。
Eureka-Client 自身初始化過程中,涉及到主要物件如下圖:
-
建立 EurekaInstanceConfig物件
-
使用 EurekaInstanceConfig物件 建立 InstanceInfo物件
-
使用 EurekaInstanceConfig物件 + InstanceInfo物件 建立 ApplicationInfoManager物件
-
建立 EurekaClientConfig物件
-
使用 ApplicationInfoManager物件 + EurekaClientConfig物件 建立 EurekaClient物件
考慮到整個初始化的過程中涉及的配置特別多,拆分成三篇文章:
-
【本文】(一)EurekaInstanceConfig
-
(二)EurekaClientConfig
-
(三)EurekaClient
下麵我們來看看每個類的實現。
推薦 Spring Cloud 書籍:
-
請支援正版。下載盜版,等於主動編寫低階 BUG 。
-
程式猿DD —— 《Spring Cloud微服務實戰》
-
周立 —— 《Spring Cloud與Docker微服務架構實戰》
-
兩書齊買,京東包郵。
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 hostInfo = getHostInfo();
/**
* 資料中心資訊
*/
private DataCenterInfo info = new DataCenterInfo() {
@Override
public Name getName() {
return Name.MyOwn;
}
};
private static Pair getHostInfo() {
Pair pair;
try {
InetAddress localHost = InetAddress.getLocalHost();
pair = new Pair(localHost.getHostAddress(), localHost.getHostName());
} catch (UnknownHostException e) {
logger.error("Cannot get host info", e);
pair = new Pair("", "");
}
return pair;
}
// .... 省略 setting / getting 方法
}
-
#getHostInfo()
方法,獲取本地伺服器的主機名和主機 IP 地址。如果主機有多網絡卡或者虛擬機器網絡卡,這塊要小心,解決方式如下: -
手動配置本機的
hostname
+etc/hosts
檔案,從而對映主機名和 IP 地址。 -
使用 Spring-Cloud-Eureka-Client 的話,參考周立 —— 《Eureka服務註冊過程詳解之IpAddress》解決。
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}
對應的配置檔案;然後讀取${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 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 是一致的 。實現程式碼如下: -
第 82 至 85 行 :應用不開啟,應用實體處於 STARTING 狀態。
-
第 86 至 90 行 :應用開啟,應用實體處於 UP 狀態。
-
使用應用初始化後不開啟,可以透過呼叫
ApplicationInfoManager#setInstanceStatus(...)
方法改變應用實體狀態,在《Eureka 原始碼解析 —— 應用實體註冊發現 (一)之註冊》「2.1 應用實體資訊複製器」有詳細解析。 -
使用
#resolveDeploymentContextBasedVipAddresses()
方法,將 VIP地址 裡的${(.*?)}
查詢配置檔案裡的鍵值進行替換。例如,${eureka.env}.domain.com
,查詢配置檔案裡的鍵${eureka.env}
對應值進行替換。TODO[0005]:除錯下來,發現 Archaius 已經替換,等到找到答案修改此處。 -
第 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("\\$\\{(.*?)\\}");
}
@Override
public String resolveDeploymentContextBasedVipAddresses(String vipAddressMacro) {
if (vipAddressMacro == null) {
return null;
}
String result = vipAddressMacro;
// 替換運算式
Matcher matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);
while (matcher.find()) {
String key = matcher.group(1);
String value = DynamicPropertyFactory.getInstance().getStringProperty(key, "").get();logger.debug("att:" + matcher.group());
logger.debug(", att key:" + key);
logger.debug(", att value:" + value);
logger.debug("");
result = result.replaceAll("\\$\\{" + key + "\\}", value);
matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);
}
return result;}
-
第 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 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();
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 。一般情況下,不需要關註該類。
666. 彩蛋
涉及到配置,內容初看起來會比較多,慢慢理解後,就會變得很“囉嗦”,請保持耐心。
胖友,分享一個朋友圈可好。