https://blog.csdn.net/qq_30062125/article/details/83856655

1、前言
对于spring cloud,各个服务实例需要注册到Eureka注册中心。
一般会配置ip注册,即eureka.instance.prefer-ip-address=true。
但是,如果服务实例所在的环境存在多个网卡,经常会出现注册过去的ip不是我们想要的ip。

2、配置解决说明
针对上面的情况,我们一般有几种不同的解决思路。

2.1、方法一:直接配置eureka.instance.ip-address
如:eureka.instance.ip-address=192.168.1.7
直接配置一个完整的ip,一般适用于环境单一场景,对于复杂场景缺少有利支持。

具体实现可以参考org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean
和org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#getHostName,
这里不再描述。如果不清楚,可以先看下后面的源码逻辑分析,再回头来看下,思路类似。

2.2、方法二:增加inetutils相关配置
配置对应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
举例说明:
只使用以192.168.开头的ip,注意多个项是交集的关系,需要都满足。
spring.cloud.inetutils.preferred-networks=^192\.168\.[\d]+\.[\d]+$
1
使用/etc/hosts中主机名称映射的ip,这一种在docker swarm环境中比较好用。
# 随便配置一个不可能存在的ip,会走到InetAddress.getLocalHost()逻辑。
spring.cloud.inetutils.preferred-networks=none
1
2
排除网卡en0和en1
#ignored-interfaces配置的是正则表达式
spring.cloud.inetutils.ignored-interfaces=en0,en1
1
2
只使用内网地址
# 遵循 RFC 1918
# 10/8 前缀
# 172.16/12 前缀
# 192.168/16 前缀
spring.cloud.inetutils.use-only-site-local-interfaces=true
1
2
3
4
5
一般来说这几种就够用了。

3、源码分析
主要分析下为什么这样配置可以生效。会从最开始的自动配置入手往下看。如果要看ip相部分关,直接跳到指定目录即可。

这里使用的版本是spring-boot 1.5.13.RELEASE, spring cloud Dalston.SR5, 版本不同,会有一些差异。

3.1 服务端注册说明
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>

3.2 客户端发起注册说明
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
由于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);
}
1
2
3
4
5
6
7
8
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;
......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注册逻辑

/**
* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.3 ip解析配置说明
从上面可以看出来注册到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();
}
1
2
3
4
5
6
7
8
@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;
}
1
2
3
4
5
6
7
8
9
最终通过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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
3.3.1 忽略网卡
不匹配 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;
}
1
2
3
4
5
6
7
8
9
3.3.2 忽略ip
是否必须内网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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
内网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));
}
1
2
3
4
5
6
7
8
9
10
11
12
3.3.3 InetAddress.getLocalHost逻辑
当我们配置spring.cloud.inetutils.preferred-networks=none时,根据网卡是找不到匹配的ip的,就会走到InetAddress.getLocalHost()逻辑中。
逻辑步骤如下:

查找本地主机名称
如果主机名称是localhost直接返回地址,其中ipv4是127.0.0.1,ipv6是::1
如果不是,需要走本地域名解析。域名解析优先会获取本地hosts中配置的ip地址。当为主机名称配置了hosts,就会读取该配置的ip地址。
如果本地没找到,继续走域名解析。
到这里,spring cloud eureka client如何设置ip我们基本上已经理清楚了。好了,结束了。
————————————————
版权声明:本文为CSDN博主「这是一个懒人」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_30062125/article/details/83856655

spring cloud EurekaClient 多网卡 ip 配置 和 源码分析(转)的更多相关文章

  1. Spring Cloud Eureka(七):DiscoveryClient 源码分析

    1.本节概要 上一节文章主要介绍了Eureka Client 的服务注册的流程,没有对服务治理进行介绍,本文目的就是从源码角度来学习服务实例的治理机制,主要包括以下内容: 服务注册(register) ...

  2. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  3. Spring PropertyResolver 占位符解析(二)源码分析

    Spring PropertyResolver 占位符解析(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) ...

  4. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  5. Spring笔记(5) - 声明式事务@EnableTransactionManagement注解源码分析

    一.背景 前面详解了实现Spring事务的两种方式的不同实现:编程式事务和声明式事务,对于配置都使用到了xml配置,今天介绍Spring事务的注解开发,例如下面例子: 配置类:注册数据源.JDBC模板 ...

  6. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  7. Spring Cloud系列(三):Eureka源码解析之服务端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...

  8. Django基于Pycharm开发之四[关于静态文件的使用,配置以及源码分析](原创)

    对于django静态文件的使用,如果开发过netcore程序的开发人员,可能会比较容易理解django关于静态文件访问的设计原理,个人觉得,这是一个middlerware的设计,但是在django中我 ...

  9. [第十八篇]——Docker 安装 Node.js之Spring Cloud大型企业分布式微服务云架构源码

    Docker 安装 Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,是一个让 JavaScript 运行在服务端的开发平台. 1.查看可用的 N ...

随机推荐

  1. 【转】netty4.1.32 pipeline的添加顺序和执行顺序

    原文:https://www.cnblogs.com/ruber/p/10186571.html 本文只想讨论一下pipeline的执行顺序问题,因为这个搞不明白就不知道先添加编码还是解码,是不是可以 ...

  2. kotlin嵌套类

    就是类中定义类 package loaderman.demo class Outer { var name: String = "name" inner class inner { ...

  3. EasyUI中对datagrid的扩展方法

    以下是给datagrid扩展一个方法的demo 1.给datagrid添加一个属性 $.extend($.fn.datagrid.defaults, { demo: "demo1" ...

  4. Spring Aop(十二)——编程式的创建Aop代理之AspectjProxyFactory

    转发地址:https://www.iteye.com/blog/elim-2397922 编程式的创建Aop代理之AspectjProxyFactory 之前已经介绍了一款编程式的创建Aop代理的工厂 ...

  5. iOS-导航栏全透明效果, 只保留左右两个按钮以及NavigationController返回几级页面

    [self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[UIColor clearCo ...

  6. FFMPEG 常用命令行

    目录 1. 分离音视频 2. 解复用 3. 视频转码 4. 视频封装 5. 视频剪切 6. 视频录制 7.叠加水印 8.将MP3转换为PCM数据 9. 推送RTP流.接收RTP流并存为ts文件 10. ...

  7. 我是如何提高工作效率的-工具篇(二)-listary

    痛点: 还在为不能闪电速度搜索全盘文件.程序.无缝切换程序烦恼吗? 效果图: 功能1:搜索 各种文件.支持模糊搜索.全拼.文字首字母等模糊查询方式. (功能强大.尽情探索!)   痛点:文件好多啊,可 ...

  8. 攻防世界CRYPTO新手练习

    0x01 base64 直接base64 Decode 得到flag cyberpeace{Welcome_to_new_World!} 0x02 Caesar key为12 的恺撒密码,解密德fla ...

  9. gx_dlms 的杂乱记录

    DLMS_ERROR_CODE_FALSE W3Jehpnc543MuwUz6ZWDshy5kwbbE9Cw CGXDLMSClient::GetData(CGXByteBuffer& rep ...

  10. 24.Azkaban调度脚本的编写

    启动azkaban 在启动了server和excutor之后,在浏览器打开azkaban,会发现不能打开,日志报这个错误 at sun.security.ssl.InputRecord.handleU ...