赞
踩
对于spring cloud,各个服务实例需要注册到Eureka注册中心。
一般会配置ip注册,即eureka.instance.prefer-ip-address=true。
但是,如果服务实例所在的环境存在多个网卡,经常会出现注册过去的ip不是我们想要的ip。
针对上面的情况,我们一般有几种不同的解决思路。
直接配置一个完整的ip,一般适用于环境单一场景,对于复杂场景缺少有利支持。
具体实现可以参考org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean
和org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#getHostName,
这里不再描述。如果不清楚,可以先看下后面的源码逻辑分析,再回头来看下,思路类似。
配置对应org.springframework.cloud.commons.util.InetUtilsProperties,其中包含:
配置 | 说明 |
---|---|
spring.cloud.inetutils.default-hostname | 默认主机名,只有解析出错才会用到 |
spring.cloud.inetutils.default-ip-address | 默认ip地址,只有解析出错才会用到 |
spring.cloud.inetutils.ignored-interfaces | 配置忽略的网卡地址,多个用,分割 |
spring.cloud.inetutils.preferred-networks | 正则匹配的ip地址或者ip前缀,多个用,分割,是交集的关系 |
spring.cloud.inetutils.timeout-seconds | 计算主机ip信息的超时时间,默认1秒钟 |
spring.cloud.inetutils.use-only-site-local-interfaces | 只使用内网ip |
spring.cloud.inetutils.preferred-networks=^192\.168\.[\d]+\.[\d]+$
# 随便配置一个不可能存在的ip,会走到InetAddress.getLocalHost()逻辑。
spring.cloud.inetutils.preferred-networks=none
#ignored-interfaces配置的是正则表达式
spring.cloud.inetutils.ignored-interfaces=en0,en1
# 遵循 RFC 1918
# 10/8 前缀
# 172.16/12 前缀
# 192.168/16 前缀
spring.cloud.inetutils.use-only-site-local-interfaces=true
一般来说这几种就够用了。
主要分析下为什么这样配置可以生效。会从最开始的自动配置入手往下看。如果要看ip相部分关,直接跳到指定目录即可。
这里使用的版本是spring-boot 1.5.13.RELEASE, spring cloud Dalston.SR5, 版本不同,会有一些差异。
eureka server常用api说明见:https://blog.csdn.net/qq_30062125/article/details/83829357
服务端代码从@EnableEurekaServer入口,api使用了Jersey实现,,其中使用到了子资源加载器。具体可以参考:https://blog.csdn.net/qq_30062125/article/details/83758334
我们主要关心应用实例注册时候传递的hostname,如: <hostName>192.168.1.7</hostName>
spring boot eureka client 客户端逻辑可以参考 https://blog.csdn.net/qq_30062125/article/details/83833006
我们这里关心的是org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean,这是服务实例的信息。
@Bean @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT) public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) { RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(env, "eureka.instance."); String hostname = relaxedPropertyResolver.getProperty("hostname"); boolean preferIpAddress = Boolean.parseBoolean(relaxedPropertyResolver.getProperty("preferIpAddress")); // ip解析主要在这一步 EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(this.nonSecurePort); instance.setInstanceId(getDefaultInstanceId(this.env)); instance.setPreferIpAddress(preferIpAddress); if (this.managementPort != this.nonSecurePort && this.managementPort != 0) { if (StringUtils.hasText(hostname)) { instance.setHostname(hostname); } String statusPageUrlPath = relaxedPropertyResolver.getProperty("statusPageUrlPath"); String healthCheckUrlPath = relaxedPropertyResolver.getProperty("healthCheckUrlPath"); if (StringUtils.hasText(statusPageUrlPath)) { instance.setStatusPageUrlPath(statusPageUrlPath); } if (StringUtils.hasText(healthCheckUrlPath)) { instance.setHealthCheckUrlPath(healthCheckUrlPath); } String scheme = instance.getSecurePortEnabled() ? "https" : "http"; instance.setStatusPageUrl(scheme + "://" + instance.getHostname() + ":" + this.managementPort + instance.getStatusPageUrlPath()); instance.setHealthCheckUrl(scheme + "://" + instance.getHostname() + ":" + this.managementPort + instance.getHealthCheckUrlPath()); } return instance; }
由于EurekaInstanceConfigBean类上面配置了@ConfigurationProperties(“eureka.instance”),所以生成bean的过程中,在ConfigurationPropertiesBindingPostProcessor逻辑中,会注入配置文件的配置参数。
这个bean会加工成InstanceInfo,保存到ApplicationInfoManager中,ApplicationInfoManager会注入到CloudEurekaClient。最终,在com.netflix.discovery.DiscoveryClient#DiscoveryClient逻辑中赋值,用于后续逻辑处理。\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.EurekaClientConfiguration#eurekaApplicationInfoManager 源码:
@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
public ApplicationInfoManager eurekaApplicationInfoManager(
EurekaInstanceConfig config) {
// 先加工成InstanceInfo
InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
return new ApplicationInfoManager(config, instanceInfo);
}
DiscoveryClient部分源码如下:
@Inject DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) { ...... this.applicationInfoManager = applicationInfoManager; // 前面放入的InstanceInfo InstanceInfo myInfo = applicationInfoManager.getInfo(); clientConfig = config; staticClientConfig = clientConfig; transportConfig = config.getTransportConfig(); // 赋值给instanceInfo,注册逻辑使用的参数 instanceInfo = myInfo; ...... }
注册逻辑
/** * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + appPathIdentifier + ": registering service..."); EurekaHttpResponse<Void> httpResponse; try { // instanceInfo就是上面赋值的实例信息 httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; }
从上面可以看出来注册到eureka的应用实例信息是通过EurekaInstanceConfigBean处理的。ip解析主要在 new EurekaInstanceConfigBean(inetUtils):
EurekaInstanceConfigBean构造方法
public EurekaInstanceConfigBean(InetUtils inetUtils) {
this.inetUtils = inetUtils;
// 主机信息获取,其中包含ip的解析
this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
// 这个地方ip地址是从hostInfo中获取的。
this.ipAddress = this.hostInfo.getIpAddress();
this.hostname = this.hostInfo.getHostname();
}
@Override
public String getHostName(boolean refresh) {
if (refresh && !this.hostInfo.override) {
this.ipAddress = this.hostInfo.getIpAddress();
this.hostname = this.hostInfo.getHostname();
}
// 获取host,如果配置了preferIpAddress,就用ip地址。
return this.preferIpAddress ? this.ipAddress : this.hostname;
}
最终通过org.springframework.cloud.commons.util.InetUtils#findFirstNonLoopbackAddress 实现
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())) { for (Enumeration<InetAddress> addrs = ifc .getInetAddresses(); addrs.hasMoreElements();) { InetAddress address = addrs.nextElement(); // ip忽略逻辑 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 { // 当规则匹配不到ip时候, 直接使用该逻辑获取信息。 return InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.warn("Unable to retrieve localhost"); } return null; }
不匹配 InetUtilsProperties#ignoredInterfaces配置正则表达式的网卡忽略掉
/** for testing */ boolean ignoreInterface(String interfaceName) {
for (String regex : this.properties.getIgnoredInterfaces()) {
if (interfaceName.matches(regex)) {
log.trace("Ignoring interface: " + interfaceName);
return true;
}
}
return false;
}
是否必须内网ip,通过org.springframework.cloud.commons.util.InetUtilsProperties#useOnlySiteLocalInterfaces控制。
是否匹配ip规则,交集的操作,必须匹配org.springframework.cloud.commons.util.InetUtilsProperties#preferredNetworks 配置的多条正则表达式,或者完全匹配开头,一条不符合,则忽略掉。
/** for testing */ boolean ignoreAddress(InetAddress address) { // 是否必须内网ip if (this.properties.isUseOnlySiteLocalInterfaces() && !address.isSiteLocalAddress()) { log.trace("Ignoring address: " + address.getHostAddress()); return true; } // 是否匹配ip,一条规则不匹配,就不匹配 for (String regex : this.properties.getPreferredNetworks()) { if (!address.getHostAddress().matches(regex) && !address.getHostAddress().startsWith(regex)) { log.trace("Ignoring address: " + address.getHostAddress()); return true; } } return false; }
内网ip规则校验
public boolean isSiteLocalAddress() {
// refer to RFC 1918
// 10/8 prefix
// 172.16/12 prefix
// 192.168/16 prefix
int address = holder().getAddress();
return (((address >>> 24) & 0xFF) == 10)
|| ((((address >>> 24) & 0xFF) == 172)
&& (((address >>> 16) & 0xF0) == 16))
|| ((((address >>> 24) & 0xFF) == 192)
&& (((address >>> 16) & 0xFF) == 168));
}
当我们配置spring.cloud.inetutils.preferred-networks=none时,根据网卡是找不到匹配的ip的,就会走到InetAddress.getLocalHost()逻辑中。
逻辑步骤如下:
到这里,spring cloud eureka client如何设置ip我们基本上已经理清楚了。好了,结束了。
微信扫一扫关注该公众号
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。