记一次处理Dubbo多网卡注册IP错误问题

场景介绍今天在Docker上部署服务时,启动都很成功,但是访问时却访问失败 。之前在本地启动、在测试服k8s上启动都很正常,为什么同样的代码、同样的docker镜像在docker上却有问题呢?真让人摸不着头脑 。
服务是部署在docker集群上,因为是部署测试,就准备了3台机器,由3台机器组成的一个集群 。服务是按业务划分,分别写在不同的docker-compose.yml文件中,最后通过docker stack 启动 。
dubbo的注册中心使用的是nacos 。
因为在本地测试正常,而且也在k8s上部署且访问正常,所以在docker环境启动也很顺利 。唯一不行的就是docker环境部署后部分接口访问报错 。
过程排查在接口访问失败后,立刻查看了服务日志,报错是dubbo接口调用超时 。错误如下:
org.Apache.dubbo.rpc.RpcException: Failed to invoke the method getExportedURLs in the service org.apache.dubbo.rpc.service.GenericService. Tried 1 times of the providers [172.18.0.3:20881] (1/1) from the registry 172.10.36.101:8848 on the consumer 172.19.0.7 using the dubbo version 2.7.17. 。。。。。省略一大堆 。。。。。error message is:Host is unreachable: /172.18.0.3:20881复制代码从报错日志来看,有个明显的错误是Host is unreachable 。现在是服务C调用服务A的dubbo接口调用不到,用我蹩脚的英文理解刚才错误信息是主机不可达 。
此时,有两个疑问出现在容量不够大的脑子里:

  1. 为什么服务C调用不了服务A?
  2. 172.18.0.3这个ip是什么东东,哪里来的 。
针对问题1,首先想到的是服务A没有注册到nacos里去,打开nacos控制台发现服务A是已经注册上去了的,并且发现注册的ip是172.18.0.3,刚好和问题2里的ip一致 。
记一次处理Dubbo多网卡注册IP错误问题

文章插图
 
既然服务已经正常注册,那就剩下172.18.0.3这个ip是哪里来的了 。通过docker exec命令进入服务A容器,使用命名ifconfig看下服务A的ip信息:
记一次处理Dubbo多网卡注册IP错误问题

文章插图
 
找到了,是服务A容器的一个网卡地址,不过这个容器怎么网卡?难道是因为多个网卡导致的吗?那为什么部署在k8s容器里没有问题?难道k8s里面没有多个网卡吗?又接着一连串的问号在脑海里出现了?
先看看,k8s里的ip信息吧 。通过kubectl exec登录容器, 查看ip信息ip addr 。嗯?只有两个,比docker里少了好多 。
记一次处理Dubbo多网卡注册IP错误问题

文章插图
 
看来是,dubbo注册的时候,选择网卡的时候,是有一定的机制的,选择的不是我想要的 。看看源码吧,到底是怎么选择的 。
源码分析Dubbo获取网卡地址的逻辑是在
org.apache.dubbo.common.utils.NETUtils类中getLocalAddress0方法 。
private static InetAddress getLocalAddress0() {InetAddress localAddress = null;// @since 2.7.6, choose the {@link NetworkInterface} firsttry {NetworkInterface networkInterface = findNetworkInterface();Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();while (addresses.hasMoreElements()) {Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());if (addressOp.isPresent()) {try {if (addressOp.get().isReachable(100)) {return addressOp.get();}} catch (IOException e) {// ignore}}}} catch (Throwable e) {logger.warn(e);}try {localAddress = InetAddress.getLocalHost();Optional<InetAddress> addressOp = toValidAddress(localAddress);if (addressOp.isPresent()) {return addressOp.get();}} catch (Throwable e) {logger.warn(e);}return localAddress;}复制代码这块代码的整体逻辑还是很好理解的:
  1. 查找所有的网卡
  2. 校验网卡对应的ip是否合适
  3. 如果找不到合适的ip,则设置ip为127.0.0.1
再看下Dubbo是如何校验ip是否合适的呢?对应方法为toValidAddress
private static Optional<InetAddress> toValidAddress(InetAddress address) {if (address instanceof Inet6Address) {Inet6Address v6Address = (Inet6Address) address;if (isPreferIPV6Address()) {return Optional.ofNullable(normalizeV6Address(v6Address));}}if (isValidV4Address(address)) {return Optional.of(address);}return Optional.empty();}复制代码先判断拿到的地址是否为ipv6,再看是否设置了优先选择ipv6,即有没有配置
JAVA.net.preferIPv6Addresses=true,我们项目配置的是java.net.preferIPv4Stack=true,所以走的是下面的逻辑 。再检测是否是合法的ipv4地址,拿到ip后,检查ip的网速,如果响应时间为100ms内,则把这个ip作为注册ip 。


推荐阅读