为了说明问题, 这里描述一下场景.
应用配置的ZK地址是zk.infuq.com, 通过DNS解析的IP是192.168.0.1, 因此应用连接到了ZK1
然后把DNS的映射关系改成如下图所示,让zk.infuq.com解析成ZK2的IP(192.168.0.2), 先关闭ZK1的服务(或者禁用2181端口的出入流量)过了1分钟再开启服务(目的就是让ZK1和应用断开连接),根据应用(Dubbo应用)的重连机制, 最后应用连接注册到ZK2上.
然而, 这样操作之后, 应用真的可以连接到ZK2上吗?
先说下答案, 根据应用服务器配置的zookeeper版本不同,应用服务器可能还会连接到ZK1上,也可能会连接到ZK2上.
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
如果使用的版本是3.4.9, 那么应用服务器会连接到ZK1上.
Dubbo服务在启动的过程中,会连接ZK,其中会进入org.apache.zookeeper.client.StaticHostProvider#StaticHostProvider代码,实例化StaticHostProvider.
public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) throws UnknownHostException {
for (InetSocketAddress address : serverAddresses) {
// ia == null
InetAddress ia = address.getAddress();
// 解析IP
InetAddress resolvedAddresses[] = InetAddress.getAllByName((ia!=null) ? ia.getHostAddress(): address.getHostName());
for (InetAddress resolvedAddress : resolvedAddresses) {
if (resolvedAddress.toString().startsWith("/")
&& resolvedAddress.getAddress() != null) {
this.serverAddresses.add(
new InetSocketAddress(InetAddress.getByAddress(
address.getHostName(),
resolvedAddress.getAddress()),
address.getPort()));
} else {
this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort()));
}
}
}
if (this.serverAddresses.isEmpty()) {
throw new IllegalArgumentException(
"A HostProvider may not be empty!");
}
Collections.shuffle(this.serverAddresses);
}
它会根据域名解析IP, 拿到配置的zk.infuq.com域名,解析IP(192.168.0.1).
解析出来的IP(192.168.0.1)最后封装成InetSocketAddress并放到serverAddresses集合中.
也就是说,不管是首次连接ZK还是重连ZK,都是从serverAddresses集合中取出地址进行连接ZK,而不会再重新解析IP.
如果是3.4.13版本
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
这个版本的逻辑是,如果需要连接ZK,都是调用下面的next方法获取地址. 而它每次都会解析IP,一旦DNS有变动,那么就会解析到新的IP地址.
public InetSocketAddress next(long spinDelay) {
...
InetSocketAddress curAddr = serverAddresses.get(currentIndex);
try {
// curHostString是域名,例如zk.infuq.com
String curHostString = getHostString(curAddr);
// 解析IP
List<InetAddress> resolvedAddresses = new ArrayList<InetAddress>(Arrays.asList(this.resolver.getAllByName(curHostString)));
if (resolvedAddresses.isEmpty()) {
return curAddr;
}
Collections.shuffle(resolvedAddresses);
// 返回地址
return new InetSocketAddress(resolvedAddresses.get(0), curAddr.getPort());
} catch (UnknownHostException e) {
return curAddr;
}
}
所以,最后结论如下图所示,根据不同的Zookeeper版本,应用会连接不同的ZK.