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; }