Eureka Client啟動時撩穿,會根據application.yml屬性信息初始化配置。配置入口可查看EurekaClientAutoConfiguration類。如果指定了eureka.instance.instance-id
,就使用指定的參數作為InstanceId饱普,如果沒有指定运挫,會調用IdUtils這個工具類生成缺省的InstanceId状共。另外如果配置了eureka.instance.prefer-ip-address
,那么客戶端注冊到注冊中心時將決定是否采用ip來注冊, 如果為true將用eureka.instance.ip-address
指定的IP地址注冊谁帕。
eureka.instance后跟參數的寫法實際是比較靈活的峡继,spring會容錯多種形式,例如:
0 = "preferIpAddress"
1 = "prefer_ip_address"
2 = "prefer-ip-address"
3 = "preferipaddress"
4 = "PREFERIPADDRESS"
5 = "PREFER_IP_ADDRESS"
6 = "PREFER-IP-ADDRESS"
EurekaClient的注冊過程可以看另一篇SpringCloud學習筆記(一)-EurekaClient注冊過程
入口EurekaClientAutoConfiguration類路徑
spring-cloud-netflix-eureka-client-xx.jar/org.springframework.cloud.netflix.eureka/EurekaClientAutoConfiguration
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
ManagementMetadataProvider managementMetadataProvider) throws MalformedURLException {
PropertyResolver eurekaPropertyResolver = new RelaxedPropertyResolver(this.env, "eureka.instance.");
String hostname = eurekaPropertyResolver.getProperty("hostname");
//對應eureka.instance.prefer-ip-address配置
boolean preferIpAddress = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("preferIpAddress"));
boolean isSecurePortEnabled = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("securePortEnabled"));
String serverContextPath = propertyResolver.getProperty("server.contextPath", "/");
int serverPort = Integer.valueOf(propertyResolver.getProperty("server.port", propertyResolver.getProperty("port", "8080")));
Integer managementPort = propertyResolver.getProperty("management.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = propertyResolver.getProperty("management.contextPath");// nullable. should be wrapped into optional
Integer jmxPort = propertyResolver.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(serverPort);
//設置缺省的InstanceId
instance.setInstanceId(getDefaultInstanceId(propertyResolver));
instance.setPreferIpAddress(preferIpAddress);
if(isSecurePortEnabled) {
instance.setSecurePort(serverPort);
}
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = eurekaPropertyResolver.getProperty("statusPageUrlPath");
String healthCheckUrlPath = eurekaPropertyResolver.getProperty("healthCheckUrlPath");
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
serverContextPath, managementContextPath, managementPort);
if(metadata != null) {
instance.setStatusPageUrl(metadata.getStatusPageUrl());
instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
Map<String, String> metadataMap = instance.getMetadataMap();
if (metadataMap.get("management.port") == null) {
metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
}
}
setupJmxPort(instance, jmxPort);
return instance;
}
可以看到缺省InsanceId的生成是調用IdUtils這個工具類
類路徑
spring-cloud-commons-1.3.0.RELAEASE.jar/org.springframework.cloud.commons.util.IdUtils
public static String getDefaultInstanceId(PropertyResolver resolver) {
RelaxedPropertyResolver relaxed = new RelaxedPropertyResolver(resolver);
String vcapInstanceId = relaxed.getProperty("vcap.application.instance_id");
if (StringUtils.hasText(vcapInstanceId)) {
return vcapInstanceId;
}
String hostname = relaxed.getProperty("spring.cloud.client.hostname");
String appName = relaxed.getProperty("spring.application.name");
String namePart = combineParts(hostname, SEPARATOR, appName);
//如果沒有配置spring.application.instance_id,indexPart默認為server.port
String indexPart = relaxed.getProperty("spring.application.instance_id",
relaxed.getProperty("server.port"));
return combineParts(namePart, SEPARATOR, indexPart);
}
從源碼可以看出匈挖,這個方法作用主要是根據hostname碾牌、appName、端口等信息拼裝一個缺省的instanceId儡循。如果只配置了eureka.instance.prefer-ip-address
舶吗,而沒有配置spring.application.instance_id
,那么instanceId依然顯示hostname择膝,但是通過ip可以訪問誓琼。
最后返回結果
由此可見,instanceId的默認值是
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
根據個人對源碼的理解肴捉,以及實際測試腹侣,如果配置了spring.application.instance_id,會取代server.port齿穗,并不是拼接的方式(也可能是本人對源碼理解還不夠全面傲隶,如果有錯誤之處,還請不吝指教)窃页。在eureka監(jiān)控頁面可以看到instanceId跺株。
那么spring.cloud.client.hostname又是如何獲取的呢复濒?HostInfoEnvironmentPostProcessor類中可以看到對hostName和ipAddress的設置。
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
InetUtils.HostInfo hostInfo = getFirstNonLoopbackHostInfo(environment);
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("spring.cloud.client.hostname", hostInfo.getHostname());
map.put("spring.cloud.client.ipAddress", hostInfo.getIpAddress());
MapPropertySource propertySource = new MapPropertySource(
"springCloudClientHostInfo", map);
environment.getPropertySources().addLast(propertySource);
}
以上代碼中根據InetUtils工具類提供的方法查找第一個非回送主機信息
類路徑:
spring-cloud-commons-1.3.0.RELAEASE.jar/org.springframework.cloud.commons.util.InetUtils
public HostInfo findFirstNonLoopbackHostInfo() {
//查找合適的網絡地址信息
InetAddress address = findFirstNonLoopbackAddress();
if (address != null) {
//組裝成HostInfo對象乒省,主要是主機名和IP地址
return convertAddress(address);
}
//獲取的網絡信息為空就用默認的
HostInfo hostInfo = new HostInfo();
hostInfo.setHostname(this.properties.getDefaultHostname());
hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
return hostInfo;
}
接著看findFirstNonLoopbackAddress是如何查找合適的網絡接口地址的芝薇,包含了對多網卡情況下的網絡地址的選擇。
//根據InetUtils工具類提供的方法查找第一個非回送地址
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())) {
//獲取綁定到此網絡接口的InetAddress集合
for (Enumeration<InetAddress> addrs = ifc
.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress address = addrs.nextElement();
//判斷是否是IPV4作儿,并且不是回送地址洛二,并且不是被忽略的地址
if (address instanceof Inet4Address
&& !address.isLoopbackAddress()
&& !ignoreAddress(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 {
//如果沒有找到合適的網絡接口,使用JDK自帶的getLocalHost返回本機地址攻锰,
//也就是本機配置的hostname及/etc/hosts配置的映射地址
return InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
log.warn("Unable to retrieve localhost");
}
return null;
}