问题概述
使用客户端连接ZooKeeper过程中有时会因为网络延时的原因跟ZooKeeper集群断开连接。一旦断开的时间超过了配置文件中规定的时间,ZooKeeper集群就会设置sessionId过期,并清空该客户端产生的临时数据。而即使后来该客户端又重新连接到了ZooKeeper集群,ZooKeeper集群会发送一个Expired事件通知客户端会话已经过期。
问题的解决
要重新连接到ZooKeeper集群,只需要重新new一个ZooKeeper对象,重新产生一个会话即可。但是需要注意,由于这个会话是一个全新的会话,过期会话产生的临时信息也不会被恢复了。因此如果需要保持一些临时信息在ZooKeeper集群中不丢失,我们的代码要妥善的处理这个问题。
在我之前的Mrpc框架中,ServerRegister类需要保持注册的服务器地址信息,让其保存在ZooKeeper集群中。由于我创建的是临时节点,一旦会话过期,ZooKeeper会将这些信息全部删除。因此必须监听会话的状态,一旦收到会话过期通知,就要重新连接ZooKeeper集群,并在连接成功后依次检查已创建的信息项目,如果有项目不存在就再次重新创建。
重点代码
// 处理zk事件
public void process(WatchedEvent event) {
System.out.println("register:------" + event);
if(event.getState() == Event.KeeperState.SyncConnected){
latch.countDown();
try {
// 逐个查找之前创建过的临时节点,不存在的重新创建
Map<String,String> newTempNodes = new HashMap<String,String>();
for(Map.Entry<String, String> entry:createdTempNodes.entrySet()){
Stat stat = zk.exists(entry.getKey(), null);
if(stat == null){
// 重新创建
String newNode = zk.create(entry.getKey(), entry.getValue().getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
newTempNodes.put(newNode, entry.getValue());
}else{
// 还存在,不需要重新创建
newTempNodes.put(entry.getKey(), entry.getValue());
}
}
createdTempNodes = newTempNodes;
} catch (Exception e) {
}
}
if(event.getState() == Event.KeeperState.Expired){
try {
// 会话过期,重新连接ZooKeeper集群
initZk();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
private void initZk() throws Exception {
latch = new CountDownLatch(1);
this.zk = new ZooKeeper(zkConnetionString, sessionTimeout, this);
latch.await(sessionTimeout + 100, TimeUnit.MILLISECONDS);
if(latch.getCount() > 0){
throw new RuntimeException("Can not connect to ZooKeeper cluster "
+ zkConnetionString + ", please check and try again later");
}
}