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监控页面可以看到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;
}
网友评论