[Zookeeper学习笔记之三]Zookeeper实例创建和会话建立的异步特性

为了说明问题,看个简单的代码,

 

    import org.apache.zookeeper.*;  
      
    import java.io.IOException;  
    import java.util.concurrent.CountDownLatch;  
    import java.util.concurrent.ThreadLocalRandom;  
      
    public class ZKApplication implements Watcher {  
        private static final int SESSION_TIMEOUT = 3000;  
        private volatile static boolean shutdown;  
        private ZooKeeper zk;  
        private CountDownLatch connectedSignal = new CountDownLatch(1);  
      
        public void connect(String hosts) throws IOException, InterruptedException { 
        try {
            System.out.println("Start to create the Zookeeper instance");
            zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
            new Thread(new Runnable() {
                int count = 0;
                @Override
                public void run() {
                    while (count++ <= 1000) {
                        System.out.println(zk.getState());
                        try {

                            Thread.currentThread().sleep(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }).start();
        } catch(IOException e) {
            e.printStackTrace();
            throw e;
        }
       connectedSignal.await(); //Wait for the SyncConnected event occurs and process has been called, which will stop the waiting here.    }
        }  
      
        @Override  
        public void process(WatchedEvent event) { // Watcher interface  
            if (event.getState() == Event.KeeperState.SyncConnected) {  
                connectedSignal.countDown();  //connectedSignal.await() will no longer wait
            }  
        }  
      
        public void create(String groupName) throws KeeperException,  
                InterruptedException {  
            String path = "/" + groupName;  
            String createdPath = zk.create(path, null/*data*/, ZooDefs.Ids.OPEN_ACL_UNSAFE,  
                    CreateMode.EPHEMERAL); //The znode will be deleted upon the session is closed.  
            System.out.println("Created " + createdPath);  
        }  
      
        public void close() throws InterruptedException {  
            zk.close();  
        }  
      
        public static void main(String[] args) throws Exception {  
            final ZKApplication createGroup = new ZKApplication();  
            String groupName = "zoo" + ThreadLocalRandom.current().nextInt();  
            createGroup.connect(Host.HOST);  
            createGroup.create(groupName);  
            createGroup.close();  
        }  
    }  

 

    上面的代码在Zookeeper没有启动的情况下,控制台输出1000次CONNECTING,也不退出,原因是已经发生了死锁问题。  引发死锁问题的是同步闭锁connectedSignal的使用,代码中的含义是客户端在connect方法中发起链接,然后connect一直等待直到 Watcher的process被回调将闭锁计数置零,通常这个没有问题,可是当Zookeeper压根没有启动的时候,这个代码会陷入死锁:

  1. 死锁发生在客户端阻塞等待于connectedSignal的await方法上,发生阻塞说明connectedSignal的计数没有置零,没有置零说明process没有调用,因为Zookeeper没有启动,所以说process没有被调用是合理的。代 码死锁于connectSignal的await方法上,那么意思是说,Zookeeper对象构造和会话建立过程应该是异步的,Zookeeper构造 方法返回后,另外一个会话创建线程会尝试建立会话,从代码的输出可以看出,会话状态一直是Connecting状态(试图创建会话,但尚未创建成功的状 态),Zookeeper的Javadoc清楚的说到Zookeeper实例和会话建立是异步的过程: Session establishment is asynchronous. This constructor will initiate connection to the server and return immediately - potentially (usually) before the session is fully established. The watcher argument specifies  the watcher that will be notified of any changes in state. This notification can come at any point before or after the constructor call has returned.
  2. 会 话建立的失败策略,比较直观的做法时当建立会话不成功时,第一时间通知客户,给客户以失败快速响应的机制。。。不过Zookeeper似乎采用另外一种策 略,连接不成功不告诉你,只有当调用Zookeeper的API时,才会真正的把连接未建立的异常抛给客户端,这么做的好处是在Zookeeper实例创 建但是由于连接不成功而处于Connecting状态时,如果在用户调用Zookeeper的API做具体的事情之前,Zookeeper恢复了,那么客 户端再调用Zookeeper的API进行操作时,跟Zookeeper一直处于健康的状态一样,这也体现了Zookeeper的高可用性。
  3. 会话的建立和会话的状态管理是在单独的线程中

 

了解了Zookeeper对象实例化和会话建立的异步性,一方面可以了解Zookeeper的设计策略,另一方面,也可以避免一些代码和同步策略导致的陷阱,比如代码中那样。

 

 

虽然代码中主线程已经阻塞于connect方法,可是当Zookeeper服务器启动后,Client会建立连接,然后回调Watcher的process方法,此时就会解锁,从这个角度上来说,这里的死锁应该不叫事,应该启动这个程序目的就是要有Zookeeper在运行才好,如果Zookeeper之后断了,connectionsignal这个闭锁已经不再起作用。

 

 

 

 

 

 

 

你可能感兴趣的:(zookeeper)