从开发角度了解Zookeeper的工作原理及内部工作机制

来源:互联网 发布:php遍历对象数组 编辑:程序博客网 时间:2024/05/15 07:23

客户端ClientWatchManager,管理由ClientXncn产生的watchers和handle events在zookeeper的exists、getChildren、getData等这些API中可以注册watcher对象到ClientWatchManager中,create、setData、delete等这些引起zookeeper节点变化的API会触发watcher process的执行。

    服务端WatchManager,服务端的watcher对象管理器;
    注册watcher时候,会在服务端调用FinalRequestProcessor.processRequest,注册client对应的服务端连接对象ServerCnxn(实现了watcher接口)到DataTree中
这样在触发server端的watcher时,其实就是触发ServerCnxn的process方法,在ServerCnxn的process这个实现里会向对应的注册watcher对象的client发送notify消息,
而客户端会调用对应path注册的watcher对象的process方法
服务端注册watcher对象
FinalRequestProcessor.processRequest
             case OpCode.getData: {
        ...............................
                Stat stat = new Stat();
                byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
                        getDataRequest.getWatch() ? cnxn : null);//cnxn为ServerCnxn
                rsp = new GetDataResponse(b, stat);
     watcher的触发,WatchManager中的trigerWatch(String path,EvenType type),当server接受到例如createNode/deleteNode/setData等操作时,
将会操作ZKDatabase来操作DataTree中的数据,当然dataTree的数据改动,将会触发相应patch(节点)上的watch(有可能一个操作会导致多种watch被触发),
trigerWatch就是在这些时机下被调用。此操作中就是从watchManager中将相应path下注册的watch移除,并依次调用watch.process()。
此process()做了一件事情,就是向client发送一个nofication消息,此消息中包含一个WatchEvent对象,此对象封装了事件的类型/path等,

发送到EventThread中的waitingEvents队列中,EventThread后台线程从队列中拉取消息执行watcher中的process逻辑。

     在ServerCnxn处理请求时出现异常或者client关闭,将会导致ServerCnxn调用close()方法,此方法中有个分支操作就是从DataTree中的两种watches列表中删除其关联的watch。

阅读本文可以带着下面问题:
1.Zookeeper客户端有几部分组成?
2.那个模块管理所有网络IO的模块?
3.Watcher是否允许多个Client对一个或多个ZNode进行监控?
4.Zookeeper实例被创建时,会随之创建几个线程,各自是什么?
5.真正处理网络IO的是那个线程?
6.当网络出问题时ZK Client是如何处理的?
以下内容,如有新发现,欢迎讨论提高。


 

本文将在研究源码的技术上讲述ZK Client的工作原理及内部工作机制。
在看完ZK Client的大致架构以后,希望能有一种简单的方式描述ZK Client的基本结构,想来想去觉得还是图片比较能反映情况,咱们可以看下面图:


 

18180202-b52f0cda8caf4374bf6d267a701421a3.jpg(53.7 KB, 下载次数: 0)

下载附件  保存到相册

2014-2-16 00:30 上传



 

模块:
我们可以认为ZK的Client由三个主要模块组成:Zookeeper, WatcherManager, ClientCnxn
Zookeeper是ZK Client端的真正接口,用户可以操作的最主要的类,当用户创建一个Zookeeper实例以后,几乎所有的操作都被这个实例包办了,用户不用关心怎么连接到Server,Watcher什么时候被触发等等令人伤神的问题。
WatcherManager,顾名思义,它是用来管理Watcher的,Watcher是ZK的一大特色功能,允许多个Client对一个或多个ZNode进行监控,当ZNode有变化时能够通知到监控这个ZNode的各个Client。我们把一个ZK Client简单看成一个Zookeeper实例,那么这个实例内部的WatcherManager就管理了ZK Client绑定的所有Watcher。
ClientCnxn是管理所有网络IO的模块,所有和ZK Server交互的信息和数据都经过这个模块,包括给ZK Server发送Request,从ZK Server接受Response,以及从ZK Server接受Watcher Event。ClientCnxn完全管理了网络,从外部看来网络操作是透明的。


 

线程:
每当我们创建一个Zookeeper实例的时候,会有两个线程被创建:SendThread和EventThread。所以当我们使用ZK Client端的时候应该尽量只创建一个Zookeeper实例并反复使用。大量的创建销毁Zookeeper实例不仅会反复的创建和销毁线程,而且会在Server端创建大量的Session。
SendThread是真正处理网络IO的线程,所有通过网络发送和接受的数据包都在这个线程中处理。这个线程的主体是一个while循环:


 

  1. while (zooKeeper.state.isAlive()) {
  2.         try {
  3.             if (sockKey == null) {
  4.             // don’t re-establish connection if we are closing
  5.                 if (closing) {
  6.                     break;
  7.                 }
  8.                 startConnect();
  9.                 lastSend = now;
  10.                 lastHeard = now;
  11.             }
  12.             … ….
  13.             selector.select(to);
  14.             Set<SelectionKey> selected;
  15.             synchronized (this) {
  16.                 selected = selector.selectedKeys();
  17.             }
  18.             // Everything below and until we get back to the select is
  19.             // non blocking, so time is effectively a constant. That is
  20.             // Why we just have to do this once, here
  21.             now = System.currentTimeMillis();
  22.             for (SelectionKey k : selected) {
  23.                 … …
  24.                 if (doIO()) {
  25.                     lastHeard = now;
  26.                 }
  27.                 … …
  28.             }
  29.         }
  30.         catch() {
  31.             … …
  32.         }
  33.     }
复制代码
这里用了java的nio功能,当selector侦测到事件发生的时候就会触发一次循环,主要的操作会在doIO()里面完成:


 

  1. boolean doIO() throws InterruptedException, IOException {
  2.         boolean packetReceived = false;
  3.         SocketChannel sock = (SocketChannel) sockKey.channel();
  4.         if (sock == null) {
  5.             throw new IOException(“Socket is null!”);
  6.         }
  7.         if (sockKey.isReadable()) {
  8.             … …
  9.         }
  10.          
  11.         if (sockKey.isWritable()) {
  12.         … …
  13.         }

  14.         if (outgoingQueue.isEmpty()) {
  15.             disableWrite();
  16.         } else {
  17.             enableWrite();
  18.         }
  19.         return packetReceived;
  20.     }
复制代码
这个过程大概是这样的:


 

1. 如果有数据可读,则读取数据包,如果数据包是先前发出去的Request的Response,那么这个数据包一定在Pending Queue里面。将它从Pending Queue里面移走,并将此信息添加到Waiting Event Queue 里面,如果数据包是一个Watcher Event,将此信息添加到Waiting Event Queue里面。


 

2. 如果OutgoingQueue里面有数据需要发送,则发送数据包并把数据包从Outgoing Queue移至Pending Queue,意思是数据我已经发出去了,但还要等待Server端的回复,所以这个请求现在是Pending 的状态。


 

另外一个线程EventThread是用来处理Event的。前面提到SendThread从Server收到数据的时候会把一些信息添加到Event Thread里面,比如Finish Event和Watcher Event。EventThread就是专门用来处理这些Event的,收到Finish Event的时候会把相对应的Package置成Finish状态,这样等待结果的Client函数就能得以返回。收到Watcher Event的时候会联系WatcherManager找到相对应的Watcher,从WatcherManager里面移除这个Watcher(因为每个Watcher只会被通知一次) 并回调Watcher的process函数。所以所有Watcher的process函数是运行在EventThread里面的。


 

保持连接:


 

到目前为止应该已经大概介绍了ZK Client端的大致结构和处理流程。还剩下一个问题就是当网络出问题时ZK Client是如何处理的。其实这个过程并不复杂,大概是执行以下步骤:


 

1. 网络发生故障,网络操作抛出的异常被捕获。


 

2. 确认网络操作失败,清除当前与Server相关的网络资源,包括Socket等等。


 

3. 在Server列表中逐个尝试链接Server。


 

这个过程从外界看来是透明的,外界并不会觉察到ZK Client已经悄悄地更换了一个连接的Server。


 

好了,对于ZK Client的介绍大概就这么多了,希望这样的介绍对于大家学习和使用Zookeeper有一些帮助。对于文章中没有介绍或者没有说清楚的地方需要进一步查看源码来解决。

 

0 0
原创粉丝点击