ZooKeeper client and server source code to read incident watcher

It is with immense excitement to write this blog, if there is assistance, please give me a praise to you

The necessity of the presence watcher

For especially easy to understand example: If my project is based on dubbo + zookeeper build a distributed project, I have three functions the same service provider, with a zookeeper as a registration center, I have three projects registered into the zookeeper to Foreign Exposing services, but the question is, how to write java code to register into the zookeeper? of course join dependence, write the configuration file and then start to become, then, these three service providers that zookeeper body of the client, zookeeper more than one client, which I chose to rely on, which is the client, the light has not ah service provider, to provide services, I have need to service consumers ah, so in the same way, the consumers registered into zookeeper, zookeeper on the existence of a 4 node, that is, four clients, service consumers subscribe zookeeper, pull it to the address of the service provider, and then address cached locally , and then you can remotely call services to consumers, so problem again, in case Which service provider hung up, how to do it? zookeeper must notify the consumer is not it? address in case the day of the service provider becomes Is not it also have to inform consumers? This is the meaning of existence watcher, it solves it

Experimental scene:

Suppose we have successfully launched a zookeeper service and client, and added in advance watcher, then use the console to modify the dynamic node of data, we will find watcher callback phenomenon

Hook function code is added as follows:

public class ZookepperClientTest {
    public static void main(String[] args) throws Exception {
        ZooKeeper client = new ZooKeeper("localhost", 5000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.err.println("连接,触发");
            }
        });

    Stat stat = new Stat();
    
     //   todo 下面添加的事件监听器可是实现事件的消费订阅
      String content = new String(client.getData("/node1", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                // todo 任何连接上这个节点的客户端修改了这个节点的 data数据,都会引起process函数的回调

                // todo 特点1:  watch只能使用1次
                if (event.getType().equals(Event.EventType.NodeDataChanged)){
                    System.err.println("当前节点数据发生了改变");
                }
            }
        }, stat));

Look above code adds its own watcheris, client.getData("/node1", new Watcher() {}this is a callback hook function does not run when executed, will be executed, such as when certain conditions are met: node1 been deleted, node1 of the data has been modified

getData do what things?

Source follows: getData, the name suggests, the server returns the node data + stat, of course, is when the server node is changed after the call

Following several major mainstream work

  • createWatchRegistration wcb= new DataWatchRegistration(watcher, clientPath);
    • It is actually a simple internal type, the path and watch encapsulated into an object
  • Create a request, and initializerequest.head=getData=4
  • Call ClientCnxn.submitRequest (...), the presence of these packages further information
  • request.setWatch (watcher = null!); that he did not go in the watcher package, but only made a mark there watcher
 public byte[] getData(final String path, Watcher watcher, Stat stat)
        throws KeeperException, InterruptedException
     {
         // todo 校验path
        final String clientPath = path;
        PathUtils.validatePath(clientPath);

        // the watch contains the un-chroot path
        WatchRegistration wcb = null;
        if (watcher != null) {
            // todo DataWatchRegistration 继承了 WatchRegistration
            // todo DataWatchRegistration 其实就是一个简单的内部类,将path 和 watch 封装进了一个对象
            wcb = new DataWatchRegistration(watcher, clientPath);
        }

        final String serverPath = prependChroot(clientPath);
        // todo 创建一个请求头
        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.getData);

        // todo 创建了一个GetDataRequest
        GetDataRequest request = new GetDataRequest();
        // todo 给这个请求初始化,path 是传递进来的path,但是 watcher不是!!! 如果我们给定了watcher , 这里面的条件就是  true
        request.setPath(serverPath);
        request.setWatch(watcher != null); // todo 可看看看服务端接收到请求是怎么办的

        GetDataResponse response = new GetDataResponse();

        // todo 同样由 clientCnxn 上下文进行提交请求, 这个操作应该同样是阻塞的
         // todo EventThread 和 SendThread 同时使用一份 clientCnxn的 submitRequest()
        ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);

        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        if (stat != null) {
            DataTree.copyStat(response.getStat(), stat);
        }
        return response.getData();
    }

ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);I uninstalled the source code below, here came this method, one can see that it is still blocking style, and is further encapsulated packet requet

More importantly, queuePacket()the last argument of the method, there is a path + watcher we just create wrapper classes

public ReplyHeader submitRequest(RequestHeader h, Record request, Record response, WatchRegistration watchRegistration)
        throws InterruptedException {
    ReplyHeader r = new ReplyHeader();
    // todo 来到这个 queuePacket() 方法在下面, 这个方法就是将  用户输入-> string ->>> request ->>> packet 的过程
    Packet packet = queuePacket(h, r, request, response, null, null, null,
            null, watchRegistration);


    // todo 使用同步代码块,在下面的进行    同步阻塞等待, 直到有了Response响应才会跳出这个循环, 这个finished状态就是在客户端接受到服务端的
    // todo 的响应后, 将服务端的响应解析出来,然后放置到 pendingqueue里时,设置上去的
    synchronized (packet) {
        while (!packet.finished) {
            // todo 这个等待是需要唤醒的
            packet.wait();
        }
    }
    // todo 直到上面的代码块被唤醒,才会这个方法才会返回
    return r;
}

Similarly, in queuePacket () method will be submitted to the outgoingQueue packet, the final consumer is sent to the server seadThread

How to deal with server watchRegistration not empty packet

I am going to explain the follow-up process in standalone mode the server to process the request with an entire blog, so the only conclusion to say this blog

In the server, the user's request will eventually sequentially flows to three Processor, are

  • PrepRequestProcessor
    • Modify responsible for some of the state
  • SyncRequestProcessor
    • The transaction log synchronized to disk
  • Final RequestProcessor
    • The user's request

We go directly to FinalRequestProcessorthe public void processRequest(Request request) {}方法, look for him getData()to make a request which actions the way. Let's a small climax, zks.getZKDatabase().getData(getDataRequest.getPath(), stat, getDataRequest.getWatch() ? cnxn : null);whether to follow watcher of the server to add different Watcher

Really I have to draw the focus, and when I discovered this, I was super excited mood, as found in the New World

case OpCode.getData: {
        lastOp = "GETD";
        GetDataRequest getDataRequest = new GetDataRequest();
        ByteBufferInputStream.byteBuffer2Record(request.request,
                getDataRequest);
        DataNode n = zks.getZKDatabase().getNode(getDataRequest.getPath());
        if (n == null) {
            throw new KeeperException.NoNodeException();
        }
        PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().aclForNode(n),
                ZooDefs.Perms.READ,
                request.authInfo);
        Stat stat = new Stat();
        // todo 这里的操作    getDataRequest.getWatch() ? cnxn : null 对应可客户端的  跟进watcher有没有而决定往服务端传递 true 还是false 相关
        // todo 跟进去 getData()
        byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
                getDataRequest.getWatch() ? cnxn : null);
        //todo  cnxn的Processor()被回调, 往客户端发送数据 , 什么时候触发呢? 就是上面的  处理事务时的回调 第127行

        // todo 构建了一个 rsp ,在本类的最后面将rsp 响应给client
        rsp = new GetDataResponse(b, stat);
        break;
    }

Continue to follow up this getData()on the server maintains a map of the path + watcher

public byte[] getData(String path, Stat stat, Watcher watcher)
        throws KeeperException.NoNodeException {
    DataNode n = nodes.get(path);
    if (n == null) {
        throw new KeeperException.NoNodeException();
    }
    synchronized (n) {
        n.copyStat(stat);
        if (watcher != null) {
            // todo 将path 和 watcher 绑定在一起
            dataWatches.addWatch(path, watcher);
        }
        return n.data;
    }
}

Open a command line client, modify the server node status

Book connected to the back, when the client code to create a single ClientCnxntime, have the following logic, it opens the two daemon thread, sendThread responsible for sending a heartbeat to the server, the user has the relevant IO exchange and server, EventThread and is responsible for processing logic txn matters related to the rising level for the node

    // todo start就是启动了在构造方法中创建的线程
    public void start() {
        sendThread.start();
        eventThread.start();
    }

So far, clients have the following three threads

  • Responsible for handling user input commands in the console main thread
  • Daemon thread 1: seadThread
  • Daemon thread 2: eventThread

Logic code processing the user input portion of the main thread as follow:

The following code main logic processing command is input by the user, when the branch selected by the user in the end if-else determines what command input

According to our hypothetical scenario, the command entered by the user this is set /path newValueso, no doubt, after parsing code to perform the following stat = zk.setData(path, args[2].getBytes(),section

  // todo zookeeper客户端, 处理用户输入命令的具体逻辑
    // todo  用大白话讲,下面其实就是把 从控制台获取的用户的输入信息转换成指定的字符, 然后发送到服务端
    // todo MyCommandOptions 是处理命令行选项和shell脚本的工具类
    protected boolean processZKCmd(MyCommandOptions co) throws KeeperException, IOException, InterruptedException {
        // todo 在这个方法中可以看到很多的命令行所支持的命令
        Stat stat = new Stat();
        // todo 获取命令行输入中 0 1 2 3 ... 位置的内容, 比如 0 位置是命令  1 2 3 位置可能就是不同的参数
        String[] args = co.getArgArray();
        String cmd = co.getCommand();
        if (args.length < 1) {
            usage();
            return false;
        }

        if (!commandMap.containsKey(cmd)) {
            usage();
            return false;
        }

        boolean watch = args.length > 2;
        String path = null;
        List<ACL> acl = Ids.OPEN_ACL_UNSAFE;
        LOG.debug("Processing " + cmd);

        if (cmd.equals("quit")) {
            System.out.println("Quitting...");
            zk.close();
            System.exit(0);
        } else if (cmd.equals("set") && args.length >= 3) {
            path = args[1];
            stat = zk.setData(path, args[2].getBytes(),
                    args.length > 3 ? Integer.parseInt(args[3]) : -1);
            printStat(stat);

Continue to follow stat = zk.setData(path, args[2].getBytes(), the following logic is very simple, is to request the user to input the package come by ClientCnxnsubmission queue to submit a method of the class, to the consumer waiting sendThread

The purpose of submitRequest look at the last parameter is null, this parameter is WatchRegistration position, a start is set to null

 public Stat setData(final String path, byte data[], int version)
        throws KeeperException, InterruptedException
    {
        final String clientPath = path;
        PathUtils.validatePath(clientPath);

        final String serverPath = prependChroot(clientPath);

        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.setData);
        SetDataRequest request = new SetDataRequest();
        request.setPath(serverPath);
        request.setData(data);
        request.setVersion(version);
        
        SetDataResponse response = new SetDataResponse();
        ReplyHeader r = cnxn.submitRequest(h, request, response, null);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        return response.getStat();
    }

This follow-up submitRequest () method , the following source, not place material, it is likewise blocked live until the server in response to the

The current main logic of the code is to request encapsulated into the packet, and then add to ClintCnxn packet waiting in the queue maintained outgoingQueue consumption sendThread

The set came to this method is because we enter the console command triggered, the more important this packet is carried == null WatchRegistration , no doubt, taken out of service when the end FinalRequestProcessor reprocessing watcher == null, it will not save into the path + watcher in maptable

Important: sending transaction message

In FinalRequestProcessorthe public void processRequest(Request request) {}process, the following codes

//todo 请求头不为空
    if (request.hdr != null) {
        // 获取请求头
       TxnHeader hdr = request.hdr;
       // 获取事务
       Record txn = request.txn;
        // todo 跟进这个方法-----<--!!!!!!-----处理事务的逻辑,在这里面有向客户端发送事件的逻辑, 回调客户端的watcher----!!!!!!-->
       rc = zks.processTxn(hdr, txn);
    }

Continue with the inside

// todo 处理事物日志
public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) {
    ProcessTxnResult rc;
    int opCode = hdr.getType();
    long sessionId = hdr.getClientId();
    // todo 继续跟进去!!!!!!!!!
    // todo 跟进 processTxn(hdr, txn)
    rc = getZKDatabase().processTxn(hdr, txn);

Follow-up ZkDatabase.javaofprocessTxn(hdr, txn)方法

public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) {
    // todo 跟进 processTxn
    return dataTree.processTxn(hdr, txn);
}

To follow upDataTree.java

  public ProcessTxnResult processTxn(TxnHeader header, Record txn)
    {
        ProcessTxnResult rc = new ProcessTxnResult();

        try {
            rc.clientId = header.getClientId();
            rc.cxid = header.getCxid();
            rc.zxid = header.getZxid();
            rc.type = header.getType();
            rc.err = 0;
            rc.multiResult = null;
            switch (header.getType()) { // todo 根据客客户端发送过来的type进行switch,
                case OpCode.create:
                    CreateTxn createTxn = (CreateTxn) txn;
                    rc.path = createTxn.getPath();
                    // todo  跟进这个创建节点的方法
                    createNode(
                            createTxn.getPath(),

Based on the value of the request header, and then the judge went to switch branches, we are currently in the console triggered into setData branch as follows: follow this method you can see it is mainly made the following few things

  • Using the passed value of the new data replace the old
  • dataWatches.triggerWatch(path, EventType.NodeDataChanged);Trigger the specified event watch, what events it? NodeDataChange, which triggered a watcher of it? Check with the inside


    //todo  setData
    public Stat setData(String path, byte data[], int version, long zxid,
            long time) throws KeeperException.NoNodeException {
        Stat s = new Stat();
        DataNode n = nodes.get(path);
        if (n == null) {
            throw new KeeperException.NoNodeException();
        }
        byte lastdata[] = null;
        synchronized (n) {
            // todo 修改内存的数据
            lastdata = n.data;
            n.data = data;
            n.stat.setMtime(time);
            n.stat.setMzxid(zxid);
            n.stat.setVersion(version);
            n.copyStat(s);
        }
        // now update if the path is in a quota subtree.
        String lastPrefix;
        if((lastPrefix = getMaxPrefixWithQuota(path)) != null) {
          this.updateBytes(lastPrefix, (data == null ? 0 : data.length)
              - (lastdata == null ? 0 : lastdata.length));
        }
        // todo 终于 看到了   服务端 关于触发NodeDataChanged的事件
        dataWatches.triggerWatch(path, EventType.NodeDataChanged);
        return s;
    }

With the inside dataWatches.triggerWatch(path, EventType.NodeDataChanged);, the source code is as follows, deposit account when the main logic is taken is stored in the service side of the watch, then one by one callback their processor functions, the question is, in the end is what watcher it? I think we can add getData when the client starts () the wather, that is ServerCnxn


 // todo 跟进去服务端的 触发事件,  但是吧, 很纳闷. 就是没有往客户端发送数据的逻辑
    public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
        WatchedEvent e = new WatchedEvent(type,
                KeeperState.SyncConnected, path);
        HashSet<Watcher> watchers;
        synchronized (this) {
            watchers = watchTable.remove(path);
            if (watchers == null || watchers.isEmpty()) {
                if (LOG.isTraceEnabled()) {
                    ZooTrace.logTraceMessage(LOG,
                            ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                            "No watchers for " + path);
                }
                return null;
            }
            for (Watcher w : watchers) {
                HashSet<String> paths = watch2Paths.get(w);
                if (paths != null) {
                    paths.remove(path);
                }
            }
        }
        for (Watcher w : watchers) {
            if (supress != null && supress.contains(w)) {
                continue;
            }
            // todo 继续跟进去, 看它如何回调的
            w.process(e);
        }
        return watchers;
    }

Excited to go and see ServerCnxn the process () method to do what?

To achieve class NIOServerCnxn ServerCnxn indeed very excited to see the server to the client sending transactional messages, and new ReplyHeader (-1, -1L, 0) on the first position parameter is -1, which is it is important, because the client after receiving the flag xid = -1, it will put this process in response to EventThread

    @Override
    synchronized public void process(WatchedEvent event) {
        ReplyHeader h = new ReplyHeader(-1, -1L, 0);
        if (LOG.isTraceEnabled()) {
            ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                                     "Deliver event " + event + " to 0x"
                                     + Long.toHexString(this.sessionId)
                                     + " through " + this);
        }

        // Convert WatchedEvent to a type that can be sent over the wire
        WatcherEvent e = event.getWrapper();
        // todo  往服务端发送了 e event类型消息
        sendResponse(h, e, "notification");
    }

Processing the response using callbacks callbacks watch

Into the SendThreadread ready source section as follows: It is based on header.xid = -1 know this is the type of transaction response

// todo 服务端抛出来的事件, 客户端将把他存在EventThread的 watingEvents 队列中
// todo 它的实现逻辑也是这样, 会有另外一个线程不断的消费这个队列
if (replyHdr.getXid() == -1) {
    // -1 means notification
    if (LOG.isDebugEnabled()) {
        LOG.debug("Got notification sessionid:0x"
                + Long.toHexString(sessionId));
    }
    // todo 创建watcherEvent 并将服务端发送回来的数据,反序列化进这个对象中
    WatcherEvent event = new WatcherEvent();
    event.deserialize(bbia, "response");

    // convert from a server path to a client path
    // todo 将server path 反转成 client path
    if (chrootPath != null) {
        String serverPath = event.getPath();
        if (serverPath.compareTo(chrootPath) == 0)
            event.setPath("/");
        else if (serverPath.length() > chrootPath.length())
            event.setPath(serverPath.substring(chrootPath.length()));
        else {
            LOG.warn("Got server path " + event.getPath()
                    + " which is too short for chroot path "
                    + chrootPath);
        }
              WatchedEvent we = new WatchedEvent(event);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Got " + we + " for sessionid 0x"
                            + Long.toHexString(sessionId));
                }
                //todo 跟进去
                eventThread.queueEvent(we);
                return;
            }
    }

In the end, this will add the appropriate method of EventThreadconsumption of the queue, follow-upeventThread.queueEvent(we);

// todo
public void queueEvent(WatchedEvent event) {
    // todo 如果事件的类型是 none, 或者sessionState =  直接返回
    /**
     *   todo 事件的类型被设计成 watcher 接口的枚举
     *   None (-1),
     *   NodeCreated (1),
     *   NodeDeleted (2),
     *   NodeDataChanged (3),
     *   NodeChildrenChanged (4);
     */
    if (event.getType() == EventType.None
            && sessionState == event.getState()) {
        return;
    }
    sessionState = event.getState();

    // materialize the watchers based on the event
    // todo 根据事件的具体类型,将观察者具体化, 跟进去
    // todo 这个类是ClientCnxn的辅助类,作用就是将watcher 和它观察的事件封装在一起
    WatcherSetEventPair pair = new WatcherSetEventPair(
            //todo 跟进这个 materialize方法. 其实就是从map中取出了和当前client关联的全部 watcher set

            watcher.materialize(event.getState(), event.getType(),
                    event.getPath()),
            event);
    // queue the pair (watch set & event) for later processing
    // todo 将watch集合 和 event 进行排队(按顺序添加到队列里了), 以便后续处理 , 怎么处理呢?  就在EventThread的run循环中消费
    // todo watingEvent ==>  LinkedBlockingQueue<Object>
    waitingEvents.add(pair);
}

The above code is mainly to do the following things:

  • Remove all watcher and current events related to from the map
  • Will be added to the watcher set waitingEvents queue, waiting EventThead consumption

Follow-up watcher.materialize(event.getState(), event.getType(),will catch up with the following code

case NodeDataChanged: // todo node中的data改变和 nodeCreate 都会来到下面的分支
        case NodeCreated:
            synchronized (dataWatches) {
                // todo dataWatches 就是刚才存放  path : watcher 的map
                // todo dataWatches.remove(clientPath) 移除并返回clientPath对应的watcher , 放入 result 中
                addTo(dataWatches.remove(clientPath), result);
            }

The above dataWatches is saved path + watcher set of map, the above operation is to remove and return the designated watcher, which explains why zk native client will only add watcher callback time

EventThread how the consumer waitingEvents

EventThread is a daemon thread, so it has its own method of operation continues to run, it is to consume this queue in this run method


        @Override
        public void run() {
            try {
                isRunning = true;
                // todo 同样是无限的循环
                while (true) {
                    // todo 从watingEvnets 中取出一个 WatcherSetEventPair
                    Object event = waitingEvents.take();
                    if (event == eventOfDeath) {
                        wasKilled = true;
                    } else {
                        // todo 本类方法,处理这个事件,继续进入,方法就在下面
                        processEvent(event);
                    }
                    if (wasKilled)
                        synchronized (waitingEvents) {
                            if (waitingEvents.isEmpty()) {
                                isRunning = false;
                                break;
                            }
                        }
                }
            } catc
        

Continue to follow it processEvent(event), eventually calls the following code in this method, Watcher here's what I added to it at the beginning of this blog watcher, so far kick call it a day

 watcher.process(pair.event);

It is with immense excitement to write this blog, if there is assistance, please give me a praise to you

Guess you like

Origin www.cnblogs.com/ZhuChangwu/p/11593642.html