zoukankan      html  css  js  c++  java
  • spring cloud EurekaClient 多网卡 ip 配置 和 源码分析(转)

    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

  • 相关阅读:
    cassandra安装
    002 spring boot框架,引入mybatis-generator插件,自动生成Mapper和Entity
    033 SSM综合练习09--数据后台管理系统--基于AOP的日志处理
    032 SSM综合练习08--数据后台管理系统--jsp页面显示当前用户名
    031 SSM综合练习07--数据后台管理系统--用户详情查询
    030 SSM综合练习06--数据后台管理系统--SSM权限操作及Spring Security入门
    029 SSM综合练习05--数据后台管理系统--订单分页查询
    027 SSM综合练习03--数据后台管理系统--product-list.jsp和main.jsp页面制作
    Idea 目录结构下有红色波浪线
    virtualbox上,android x86 的分辨率的设置
  • 原文地址:https://www.cnblogs.com/xiaohanlin/p/11593667.html
Copyright © 2011-2022 走看看