暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

记一次Nacos注册时客户端IP的选择问题

左羊公社 2023-05-04
2469
主要问题:Nacos注册时,客户端多张网卡,IP选择造成的问题。
近期以写代码讨生活的左某写的API在调试时出现访问超时的状况,及时处理掉了问题。但左某因此受到三方联调人员的严重鄙视,为此左某决定记录本次异常状况以防再犯。
问题概述:事情这个样子的,最近左羊在服务一个基于SpringCloud微服务研发的产品,在发布一个服务供给前端人员调试时发生连接超时情况,通过排查F12开发者工具
发现访问我的服务时指向的IP为192.168.142.1
,再通过检测本机网卡列表发现其为VMware Network Adapter VMnet8
虚拟机的IP。最后通过在配置文件指定spring.cloud.nacos.discovery.ip
解决到该问题。以下信息为问题发生时的各种信息及个人粗浅理解nacos客户端注册IP的选择逻辑。
问题发生的情况
postman报错信息
{
 "code": 1,
 "msg""finishConnect(..) failed: Connection timed out: /192.168.142.1:3023",
 "data": null
}
本机注册信息
idea 控制台输出信息
2023-05-02 13:52:47.375  INFO 17964 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, DEFAULT_GROUP cloud-asset-management-biz 192.168.142.1:5017 register finished
本机网卡信息
win + r -- > cmd --> ipconfig
Windows IP 配置


以太网适配器 以太网 2:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :

以太网适配器 以太网:

   连接特定的 DNS 后缀 . . . . . . . : vlan10
   IPv4 地址 . . . . . . . . . . . . : 192.168.100.183
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.10.1

以太网适配器 以太网 3:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :

以太网适配器 VMware Network Adapter VMnet1:

   连接特定的 DNS 后缀 . . . . . . . :
   IPv4 地址 . . . . . . . . . . . . : 192.168.192.1
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . :

以太网适配器 VMware Network Adapter VMnet8:

   连接特定的 DNS 后缀 . . . . . . . :
   IPv4 地址 . . . . . . . . . . . . : 192.168.142.1
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . :

个人理解

在Nacos客户端注册服务时,选择IP的逻辑大体分为两类:手动指定IP和自动选择IP。
手动指定IP的逻辑非常简单,可以在代码中直接指定服务的IP地址,例如:
namingService.registerInstance(serviceName, "192.168.1.100", port);
在这种情况下,服务会始终使用指定的IP地址进行注册和发现。
自动选择IP的逻辑稍微复杂一些。Nacos客户端会选择一个合适的网络接口或IP地址进行注册和发现。选择哪个网络接口或IP地址需要满足以下规则:
  1. 如果手动指定了IP地址,则使用该地址进行服务注册和服务发现。
  2. 如果配置文件中指定了IP地址,则使用该地址进行服务注册和服务发现。
  3. 否则,Nacos客户端会遍历所有的本地IP地址,选择一个可用的地址进行注册和发现。选择的条件包括:
  • IP地址必须为IPv4格式;
  • IP地址不能是回环地址(127.0.0.1)或本地连接地址(169.254.X.X);
  • IP地址必须是可达的(即不在黑名单中)。
在选择IP地址后,Nacos客户端会将该地址作为服务的IP地址进行注册,其他客户端就可以通过该地址进行服务调用。

Nacos客户端IP自动选择

以下是 Nacos 客户端 IP 自动选择的核心代码:
public class UtilsAndCommons {

    // 根据 IP 地址排序
    @SuppressWarnings("unchecked")
    public static List<String> getIPList(String ipList) {
        String ipListString = IP_SPLIT_PATTERN.matcher(ipList).replaceAll(",");
        List<String> ips = new ArrayList(Arrays.asList(ipListString.split(",")));
        Collections.sort(ips, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                String[] arr1 = o1.split("\\.");
                String[] arr2 = o2.split("\\.");
                for (int i = 0; i < 4; i++) {
                    int seg1 = Integer.parseInt(arr1[i]);
                    int seg2 = Integer.parseInt(arr2[i]);
                    if (seg1 != seg2) {
                        return seg1 - seg2;
                    }
                }
                return 0;
            }
        });
        return ips;
    }

    // 获取本地 IP 地址列表
    public static List<String> getLocalIPList() {
        List<String> result = new ArrayList<String>();
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                if (ni.isLoopback()) {
                    continue;
                }
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr.isLoopbackAddress() || !(addr instanceof Inet4Address)) {
                        continue;
                    }
                    result.add(addr.getHostAddress());
                }
            }
        } catch (SocketException e) {
            Loggers.SRV_LOG.error("get local ip list error!" + e.getMessage(), e);
        }
        return result;
    }

    // 获取服务器 IP 地址列表
    public static List<String> getServerIps() {
        List<String> ips = new ArrayList<>();
        try {
            Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
            LOOP:
            while (nics.hasMoreElements()) {
                NetworkInterface nic = nics.nextElement();
                if (nic.isLoopback() || nic.isVirtual() || !nic.isUp()) {
                    continue;
                }
                Enumeration<InetAddress> addresses = nic.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr.isLoopbackAddress() || !(addr instanceof Inet4Address)) {
                        continue;
                    }
                    ips.add(addr.getHostAddress());
                    if (NetUtils.canResolved(addr.getHostName())) {
                        ips.add(addr.getHostName());
                    }
                }
            }
        } catch (SocketException e) {
            Loggers.SRV_LOG.warn("failed to retrieve the serverIp! {}", e.getMessage());
        }
        return ips;
    }

    // 根据 IP 地址获取网段
    public static String getIPSegment(String ipAddr) {
        String[] arr = ipAddr.split("\\.");
        if (arr.length != 4) {
            return "";
        }
        return arr[0] + "." + arr[1] + "." + arr[2];
    }
}

核心代码主要包括:

  1. getIPList
    方法:根据 IP 地址排序。
  2. getLocalIPList
    方法:获取本地 IP 地址列表。
  3. getServerIps
    方法:获取服务器 IP 地址列表。
  4. getIPSegment
    方法:根据 IP 地址获取网段。
在选择注册 IP 地址时,将优先使用配置的 -Dnacos.bind.ip
参数,如果没有配置该参数,则根据触发条件选择 IP 地址。具体流程如下:
  1. 如果 nacos-client 与 nacos-server 在同一台机器上运行,则优先选择本地 IP 地址,即从 getLocalIPList
    方法返回的 IP 地址列表中选择一个 IP 地址。
  2. 如果 nacos-client 与 nacos-server 不在同一台机器上运行,则选择非本地 IP 地址。选择的规则为:假设 nacos-client 获取到的本地 IP 地址为 IP1,nacos-server 获取到的本地 IP 地址为 IP2,则选择 IP2 所在的网段中距离 IP1 最近(按照 IP 地址排序)的一个 IP 地址。如果不存在则选择 IP2。
  3. 如果没有选择到合适的 IP 地址,则会使用 0.0.0.0 地址。
参考文献:
1. Nacos官方文档:https://nacos.io/zh-cn/docs/quick-start.html
2. Nacos源码 https://github.com/alibaba/nacos
感谢你的观看,YES!

文章转载自左羊公社,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论