入射ウォッチャーを読むためのZooKeeperクライアントとサーバーのソースコード

これは、援助があれば、あなたに私に賞賛を与えてください、このブログを書くために巨大な興奮であります

プレゼンスウォッチャの必要性

特に簡単には例を理解するために:私のプロジェクトはダボ+飼育係に基づいている場合、分散プロジェクトをビルド、私は登録センターとして飼育係で、私は外国人に飼育係に登録3つのプロジェクトを持って、三つの機能と同じサービスプロバイダを持っていますサービスを公開するが、問題は、それから、もちろん依存に参加?飼育係に登録し、設定ファイルを書き、Javaコードを記述する方法になり始めるされ、その後、クライアントの身体をZooKeeperのこれらの3つのサービス・プロバイダー、クライアントである私はに頼ることを選択した複数のクライアントを、飼育係、光がないああサービスプロバイダを持って、サービスを提供するために、同じ方法で、消費者はに登録されたので、私は、ああ、サービス消費者に必要になりました飼育係、4ノードの存在に飼育係、それは、4つのクライアントで、サービス利用者がサービスプロバイダのアドレスにそれを引っ張って、飼育係を購読し、[アドレスローカルにキャッシュされ、その後、あなたがリモートで消費者にサービスを呼び出すことができますので、問題が再び、サービスプロバイダがハングアップした場合に、それを行う方法?消費者に通知しなければならない飼育係はそうではありませんか?サービスプロバイダになるの日の場合に対処します ?それはまた、消費者に通知しなければならないが、それはそれを解決し、これが存在ウォッチャーの意味であります

実験場面:

我々は成功したデータの動的なノードを変更するために、コンソールを使用し、その後、飼育係のサービスとクライアントを立ち上げ、事前ウォッチャーに追加した、我々はウォッチャーコールバック現象を見つけるだろうと仮定

次のようにフック関数のコードが追加されます。

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));

上記のコードを見ては、独自に追加しwatcher、であるclient.getData("/node1", new Watcher() {}。このような特定の条件が満たされたときのように実行され、実行されたときに実行されないコールバックフック関数、次のとおりです。node1は削除され、データのnode1は変更されています

getDataは、どのようなことを行いますか?

ソースは次のとおりのgetData、名前が示す、サーバはノードデータ+スタットを返すサーバーノードが呼び出し後に変更されたときに、当然のことながら、あります

いくつかの主要な主流の仕事に続き

  • 作りますWatchRegistration wcb= new DataWatchRegistration(watcher, clientPath);
    • これは、実際のオブジェクトにカプセル化されたシンプルな内部タイプ、パスと時計です
  • リクエストを作成し、初期化しますrequest.head=getData=4
  • ClientCnxn.submitRequest(...)、これらのパッケージはさらに情報の存在を呼び出し
  • request.setWatch(ウォッチャー= nullを!)、彼はウォッチャーパッケージに行くが、そこだけウォッチャーマークを作っていなかったこと
 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);私は、下記のソースコードをアンインストールし、この方法は、1が、それはまだスタイルをブロックしていることがわかります来て、ここで、さらにパケットrequetをカプセル化されています

さらに重要なことは、queuePacket()この方法の最後の引数は、我々だけでラッパークラスを作成するパス+ウォッチャーがあります

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;
}

同様に、queuePacket()メソッドはoutgoingQueueパケットに提出されるには、最終消費者がサーバに送信されseadThread

サーバwatchRegistrationない空のパケットに対処する方法

私は、ブログ全体で要求を処理するために、スタンドアロンモードでサーバーをフォローアップのプロセスを説明しようと思って、これだけの結論は、このブログと言って

サーバーでは、ユーザの要求は、最終的になり順次 3に流れProcessorています、

  • PrepRequestProcessor
    • 状態の一部を担当する変更
  • SyncRequestProcessor
    • ディスクに同期トランザクションログ
  • 決勝のRequestProcessor
    • ユーザの要求

私たちは、直接行く、彼が探しているアクションの道を要求を行う。、のに小さなクライマックスをしてみましょう異なるウォッチャーを追加するために、サーバーのウォッチャーに従うかどうかFinalRequestProcessorpublic void processRequest(Request request) {}方法getData()zks.getZKDatabase().getData(getDataRequest.getPath(), stat, getDataRequest.getWatch() ? cnxn : null);

本当に私はフォーカスを描画する必要があり、私はこれを発見したときに、新世界に見られるように、私は、スーパー興奮気分でした

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;
    }

これをフォローアップするために続けてgetData()、サーバー上で、パス+ウォッチャーのマップを維持します

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;
    }
}

コマンドラインクライアントを開き、サーバーノードのステータスを変更します

背面に接続帳、クライアントコードは、単一の作成にClientCnxn時間を、次のロジックを持って、それが2つのデーモンスレッドを開き、サーバにハートビートを送信するための責任sendThread、ユーザーが関連するIO交換し、サーバーを持ち、EventThreadとする責任がありますノードに対して上昇レベルに関連する処理ロジックTXN事項

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

これまでのところ、クライアントは、次の3つのスレッドを持っています

  • コンソールのメインスレッドでユーザ入力コマンドを処理するための責任
  • デーモンスレッド1:seadThread
  • デーモンスレッド2:eventThread

次のようにメインスレッドのユーザ入力部を処理ロジックコード:

次のコードは、メイン論理処理コマンドは、ユーザによって入力された場合、他のエンドユーザによって選択されたブランチは、入力をコマンドかを決定する際

仮定のシナリオによると、ユーザーが入力したコマンドは、これはset /path newValue以下を実行するためのコードを解析した後、その疑いstat = zk.setData(path, args[2].getBytes(),のセクションを

  // 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);

続行しstat = zk.setData(path, args[2].getBytes(), 、次のロジックは非常に簡単です、で来るパッケージの入力をユーザーに要求することですClientCnxnsendThreadを待っている消費者に、送信キューは、クラスのメソッドを提出します

submitRequestの目的は、最後のパラメータを見てスタートがnullに設定され、このパラメータはWatchRegistration位置で、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();
    }

このフォローアップsubmitRequest()メソッドは、以下のソースは、材料を置かない、それは同様に応じてサーバまで生きてブロックされています

コードの現在のメインロジックは、パケットにカプセル化し、次いで消費outgoingQueue維持キューで待機しているClintCnxnパケットに追加する要求にあるsendThread

セットには、我々は、コンソールコマンドがトリガ入力しているため、より重要なこのパケットは== nullのWatchRegistration実施され、この方法に来たときにエンドFinalRequestProcessor再処理サービスから取り出した疑いを、ウォッチャー== nullの場合、それはmaptableでパス+ウォッチャーに保存されません

重要:送信トランザクションメッセージ

プロセスは、次のコードFinalRequestProcessorpublic void processRequest(Request request) {}

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

内部に進んで

// 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);

フォローアップZkDatabase.javaprocessTxn(hdr, txn)方法

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

フォローアップするために、DataTree.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(),

リクエストヘッダの値に基づいて、その後、裁判官は枝を切り替えるために行ってきました、私たちは次のようにsetDataブランチにトリガコンソールに現在ある:この方法に従ってください、あなたはそれには、次のいくつかのことを中心に作られて見ることができます

  • 古いを置き換える新しいデータの渡される値を使用して
  • dataWatches.triggerWatch(path, EventType.NodeDataChanged);指定されたイベントの腕時計をトリガ、どのようなイベントものウォッチャーをトリガし、それ?NodeDataChange、?内側に確認してください


    //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;
    }

内部ではdataWatches.triggerWatch(path, EventType.NodeDataChanged);、次のように、ソースコードは、ある預金口座のメインロジックを取られたとき、時計のサービス側に格納され、その後、1つのコールバックそのプロセッサ機能によって一つが、質問は終わりでは、それをウォッチャ何ですか?私は)(クライアントの起動時に、我々はのgetDataを追加することができると思いますServerCnxnあるwather、


 // 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;
    }

何を行うには、プロセス()メソッドを行くとServerCnxnを見て興奮?

確かに最初の位置パラメータにトランザクションメッセージを送信するクライアント、および新しいReplyHeader(-1、-1L、0)にサーバーを見て非常に興奮クラスNIOServerCnxn ServerCnxnを達成するためにある、-1でありますそれがあるため、クライアントのフラグがxid = -1を受け取った後、重要であり、それは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");
    }

コールバックコールバックの時計を使用して応答を処理

SendThreadリードレディ光源部を次のようにこれはトランザクション応答のタイプである知っているheader.xid = -1に基づいています

// 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;
            }
    }

最後に、これは適切な方法で追加されEventThreadたキューの消費を、フォローアップeventThread.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);
}

上記のコードは、次のことを行うために、主に次のとおりです。

  • マップからに関連するすべてのウォッチャーと現在のイベントを削除します
  • EventThead消費を待って、ウォッチャーセットwaitingEventsキューに追加されます

フォローアップは、watcher.materialize(event.getState(), event.getType(),次のコードに追いつくだろう

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);
            }

上記dataWatchesマップの保存パス+ウォッチャーセットで、上記の動作はZKネイティブクライアントのみウォッチャーコールバック時間を追加する理由を説明指定されたウォッチャを、削除して返すことです

EventThreadどのように消費者のwaitingEvents

それは、このrunメソッドでこのキューを消費することで、EventThreadはデーモンスレッドなので、操作の独自のメソッドが実行され続けています


        @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
        

それに従うことを続行しprocessEvent(event)、最終的には、この方法では、次のコードを呼び出して、ここで、私はこのブログウォッチャーの冒頭で、それに追加するものだ、これまでの日それを呼び出すキックウォッチャー

 watcher.process(pair.event);

これは、援助があれば、あなたに私に賞賛を与えてください、このブログを書くために巨大な興奮であります

おすすめ

転載: www.cnblogs.com/ZhuChangwu/p/11593642.html