最近项目在HA环境下,dubbo注册服务解析出问题了,dubbo目前都是单机的,比如服务器A吧,配置的物理IP地址是192.168.211.218,但dubbo解析出来的ip地址是192.168.211.215(某个虚拟IP地址),如下:
这台机器上配置了很多歌虚拟IP,用于HA环境中通过keepalived去切换比如mysql、memcached等服务。这样错误的解析结果导致service一直无法被访问到,调用方一直在报错。
造成这种结果的原因是什么呢?可以先看下dubbo解析IP地址的源码,我截取其中关键的片段如下:
ServiceConfig类下的doExportUrlsFor1Protocol方法
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { ...................省略...................... //1.先从ProtocolConfig中获取host String host = protocolConfig.getHost(); //2.如果host为空,再从ProviderConfig中获取host if (provider != null && (host == null || host.length() == 0)) { host = provider.getHost(); } boolean anyhost = false; //3.验证host是否为本地可用的host,如果是本地host(0.0.0.0或者localhost或者127.0.0.1之类)则继续解析 if (NetUtils.isInvalidLocalHost(host)) { anyhost = true; try { //4.通过InetAddress的方式获取host,默认读取本机hosts中hostname对应的ip地址 host = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { logger.warn(e.getMessage(), e); } if (NetUtils.isInvalidLocalHost(host)) { if (registryURLs != null && registryURLs.size() > 0) { for (URL registryURL : registryURLs) { try { Socket socket = new Socket(); try { //5.通过SocketAddress的方式获取host,一般情况下解析到此处就可以得到正确的本地ip,但是因为我配置了很多虚拟ip,所以这里导致了解析异常 SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort()); socket.connect(addr, 1000); host = socket.getLocalAddress().getHostAddress(); break; } finally { try { socket.close(); } catch (Throwable e) {} } } catch (Exception e) { logger.warn(e.getMessage(), e); } } } //6.遍历本地网卡,返回一个合理的host if (NetUtils.isInvalidLocalHost(host)) { host = NetUtils.getLocalHost(); } } } ........................省略................................ }
验证是否为本地可用IP地址的方法
public static boolean isInvalidLocalHost(String host) { return host == null || host.length() == 0 || host.equalsIgnoreCase("localhost") || host.equals("0.0.0.0") || (LOCAL_IP_PATTERN.matcher(host).matches()); }
其他的源码有兴趣大家自己点进去看下,我这里大概分析下原因:
dubbo解析IP地址的步骤如下:
1. 先从ProtocolConfig中取host
2. 再从ProviderConfig中取host
3. 若取出的是本地host, 则继续取host
4. 通过InetAddress.getLocalHost().getHostAddress();的方式获取Host
5. 通过Socket的方式尝试连接到注册中心,通过socket.getLocalAddress().getHostAddress()获取Host
6. 遍历本地网卡, 返回第一个合理的Host
第4步,就是取hostname,然后通过DNS把hostname解析成对应的IP,在我HA环境中,hostname对应的ip地址是127.0.0.1,如下:
dubbo解析到ip为127.0.0.1后他会放弃使用,继续使用后面的解析方式,他会再尝试连接zookeeper,然后用socket返回的本地地址,这个地址就是socket自己选择的了,所以他选择了一个虚拟IP地址。为什么在单机环境中没有出现这个问题呢,因为单机环境没有配置虚拟IP,即使dubbo根据hostname解析不到具体的IP,使用socket方式依然能够拿到正确的ip地址。
解决方法:
配置本机主机名,在hosts中加入本机名和本机物理ip的映射关系