問題産生場景:
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節點。