Dubbo 网卡地址注册时的一点思考

1 Dubbo 是怎么做的

       Dubbo 获取网卡地址的逻辑在各个版本中也是千回百转,走过弯路,也做过优化,我们用最新的 2.7.2-SNAPSHOT 版本来介绍,在看以下源码时,大家可以怀着质疑的心态去阅读,在 dubbo github 的 master 分支可以获取源码。获取 localhost 的逻辑位于

org.apache.dubbo.common.utils.NetUtils#getLocalAddress0() 之中

private static InetAddress getLocalAddress0() {    InetAddress localAddress = null;    // 首先尝试获取 /etc/hosts 中 hostname 对应的 IP    localAddress = InetAddress.getLocalHost();    Optional<InetAddress> addressOp = toValidAddress(localAddress);    if (addressOp.isPresent()) {        return addressOp.get();    }    // 没有找到适合注册的 IP,则开始轮询网卡    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();    if (null == interfaces) {        return localAddress;    }    while (interfaces.hasMoreElements()) {        NetworkInterface network = interfaces.nextElement();        Enumeration<InetAddress> addresses = network.getInetAddresses();        while (addresses.hasMoreElements()) {            // 返回第一个匹配的适合注册的 IP            Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());            if (addressOp.isPresent()) {                return addressOp.get();            }        }    }    return localAddress;}

Dubbo 这段选取本地地址的逻辑大致分成了两步

  1. 先去 /etc/hosts 文件中找 hostname 对应的 IP 地址,找到则返回;找不到则转 2

  2. 轮询网卡,寻找合适的 IP 地址,找到则返回;找不到返回 null,在 getLocalAddress0 外侧还有一段逻辑,如果返回 null,则注册 127.0.0.1 这个本地回环地址

     

 

首先强调下,这段逻辑并没有太大的问题,先别急着挑刺,让我们来分析下其中的一些细节,并进行验证。

1.1 尝试获取 hostname 映射 IP

Dubbo 首先选取的是 hostname 对应的 IP,在源码中对应的 InetAddress.getLocalHost(); 在 *nix 系统实际部署 Dubbo 应用时,可以首先使用 hostname 命令获取主机名

                      

紧接着在 /etc/hosts 配置 IP 映射,为了验证 Dubbo 的机制,我们随意为 hostname 配置一个 IP 地址

127.0.0.1 localhost 1.2.3.4 coreygdeMacBook-Pro.local

接着调用 NetUtils.getLocalAddress0() 进行验证,控制台打印如下:

1.2 判定有效的 IP 地址

在 toValidAddress 逻辑中,Dubbo 存在以下逻辑判定一个 IP 地址是否有效

private static Optional<InetAddress> toValidAddress(InetAddress address) {    if (address instanceof Inet6Address) {        Inet6Address v6Address = (Inet6Address) address;        if (isValidV6Address(v6Address)) {            return Optional.ofNullable(normalizeV6Address(v6Address));        }    }    if (isValidV4Address(address)) {        return Optional.of(address);    }    return Optional.empty();}

依次校验其符合 Ipv6 或者 Ipv4 的 IP 规范,对于 Ipv6 的地址,见如下代码:

static boolean isValidV6Address(Inet6Address address) {    boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");    if (!preferIpv6) {        return false;    }    try {        return address.isReachable(100);    } catch (IOException e) {        // ignore    }    return false;}

首先获取 java.net.preferIPv6Addresses 参数,其默认值为 false,鉴于大多数应用并没有使用 Ipv6 地址作为理想的注册 IP,这问题不大,紧接着通过 isReachable 判断网卡的连通性。例如一些网卡可能是 VPN/虚拟网卡的地址,如果没有配置路由表,往往无法连通,可以将之过滤。

对于 Ipv4 的地址,见如下代码:

static boolean isValidV4Address(InetAddress address) {    if (address == null || address.isLoopbackAddress()) {        return false;    }    String name = address.getHostAddress();    boolean result = (name != null            && IP_PATTERN.matcher(name).matches()            && !Constants.ANYHOST_VALUE.equals(name)            && !Constants.LOCALHOST_VALUE.equals(name));    return result;}

对比 Ipv6 的判断,这里我们已经发现前后不对称的情况了

  • Ipv4 相比 Ipv6 的逻辑多了 Ipv4 格式的正则校验、本地回环地址校验、ANYHOST 校验

  • pv4 相比 Ipv6 的逻辑少了网卡连通性的校验

大家都知道,Ipv4 将 127.0.0.1 定为本地回环地址, Ipv6 也存在回环地址:0:0:0:0:0:0:0:1 或者表示为 ::1。改进建议也很明显,我们放到文末统一总结。

1.3 轮询网卡

如果上述地址获取为 null 则进入轮询网卡的逻辑(例如 hosts 未指定 hostname 的映射或者 hostname 配置成了 127.0.0.1 之类的地址便会导致获取到空的网卡地址),轮询网卡对应的源码是 NetworkInterface.getNetworkInterfaces() ,这里面涉及的知识点就比较多了,支撑起了我写这篇文章的素材,Dubbo 的逻辑并不复杂,进行简单的校验,返回第一个可用的 IP 即可。

性子急的读者可能忍不住了,多网卡!合适的网卡可能不止一个,Dubbo 怎么应对呢?按道理说,我们也替 Dubbo 说句公道话,客官要不你自己指定下?我们首先得对多网卡的场景达成一致看法,才能继续把这篇文章完成下去:我们只能尽可能过滤那些“不对”的网卡。Dubbo 看样子对所有网卡是一视同仁了,那么是不是可以尝试优化一下其中的逻辑呢?

许多开源的服务治理框架在 stackoverflow 或者其 issue 中,注册错 IP 相关的问题都十分高频,大多数都是轮询网卡出了问题。既然事情发展到这儿,势必需要了解一些网络、网卡的知识,我们才能过滤掉那些明显不适合 RPC 服务注册的 IP 地址了。

2 Ifconfig 介绍

我并没有想要让大家对后续的内容望而却步,特地选择了这个大家最熟悉的 Linux 命令!对于那些吐槽:“天呐,都 2020 年了,你怎么还在用 net-tools/ifconfig,iproute2/ip 了解一下”的言论,请大家视而不见。无论你使用的是 mac,还是 linux,都可以使用它去 CRUD 你的网卡配置。

 

2.1 常用指令

启动关闭指定网卡:

ifconfig eth0 up
ifconfig eth0 down

ifconfig eth0 up 为启动网卡 eth0,ifconfig eth0 down 为关闭网卡 eth0。ssh 登陆 linux 服务器操作的用户要小心执行这个操作了,千万不要蠢哭自己。不然你下一步就需要去 google:“禁用 eth0 网卡后如何远程连接 Linux 服务器” 了。

 

为网卡配置和删除IPv6地址:

ifconfig eth0 add 33ffe:3240:800:1005::2/64    #为网卡eth0配置IPv6地址
ifconfig eth0 del 33ffe:3240:800:1005::2/64    #为网卡eth0删除IPv6地址

用 ifconfig 修改 MAC 地址:

ifconfig eth0 hw ether 00:AA:BB:CC:dd:EE

 

配置 IP 地址:

[root@localhost ~]# ifconfig eth0 192.168.2.10[root@localhost ~]# ifconfig eth0 192.168.2.10 netmask 255.255.255.0[root@localhost ~]# ifconfig eth0 192.168.2.10 netmask 255.255.255.0 broadcast 192.16

启用和关闭arp协议:

ifconfig eth0 mtu 1500    #设置能通过的最大数据包大小为 1500 bytes

2.2 查看网卡信息

在一台 centos 上执行 ifconfig -a

eth0      Link encap:Ethernet  HWaddr 52:54:00:a9:5f:ae          inet addr:10.154.30.130  Bcast:10.154.63.255  Mask:255.255.192.0          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1          RX packets:149673 errors:0 dropped:0 overruns:0 frame:0          TX packets:152271 errors:0 dropped:0 overruns:0 carrier:0          collisions:0 txqueuelen:1000          RX bytes:15205083 (15.2 MB)  TX bytes:21386362 (21.3 MB)lo        Link encap:Local Loopback          inet addr:127.0.0.1  Mask:255.0.0.0          UP LOOPBACK RUNNING  MTU:65536  Metric:1          RX packets:0 errors:0 dropped:0 overruns:0 frame:0          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0          collisions:0 txqueuelen:1          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)          docker0   Link encap:Ethernet  HWaddr 02:42:58:45:c1:15          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0          UP BROADCAST MULTICAST  MTU:1500  Metric:1          RX packets:0 errors:0 dropped:0 overruns:0 frame:0          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0          collisions:0 txqueuelen:0          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)tun0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00          UP POINTOPOINT NOARP MULTICAST  MTU:1500  Metric:1          RX packets:0 errors:0 dropped:0 overruns:0 frame:0          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0          collisions:0 txqueuelen:100          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth0 表示第一块网卡, 其中 HWaddr 表示网卡的物理地址,可以看到目前这个网卡的物理地址(MAC 地址)是 02:42:38:52:70:54

 

inet addr 用来表示网卡的 IP 地址,此网卡的 IP 地址是 10.154.30.130,广播地址, Bcast: 172.18.255.255,掩码地址 Mask:255.255.0.0

 

lo 是表示主机的回环地址,这个一般是用来测试一个网络程序,但又不想让局域网或外网的用户能够查看,只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD 服务器的指定到回坏地址,在浏览器输入 127.0.0.1 就能看到你所架构的 WEB 网站了。但只有你能看得到,局域网的其它主机或用户则无从知晓。

 

第一行:连接类型:Ethernet(以太网)HWaddr(硬件mac地址)

 

第二行:网卡的IP地址、子网、掩码

 

第三行:UP(代表网卡开启状态)RUNNING(代表网卡的网线被接上)MULTICAST(支持组播)MTU:1500(最大传输单元):1500字节(ifconfig 不加 -a 则无法看到 DOWN 的网卡)

 

第四、五行:接收、发送数据包情况统计

 

第七行:接收、发送数据字节数统计信息。

紧接着的两个网卡 docker0,tun0 是怎么出来的呢?我在我的 centos7 上装了 docker 和 openvpn。这两个东西应该是日常干扰我们做服务注册时的罪魁祸首了,当然,也有可能存在 eth1 这样的第二块网卡。ifconfig -a 看到的东西就对应了 JDK 的 api :NetworkInterface.getNetworkInterfaces() 。我们简单做个总结,大致有三个干扰因素

  • 以 docker 网桥为首的虚拟网卡地址,毕竟这东西这么火,怎么也得单独列出来吧?

  • 以 TUN/TAP 为代表的虚拟网卡地址,多为 VPN 场景

  • 以 eth1 为代表的多网卡场景,有钱就可以装多网卡了!

 

干扰因素二:多网卡

7 MAC 下的差异

coreydeMacBook-Pro:dubbo-in-action corey$ ifconfig -alo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384  options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>  inet 127.0.0.1 netmask 0xff000000  inet6 ::1 prefixlen 128  inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1  nd6 options=201<PERFORMNUD,DAD>gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280stf0: flags=0<> mtu 1280XHC0: flags=0<> mtu 0XHC20: flags=0<> mtu 0en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500  ether 88:e9:fe:88:a0:76  inet6 fe80::1cab:f689:60d1:bacb%en0 prefixlen 64 secured scopeid 0x6  inet 30.130.11.242 netmask 0xffffff80 broadcast 30.130.11.255  nd6 options=201<PERFORMNUD,DAD>  media: autoselect  status: activep2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304  ether 0a:e9:fe:88:a0:76  media: autoselect  status: inactiveawdl0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1484  ether 66:d2:8c:8c:dd:85  inet6 fe80::64d2:8cff:fe8c:dd85%awdl0 prefixlen 64 scopeid 0x8  nd6 options=201<PERFORMNUD,DAD>  media: autoselect  status: activeen1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500  options=60<TSO4,TSO6>  ether aa:00:d0:13:0e:01  media: autoselect <full-duplex>  status: inactiveen2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500  options=60<TSO4,TSO6>  ether aa:00:d0:13:0e:00  media: autoselect <full-duplex>  status: inactivebridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500  options=63<RXCSUM,TXCSUM,TSO4,TSO6>  ether aa:00:d0:13:0e:01  Configuration:    id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0    maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200    root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0    ipfilter disabled flags 0x2  member: en1 flags=3<LEARNING,DISCOVER>          ifmaxaddr 0 port 9 priority 0 path cost 0  member: en2 flags=3<LEARNING,DISCOVER>          ifmaxaddr 0 port 10 priority 0 path cost 0  nd6 options=201<PERFORMNUD,DAD>  media: <unknown type>  status: inactiveutun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000  inet6 fe80::3fe0:3e8b:384:9968%utun0 prefixlen 64 scopeid 0xc  nd6 options=201<PERFORMNUD,DAD>utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380  inet6 fe80::7894:3abc:5abd:457d%utun1 prefixlen 64 scopeid 0xd  nd6 options=201<PERFORMNUD,DAD>

 内容很多,我挑几点差异简述下

 

  • 内容展示形式不一样,没有 Linux 下的接收、发送数据字节数等统计信息

  • 真实网卡的命名不一样:eth0 -> en0

  • 虚拟网卡的命名格式不一样:tun/tap -> utun

8 Dubbo 改进建议(来自网络)

我们进行了以上探索,算是对网卡有一点了解了。回过头来看看 Dubbo 获取网卡的逻辑,是否可以做出改进呢?

Dubbo Action 1:

保持 Ipv4 和 Ipv6 的一致性校验。为 Ipv4 增加连通性校验;为 Ipv6 增加 LoopBack 和 ANYHOST 等校验。

Dubbo Action 2:

NetworkInterface network = interfaces.nextElement();
if (network.isLoopback() || network.isVirtual() || !network.isUp()) {
    continue;
}

JDK 提供了以上的 API,我们可以利用起来,过滤一部分一定不正确的网卡。


对于真实多网卡、内外网 IP 共存的场景,不能仅仅是框架侧在做努力,用户也需要做一些事,就像爱情一样,我可以主动一点,但你也得反馈,才能发展出故事

Dubbo User Action 1:

可以配置 /etc/hosts 文件,将 hostname 对应的 IP 显式配置进去。

Dubbo User Action 2:

可以使用启动参数去显式指定注册的 IP:

-DDUBBO_IP_TO_REGISTRY=1.2.3.4

也可以指定 Dubbo 服务绑定在哪块网卡上:

-DDUBBO_IP_TO_BIND=1.2.3.4

参考:

https://www.jianshu.com/p/09f9375b7fa7

https://stackoverflow.com/questions/29958143/what-are-en0-en1-p2p-and-so-on-that-are-displayed-after-executing-ifconfig

http://dubbo.apache.org/zh-cn/docs/user/quick-start.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值