问题产生场景:
Ephermal节点未及时删除导致provider不能恢复注册,在应用日志中,应用重连Zookeeper成功后,provider立刻进行了重新注册,之后便没有打印任何日志。而在ZK日志中,注册节点被删除后,并没有重新创建注册节点。
public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try{
doRegister(url);
} catch (Exception e) {
Throwable t = e;
boolean check = t instanceof SkipFailbackWrapperException;
if(check || skipFailback) {
if(skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register " + url + "to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(),t);
} else {
logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
failedRegistered.add(url);
}
}
Dubbo默认使用curator作为Zookeeper的客户端,curator与ZK是通过session未知链接的。当curator重连ZK时,若session未过期,则继续使用原session重新链接。而Ephemeral节点与session是绑定的关系,在session过期后,会删除此session下的Ephemeral节点。
继续对doRegister(url)的代码进行进一步排查,发现在CuratorZookeeperClient.createEphemeral(path)方法中有这么一段逻辑:在createEphemeral(path)捕获到了NodeExistsException,创建Ephemeral节点时,若此节点已存在,则认为Ephemeral创建成功。
但当ZK的session过期与删除Ephemeral节点不是原子性的操作情况下。在客户端得到session过期的消息时,session对应的Ephemeral节点可能还未被ZK删除。此时Dubbo去创建Ephemeral节点,发现节点仍然存在,所以不再创建新节点。当Ephemeral节点被ZK删除后,便会出现Dubbo认为重新注册成功但实际未成功的情况,会导致一部分的provider无法重新注册到ZK上。
public void createEphemeral(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
解决方案:
1.临时方案:手动重启provider。
2.升级Dubbo版本至2.7.3(需要检测此版本是否与线上环境兼容,目前此版本不与DubboX兼容)
3.修改源码,当Ephemeral节点捕获到NodeExistsException时,进行判断,若Ephemeral节点的SessionId与当前客户端的SessionId不同,则删除并重建Ephemeral节点。