6-zookeeper源码之watcher源码分析

一,Watcher 的基本流程

ZooKeeper 的 Watcher 机制,总的来说可以分为三个过程:客户端注册 Watcher、服务器处理 Watcher 和客户端回调 Watcher

客户端注册 watcher 有 3 种方式,getData、exists、getChildren;以如下代码为例

1,基于 zkclient 客户端发起一个数据操作

		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
		</dependency>
    @SneakyThrows
    public static void main(String[] args) {
        SpringApplication.run(ZkclientApplication.class, args);
        ZooKeeper zookeeper = new ZooKeeper("192.168.13.102:2181",
                4000, event -> System.out.println("event.type" + event.getType()));
        zookeeper.create("/watch", "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 创建节点
        zookeeper.exists("/watch", true); // 注册监听
        Thread.sleep(1000);
        zookeeper.setData("/watch", "1".getBytes(), -1); // 修改节点的值触发监听
        System.in.read();
    }

2,ZooKeeper API 的初始化过程

在创建一个 ZooKeeper 客户端对象实例时,我们通过 new Watcher()向构造方法中传入一个默认的 Watcher, 这个 Watcher 将作为整个 ZooKeeper 会话期间的默认Watcher,会一直被保存在客户端ZKWatchManager的defaultWatcher中。

1)Zookeeper构造器

    public ZooKeeper(
            String connectString,
            int sessionTimeout,
            Watcher watcher,
            boolean canBeReadOnly,
            HostProvider hostProvider,
            ZKClientConfig clientConfig
    ) throws IOException {

        this.clientConfig = clientConfig != null ? clientConfig : new ZKClientConfig();
        this.hostProvider = hostProvider;
        ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
		//debug
        cnxn = createConnection(
                connectStringParser.getChrootPath(),
                hostProvider,
                sessionTimeout,
                this.clientConfig,
                watcher,
                getClientCnxnSocket(),
                canBeReadOnly);
        //调用start方法
        cnxn.start();
    }

2)createConnection()

    ClientCnxn createConnection(
            String chrootPath,
            HostProvider hostProvider,
            int sessionTimeout,
            ZKClientConfig clientConfig,
            Watcher defaultWatcher,
            ClientCnxnSocket clientCnxnSocket,
            boolean canBeReadOnly
    ) throws IOException {
        //初始化ClientCnxn
        return new ClientCnxn(
                chrootPath,
                hostProvider,
                sessionTimeout,
                clientConfig,
                defaultWatcher,
                clientCnxnSocket,
                canBeReadOnly);
    }

3)ClientCnxn构造器

public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZKClientConfig clientConfig, Watcher defaultWatcher, ClientCnxnSocket clientCnxnSocket, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) throws IOException {
    this.chrootPath = chrootPath;
    this.hostProvider = hostProvider;
    this.sessionTimeout = sessionTimeout;
    this.clientConfig = clientConfig;
    this.sessionId = sessionId;
    this.sessionPasswd = sessionPasswd;
    this.readOnly = canBeReadOnly;
    //在这里将watcher设置到ZKWatchManager
    this.watchManager = new ZKWatchManager(
            clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET),
            defaultWatcher);

    this.connectTimeout = sessionTimeout / hostProvider.size();
    this.readTimeout = sessionTimeout * 2 / 3;

    this.sendThread = new SendThread(clientCnxnSocket);
    this.eventThread = new EventThread();
    initRequestTimeout();
}

ClientCnxn是zk客户端和zk服务器端进行通信和事件通知处理的主要类,它内部包含两个类

SendThread:负责客户端和服务端的数据通信,也包括事件信息的传输。

EventThread:主要在客户端回调注册的Watchers进行通知处理。

3,ClientCnxn 初始化

    public void start() {
        sendThread.start();
        eventThread.start();
    }

二,服务端接收请求处理流程

服务端有一个 NIOServerCnxn 类,用来处理客户端发送过来的请求

1,NIOServerCnxn

doIO()处理IO请求,里面调用了那个那啥readPayload()

readPayload()调用了readRequest()来处理读请求

实际上读请求交给了ZookeeperServer的processPacket()。

    protected void readRequest() throws IOException {
        zkServer.processPacket(this, incomingBuffer);
    }

2,processPacket()

处理客户端传送过来的数据包

public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
    // We have the request, now process and setup for next
    InputStream bais = new ByteBufferInputStream(incomingBuffer);
    BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
    RequestHeader h = new RequestHeader();
    //反序列化客户端 header 头信息
    h.deserialize(bia, "header");
    cnxn.incrOutstandingAndCheckThrottle(h);
    incomingBuffer = incomingBuffer.slice();
    // 判断当前操作类型,如果是 auth操作,则执行下面的代码
    if (h.getType() == OpCode.auth) {
        //省略
    } else if (h.getType() == OpCode.sasl) {
        // 如果不是授权操作,再判断是否为 sasl 操作
        processSasl(incomingBuffer, cnxn, h);
    } else {// 最终进入这个代码块进行处理
        if (!authHelper.enforceAuthentication(cnxn, h.getXid())) {

            return;
        } else {
            // 封装请求对象
            Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), incomingBuffer, cnxn.getAuthInfo());
            int length = incomingBuffer.limit();
            if (isLargeRequest(length)) {
                // checkRequestSize will throw IOException if request is rejected
                checkRequestSizeWhenMessageReceived(length);
                si.setLargeRequestSize(length);
            }
            si.setOwner(ServerCnxn.me);
            // 提交请求
            submitRequest(si);
        }
    }
}

3,submitRequest()

负责在服务端提交当前请求

public void submitRequestNow(Request si) {
        if (firstProcessor == null) {//processor 处理器, request 过来以后会经历一系列处理器的处理过程
            synchronized (this) {
                try {

                    while (state == State.INITIAL) {
                        wait(1000);
                    }
                } catch (InterruptedException e) {
                    LOG.warn("Unexpected interruption", e);
                }
                if (firstProcessor == null || state != State.RUNNING) {
                    throw new RuntimeException("Not started");
                }
            }
        }
        try {
            touch(si.cnxn);
            // 判断是否合法
            boolean validpacket = Request.isValid(si.type);
            if (validpacket) {
                setLocalSessionFlag(si);
                //调用 firstProcessor发起请求,而这个 firstProcess 是一个接口,有多个实现类
                firstProcessor.processRequest(si);
                if (si.cnxn != null) {
                    incInProcess();
                }
            } else {
                LOG.warn("Received packet at server of unknown type {}", si.type);

                requestFinished(si);
                new UnimplementedRequestProcessor().processRequest(si);
            }
        } catch (MissingSessionException e) {
            LOG.debug("Dropping request.", e);
            // Update request accounting/throttling limits
            requestFinished(si);
        } catch (RequestProcessorException e) {
            LOG.error("Unable to process request", e);
            // Update request accounting/throttling limits
            requestFinished(si);
        }
    }

1)firstProcessor的请求链组成

firstProcessor 的初始化是在 ZookeeperServer 的 setupRequestProcessor 中完成的,代码如下

protected void setupRequestProcessors() {
    RequestProcessor finalProcessor = new FinalRequestProcessor(this);
    RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
    // 需要注意的是, PrepRequestProcessor 中传递的是一个 syncProcessor
    ((SyncRequestProcessor) syncProcessor).start();
    firstProcessor = new PrepRequestProcessor(this, syncProcessor);
    ((PrepRequestProcessor) firstProcessor).start();
}

从上面我们可以看到 firstProcessor 的实例是一个 PrepRequestProcessor,而这个构造方法中又传递了一个 Processor 构成了一个调用链。

RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);

而 syncProcessor 的构造方法传递的又是一个 Processor,对应的是FinalRequestProcessor

所以整个调用链是 PrepRequestProcessor -> SyncRequestProcessor ->FinalRequestProcessor

4,PredRequestProcessor.processRequest()

firstProcessor.processRequest(si);会调用到 PrepRequestProcessor

public void processRequest(Request request) {
	submittedRequests.add(request);
}

processRequest 只是把 request 添加到 submittedRequests 中,根据前面的经验,很自然的想到这里又是一个异步操作。而 subittedRequests 又是一个阻塞队列

LinkedBlockingQueue submittedRequests = new LinkedBlockingQueue()

而 PrepRequestProcessor 这个类又继承了线程类,因此直接找到当前类中的run 方法如下

@Override
public void run() {
    try {
        while (true) {
            ServerMetrics.getMetrics().PREP_PROCESSOR_QUEUE_SIZE.add(submittedRequests.size());
            //从队列中拿到请求进行处理
            Request request = submittedRequests.take();
            ServerMetrics.getMetrics().PREP_PROCESSOR_QUEUE_TIME
                .add(Time.currentElapsedTime() - request.prepQueueStartTime);
            if (LOG.isTraceEnabled()) {
                long traceMask = ZooTrace.CLIENT_REQUEST_TRACE_MASK;
                if (request.type == OpCode.ping) {
                    traceMask = ZooTrace.CLIENT_PING_TRACE_MASK;
                }
                ZooTrace.logRequest(LOG, traceMask, 'P', request, "");
            }
            if (Request.requestOfDeath == request) {
                break;
            }

            request.prepStartTime = Time.currentElapsedTime();
            // 调用 pRequest 进行预处理
            pRequest(request);
        }
    } catch (Exception e) {
        handleException(this.getName(), e);
    }

}

1)pRequest

nextProcessor.processRequest(request);

5,SyncRequestProcessor.processRequest()

public void processRequest(Request request) {
    // request.addRQRec(">sync");
    queuedRequests.add(request);
}

这个方法的代码也是一样,基于异步化的操作,把请求添加到 queuedRequets 中

@Override
    public void run() {
        try {
            resetSnapshotStats();
            lastFlushTime = Time.currentElapsedTime();
            while (true) {
                ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size());

                long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay());

                Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS);
                if (si == null) {
                    flush();
                    // 从阻塞队列中获取请求
                    si = queuedRequests.take();
                }

                if (si == REQUEST_OF_DEATH) {
                    break;
                }

                long startProcessTime = Time.currentElapsedTime();
                ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime);

                // track the number of records written to the log
                if (!si.isThrottled() && zks.getZKDatabase().append(si)) {
                    if (shouldSnapshot()) {
                        resetSnapshotStats();
                        // roll the log
                        zks.getZKDatabase().rollLog();
                        // take a snapshot
                        if (!snapThreadMutex.tryAcquire()) {
                            LOG.warn("Too busy to snap, skipping");
                        } else {
                            //启动一个处理快照的线程
                            new ZooKeeperThread("Snapshot Thread") {
                                public void run() {
                                    try {
                                        zks.takeSnapshot();
                                    } catch (Exception e) {
                                        LOG.warn("Unexpected exception", e);
                                    } finally {
                                        snapThreadMutex.release();
                                    }
                                }
                            }.start();
                        }
                    }
                } else if (toFlush.isEmpty()) {
                    if (nextProcessor != null) {
                        // 继续调用下一个处理器来处理请求
                        nextProcessor.processRequest(si);
                        if (nextProcessor instanceof Flushable) {
                            ((Flushable) nextProcessor).flush();
                        }
                    }
                    continue;
                }
                toFlush.add(si);
                if (shouldFlush()) {
                    flush();
                }
                ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime);
            }
        } catch (Throwable t) {
            handleException(this.getName(), t);
        }
        LOG.info("SyncRequestProcessor exited!");
    }

6,FinalRequestProcessor.processRequest()

FinalRequestProcessor.processRequest 方法并根据 Request 对象中的操作更新内存中 Session 信息或者 znode 数据。

case OpCode.exists: {
    lastOp = "EXIS";

    ExistsRequest existsRequest = new ExistsRequest();
    // 反序列化 ( 将 ByteBuffer 反序列化成为 ExitsRequest. 这个就是我们在客户端发起请求的时候传递过来的 Request 对象
    ByteBufferInputStream.byteBuffer2Record(request.request, existsRequest);
    // 得到请求的路径
    path = existsRequest.getPath();
    if (path.indexOf('\0') != -1) {
        throw new KeeperException.BadArgumentsException();
    }
    // 终于找到一个很关键的代码,判断请求的 getWatch 是否存在,如果存在,则传递 cnxn ( servercnxn )
    //对于 exists 请求,需要监听 data 变化事件,添加 watcher
    Stat stat = zks.getZKDatabase().statNode(path, existsRequest.getWatch() ? cnxn : null);
    // 在服务端内存数据库中根据路径得到结果进行组装,设置为 ExistsResponse
    rsp = new ExistsResponse(stat);
    requestPathMetricsCollector.registerRequest(request.type, path);
    break;
}

三,客户端接收服务端处理完成的响应

1,ClientCnxnSocketNIO.doIO()

服务端处理完成以后,会通过 NIOServerCnxn.sendResponse 发送返回的响应信息,客户端会在 ClientCnxnSocketNIO.doIO 接收服务端的返回,

注意一下 SendThread.readResponse ,接收服务端的信息进行读取.

void doIO(Queue<Packet> pendingQueue, ClientCnxn cnxn) throws InterruptedException, IOException {
    SocketChannel sock = (SocketChannel) sockKey.channel();
    if (sock == null) {
        throw new IOException("Socket is null!");
    }
    if (sockKey.isReadable()) {
        int rc = sock.read(incomingBuffer);
        if (rc < 0) {
            throw new EndOfStreamException("Unable to read additional data from server sessionid 0x"
                                           + Long.toHexString(sessionId)
                                           + ", likely server has closed socket");
        }
        if (!incomingBuffer.hasRemaining()) {
            incomingBuffer.flip();
            if (incomingBuffer == lenBuffer) {
                recvCount.getAndIncrement();
                readLength();
            } else if (!initialized) {
                readConnectResult();
                enableRead();
                if (findSendablePacket(outgoingQueue, sendThread.tunnelAuthInProgress()) != null) {
                    // Since SASL authentication has completed (if client is configured to do so),
                    // outgoing packets waiting in the outgoingQueue can now be sent.
                    enableWrite();
                }
                lenBuffer.clear();
                incomingBuffer = lenBuffer;
                updateLastHeard();
                initialized = true;
            } else {
                sendThread.readResponse(incomingBuffer);
                lenBuffer.clear();
                incomingBuffer = lenBuffer;
                updateLastHeard();
            }
        }
    }
    if (sockKey.isWritable()) {
        Packet p = findSendablePacket(outgoingQueue, sendThread.tunnelAuthInProgress());

        if (p != null) {
            updateLastSend();
            // If we already started writing p, p.bb will already exist
            if (p.bb == null) {
                if ((p.requestHeader != null)
                    && (p.requestHeader.getType() != OpCode.ping)
                    && (p.requestHeader.getType() != OpCode.auth)) {
                    p.requestHeader.setXid(cnxn.getXid());
                }
                p.createBB();
            }
            sock.write(p.bb);
            if (!p.bb.hasRemaining()) {
                sentCount.getAndIncrement();
                outgoingQueue.removeFirstOccurrence(p);
                if (p.requestHeader != null
                    && p.requestHeader.getType() != OpCode.ping
                    && p.requestHeader.getType() != OpCode.auth) {
                    synchronized (pendingQueue) {
                        pendingQueue.add(p);
                    }
                }
            }
        }
        if (outgoingQueue.isEmpty()) {
         
            disableWrite();
        } else if (!initialized && p != null && !p.bb.hasRemaining()) {
           
            disableWrite();
        } else {
            // Just in case
            enableWrite();
        }
    }
}

2,SendThread.readResponse()

这个方法里面主要的流程如下:

首先读取 header,如果其 xid == -2,表明是一个 ping 的 response,return

如果 xid 是 -4 ,表明是一个 AuthPacket 的 response return

如果 xid 是 -1,表明是一个 notification,此时要继续读取并构造一个 enent,通过EventThread.queueEvent 发送,return

其它情况下:

从 pendingQueue 拿出一个 Packet,校验后更新 packet 信息

3,finishPacket()

主要功能是把从 Packet 中取出对应的 Watcher 并注册到 ZKWatchManager 中去.

下面这段代码是客户端存储 watcher 的几个 map 集合,分别对应三种注册监听事件

static class ZKWatchManager implements ClientWatchManager {
    private final Map<String, Set<Watcher>> dataWatches =
    new HashMap<String, Set<Watcher>>();
    private final Map<String, Set<Watcher>> existWatches =
    new HashMap<String, Set<Watcher>>();
    private final Map<String, Set<Watcher>> childWatches =
    new HashMap<String, Set<Watcher>>();

总的来说,当使用 ZooKeeper 构造方法或者使用 getData、exists 和getChildren 三个接口来向 ZooKeeper 服务器注册 Watcher 的时候,首先将此消息传递给服务端,传递成功后,服务端会通知客户端,然后客户端将该路径和Watcher 对应关系存储起来备用。

finishPacket 方法最终会调用 eventThread.queuePacket, 将当前的数据包添加到等待事件通知的队列中

四,事件触发

前面这么长的说明,只是为了清晰的说明事件的注册流程,最终的触发,还得需要通过事务型操作来完成

zookeeper.setData(/mic”,1.getByte(),-1) ; // 修改节点的值触发监听

1,服务端的事件响应DataTree.setData()

		// 触发对应节点的 NodeDataChanged 事件
        dataWatches.triggerWatch(path, EventType.NodeDataChanged);

1)WatcherManager.triggerWatch()

w.process(e);

2)w.process(e)

在服务端绑定事件的时候,watcher 绑定是是什么?是 ServerCnxn,所以 w.process(e),其实调用的应该是 ServerCnxn 的 process 方法。而
servercnxn 又是一个抽象方法,有两个实现类,分别是:NIOServerCnxn 和NIOServerCnxn。

public void process(WatchedEvent event) {
    ReplyHeader h = new ReplyHeader(ClientCnxn.NOTIFICATION_XID, -1L, 0);
    if (LOG.isTraceEnabled()) {

    }
    WatcherEvent e = event.getWrapper();

    // 这个地方发送了一个事件,事件对象为 WatcherEvent 。
    int responseSize = sendResponse(h, e, "notification", null, null, ZooDefs.OpCode.error);
    ServerMetrics.getMetrics().WATCH_BYTES.add(responseSize);
}

那接下来,客户端会收到这个 response,触发 SendThread.readResponse 方法

2,客户端处理事件响应

SendThread 接收到服务端的通知事件后,会通过调用 EventThread 类的queueEvent 方法将事件传给 EventThread 线程,queueEvent 方法根据该通知事件,从 ZKWatchManager 中取出所有相关的 Watcher,如果获取到相应的Watcher,就会让 Watcher 移除失效。

通过 dataWatches 或者 existWatches 或者 childWatches 的 remove 取出对应的watch,表明客户端 watch 也是注册一次就移除.

同时需要根据 keeperState、eventType 和 path 返回应该被通知的 Watcher 集合

waitingEvents 是 EventThread 这个线程中的阻塞队列,很明显,又是在我们第一步操作的时候实例化的一个线程。

从名字可以知道,waitingEvents 是一个待处理 Watcher 的队列,EventThread 的run() 方法会不断从队列中取数据,交由 processEvent 方法处理:

判断事件类型,得到 watcherseteventPair,拿到符合触发机制的所有 watcher 列表,循环进行调用,调用客户端的回调 process.

3,服务端接收数据请求

zookeeper 启动的时候,通过下面的代码构建了一个NIOServerCnxnFactory,它实现了 Thread,所以在启动的时候,会在 run 方法中不断循环接收客户端的请求进行分发

ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();

4,集群模式下的处理流程

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值