【分布式】ZooKeeper源码分析:Watcher机制

概要

本文内容主要介绍了ZooKeeper中Watcher机制,我们会先对Watcher做个简单介绍,然后再学习它的源码,最后会通过具体实例学会使用Watcher。主要内容如下:

转载请注明出处:https://www.cnblogs.com/fancyfairy/p/13531706.html

第1部分 Watcher介绍

第1.1部分 Watcher简介

ZooKeeper常用来作为数据发布/订阅中心,发布/订阅系统一般有两种模式:模式。推模式中,服务端主动将数据更新发送给所有订阅的客户端,拉模式则是由客户端主动发起请求来获取最新的数据,通常会采用定时轮询的方式。而ZooKeeper采用了推拉结合的方式:客户端像服务端注册自己需要关注的节点,一旦节点数据发生了变更,那么服务端就会像客服端发送Watcher事件通知,但是这个事件通知并不会包含具体的变更数据,客户端接收到这个消息通知之后,需要主动向服务端发起请求来获取最新的数据。

第1.2部分 Watcher特性

  • 一次性

无论是客户端还是服务端,一旦一个Watcher被触发,ZooKeeper就会从相应存储中删除,在使用上需要反复注册

  • 客户端串行执行

客户端Watcher的执行是一个串行同步的过程,为我们保证了顺序,开发的时候需要注意避免一个Watcher的逻辑阻塞整个客户端Watcher的回调

  • 轻量

Watcher的通知非常简单,只包含通知状态、通知事件和通知路径,并不会包含数据变更,需要客户端主动获取数据。客户端注册的时候也不会将Watcher对象传给服务端,只是通过boolean类型属性进行标记,服务端会保存连接的ServerCnxn对象

第2部分 Watcher源码解析(基于ZooKeeper3.4.6)

第2.1部分 Watcher接口

// Watcher接口
public interface Watcher {

    /**
     * 定义了事件对应的不同状态
     */
    public interface Event {
       
        // 事件通知状态
        public enum KeeperState {
            // ...
        }

        /**
         * 事件类型
         */
        public enum EventType {
            // ...        
        }
    }

    // 事件的回调方法
    abstract public void process(WatchedEvent event);
}

Watcher是一个标准的事件处理器,包含了KeeperStateEventType两个枚举类,以及一个事件的回调方法。回调方法的定义很简单,包含了三个基本属性:KeeperState(通知状态)、EventType(事件类型)和path(节点路径)

public class WatchedEvent {
    final private KeeperState keeperState;
    final private EventType eventType;
    private String path;
  
  	public WatchedEvent(WatcherEvent eventMessage) {
        keeperState = KeeperState.fromInt(eventMessage.getState());
        eventType = EventType.fromInt(eventMessage.getType());
        path = eventMessage.getPath();
    }
  
    public WatcherEvent getWrapper() {
        return new WatcherEvent(eventType.getIntValue(), 
                                keeperState.getIntValue(), 
                                path);
    }
}

WatchedEvent只会在服务端或者客户端内部使用,实际在网络间传输的是WatcherEvent,我们可以看到这个类实现了序列化的接口Record,它的定义和WatchedEvent一摸一样,只是一个用的枚举类一个用的code码。

public class WatcherEvent implements Record {
  private int type;
  private int state;
  private String path;
}

服务端生成WatchedEvent事件后,会调用getWrapper方法把自己转化成可以网络传输的WatchedEvent事件,客户端收到这个对象后,会反序列化后还原成一个WatchedEvent事件,并传递给process方法处理。无论节点发生了什么事件即使是数据变更,客户端也只能收到KeeperState(通知状态)、EventType(事件类型)和path(节点路径)这三个信息。

第2.2部分 工作过程

ZooKeeper的工作过程总的来说分为三步:客户端注册Watcher、服务端处理Watcher和客户端回调Watcher。其内部关键组件结构如下图:

2.2.1 客户端注册Watcher

客户端注册Watcher有以下几种方式:

(1) 创建ZooKeeper对象实例时,在构造方法中传入:

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher);

(2) 获取数据时注册Watcher:

public byte[] getData(final String path, Watcher watcher, Stat stat);

(3) 判断节点是否存在时注册Watcher:

void exists(final String path, Watcher watcher, StatCallback cb, Object ctx);

(4) 获取子节点时注册Watcher:

public List<String> getChildren(final String path, Watcher watcher);

对于getDate/exists/getChildren几个方法,首先会将请求标记设置是否使用Watcher监听,同时会将Watcher的注册信息和节点路径封装成WatchRegistration对象,用于暂时保存路径和Watcher的对应关系。重点关注request.setWatch(watcher != null);,后面的介绍中会发现,watcher对象并不会真正的传递给服务端,客户端只告诉服务端是否订阅某个节点,但是具体订阅什么事件服务端是不知道的

public byte[] getData(final String path, Watcher watcher, Stat stat)
        throws KeeperException, InterruptedException
     {
        ...
        WatchRegistration wcb = null;
        if (watcher != null) {
            wcb = new DataWatchRegistration(watcher, clientPath);
        }

        final String serverPath = prependChroot(clientPath);

        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.getData);
        GetDataRequest request = new GetDataRequest();
        request.setPath(serverPath);
        request.setWatch(watcher != null);
        GetDataResponse response = new GetDataResponse();
        ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
        ...
    }

通过submitRequest请求后,会封装成Packet对象(这部分详细介绍内容可以查看Packet相关内容)用于通信。

  public ReplyHeader submitRequest(RequestHeader h, Record request,
            Record response, WatchRegistration watchRegistration)
            throws InterruptedException {
        ...
        Packet packet = queuePacket(h, r, request, response, null, null, null,
                    null, watchRegistration);
        ...
    }

    Packet queuePacket(RequestHeader h, ReplyHeader r, Record request,
            Record response, AsyncCallback cb, String clientPath,
            String serverPath, Object ctx, WatchRegistration watchRegistration)
    {
        ...
        synchronized (outgoingQueue) {
            packet = new Packet(h, r, request, response, watchRegistration);
            ...
              outgoingQueue.add(packet);
            ...
        }
        ...
    }

随后,ZooKeeper客户端就会向服务端发送这个请求,同时会由客户端的sendThread线程的readResponse方法接收来自服务端的响应。服务端成功响应后(响应头中),客户端就会从Packet中取出对应的Watcher并注册到ZKWatchManager中去:

    private void finishPacket(Packet p) {
        if (p.watchRegistration != null) {
            p.watchRegistration.register(p.replyHeader.getErr());
        }
        ...
    }
	protected Map<String, Set<Watcher>> getWatches(int rc) {
            // 这里用了策略模式,不同的WatchRegistration返回的watcherMap不同
            return watchManager.dataWatches;
        }   

	public void register(int rc) {
            if (shouldAddWatch(rc)) {
                Map<String, Set<Watcher>> watches = getWatches(rc);
                synchronized(watches) {
                    Set<Watcher> watchers = watches.get(clientPath);
                    if (watchers == null) {
                        watchers = new HashSet<Watcher>();
                        watches.put(clientPath, watchers);
                    }
                    watchers.add(watcher);
                }
            }
        }
private static class ZKWatchManager implements ClientWatchManager {
  			// Watches分为三个HashMap存储
        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>>();

        private volatile Watcher defaultWatcher;
}

register方法中,客户端会将之前赞数保存的Watcher对象转交给ZKWatcherManager,并最终保存到dataWatches中去。在这个过程中,需要注意几个点:

Watcher对象并不会真正的传输到服务端

如果客户端注册的所有Watcher都被传递到服务端,那么服务端肯定会出现内存紧张或其他性能问题,所以Zookeeper在设计的时候,并没有真正的将WatchRegistration对象完全序列化到网络传输的字节数组中。

从以上代码中我们可以看到,Packet中真正用于网络传输的属性只有requestHeaderrequest

public void createBB() {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
        boa.writeInt(-1, "len"); // We'll fill this in later
        if (requestHeader != null) {
            requestHeader.serialize(boa, "header");
        }
        if (request instanceof ConnectRequest) {
            request.serialize(boa, "connect");
            // append "am-I-allowed-to-be-readonly" flag
            boa.writeBool(readOnly, "readOnly");
        } else if (request != null) {
            request.serialize(boa, "request");
        }
        ...
}
2.2.2 服务端处理Watcher

服务端接收到客户端的Watcher后的处理逻辑分为,在FinalRequestProcessor.processRequest中会判断当前请求是否注册Watcher,以getData为例:

case OpCode.getData: {
    ...
    byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
                                           getDataRequest.getWatch() ? cnxn : null);
    rsp = new GetDataResponse(b, stat);
    break;
}

可以看到,服务端会根据请求中watch属性为true的时候传入当前ServerCnxn,同时将数据节点路径和ServerCnxn对象存储到WatchManager中的watchTablewatch2Paths中。

  • watchTable:从数据节点路径的粒来托管Watcher
  • Watch2Paths:从Watcher的粒度控制事件触发需要触发的数据节点
public byte[] getData(String path, Stat stat, Watcher watcher) {
    return dataTree.getData(path, stat, watcher);
}

public byte[] getData(String path, Stat stat, Watcher watcher) {
    ...
    if (watcher != null) {
        dataWatches.addWatch(path, watcher);
    }
    return n.data;
}

public synchronized void addWatch(String path, Watcher watcher) {
    HashSet<Watcher> list = watchTable.get(path);
    if (list == null) {
        // don't waste memory if there are few watches on a node
        // rehash when the 4th entry is added, doubling size thereafter
        // seems like a good compromise
        list = new HashSet<Watcher>(4);
        watchTable.put(path, list);
    }
    list.add(watcher);

    HashSet<String> paths = watch2Paths.get(watcher);
    if (paths == null) {
        // cnxns typically have many watches, so use default cap here
        paths = new HashSet<String>();
        watch2Paths.put(watcher, paths);
    }
    paths.add(path);
}

WatchManager在服务端会由DataTree托管两个WatchManager,分别是dataWatcheschildWatches,本例中是getData接口,所以会存储在dataWatches中。

服务端如何触发Watcher的呢

每个事件的触发条件都是固定的,比如NodeDataChanged事件的触发条件是Watcher监听的对应数据节点的数据内容发生了变更,所以在setData的时候会触发对应事件:

public Stat setData(String path, byte data[], int version, long zxid,
                    long time) throws KeeperException.NoNodeException {
    // ...
    dataWatches.triggerWatch(path, EventType.NodeDataChanged);
    return s;
}

在更新完节点数据后,通过调用WatchManagertriggerWatch方法触发相关事件:

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);
        // ...
        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;
        }
        w.process(e);
    }
    return watchers;
}

触发过程分为三步:封装WatchedEvent(按照具体事件类型、通知状态和路径)、查询路径对应的wathers(查询到则在watchTable和watch2Paths中删除)、根据Watcher依次调用process方法。从这里可以看到,Watcher是一次性的,触发一次即失效。process是通过ServerCnxn中process接口来调用的,Zookeeper中ServerCnxn的实现有两种:NIOServerCnxn(默认实现)和NettyServerCnxn,在和客户端建立连接的时候会进行初始化,它是客户端和服务端之间的连接接口,并且实现了Watcher接口,因此无论哪种实现方式都实现了Watcher中的process方法。

我们以NIOServerCnxn中的实现为例,服务端将内部封装的WatchedEvent包装成WatcherEvent用于网络传输序列化,然后向客户端发送通知。process本身并不处理很多复杂的逻辑,只是发送通知,真正复杂的逻辑都在客户端处理了。

@Override
synchronized public void process(WatchedEvent event) {
    ...
    // Convert WatchedEvent to a type that can be sent over the wire
    WatcherEvent e = event.getWrapper();

    sendResponse(h, e, "notification");
}
2.2.3 客户端回调Watcher

客户端会服务端发过来的消息是统一处理的, 根据响应头的类型判断属于什么操作,-1代表这是一个通知。通过反序列化成WatchedEvent之后交给事件线程EventThread处理。

class SendThread extends Thread {
    void readResponse(ByteBuffer incomingBuffer) throws IOException {
        // ...
        if (replyHdr.getXid() == -1) {
            // -1 means notification
            WatcherEvent event = new WatcherEvent();
            event.deserialize(bbia, "response");
            // ...
            WatchedEvent we = new WatchedEvent(event);
            // ...
            eventThread.queueEvent( we );
            return;
        }
    }
}

EventThread事件线程是ZooKeeper客户端专门用来处理服务端通知的线程,其处理逻辑为:

public void queueEvent(WatchedEvent event) {
    if (event.getType() == EventType.None
            && sessionState == event.getState()) {
        return;
    }
    sessionState = event.getState();

    // materialize the watchers based on the event
    WatcherSetEventPair pair = new WatcherSetEventPair(
        watcher.materialize(event.getState(), event.getType(),
                            event.getPath()),
        event);
    // queue the pair (watch set & event) for later processing
    waitingEvents.add(pair);
}

客户端会从ZKWatchManager中取出所有相关的Watcher,客户端在识别出事件类型后,会从存储的watches中去除对应的watcher,此处Watcher机制也是一次性的,即触发后就失效

public Set<Watcher> materialize(Watcher.Event.KeeperState state,
                                Watcher.Event.EventType type,
                                String clientPath) {
    Set<Watcher> result = new HashSet<Watcher>();

    switch (type) {
    case None:
    // ...
    case NodeDataChanged:
    case NodeCreated:
        synchronized (dataWatches) {
            addTo(dataWatches.remove(clientPath), result);
        }
        synchronized (existWatches) {
            addTo(existWatches.remove(clientPath), result);
        }
        break;
    case NodeChildrenChanged:
        synchronized (childWatches) {
            addTo(childWatches.remove(clientPath), result);
        }
        break;
    case NodeDeleted:
    // ...
    default:
        // ...
    }
    return result;
}
final private void addTo(Set<Watcher> from, Set<Watcher> to) {
    if (from != null) {
        to.addAll(from);
    }
}

拿到相关Watcher后,会放入waitingEvent队列中去,EventThread的run方法会不断对该队列进行处理,Watcher回调在客户端串行执行,在每个Watcher处理中会调用process方法,真正的调用处理逻辑实现客户端的回调。

public void run() {
    try {
        isRunning = true;
        while (true) {
            Object event = waitingEvents.take();
            if (event == eventOfDeath) {
                wasKilled = true;
            } else {
                processEvent(event);
            }
            if (wasKilled)
                synchronized (waitingEvents) {
                    if (waitingEvents.isEmpty()) {
                        isRunning = false;
                        break;
                    }
                }
        }
   // ...
}
private void processEvent(Object event) {
    try {
        if (event instanceof WatcherSetEventPair) {
            // each watcher will process the event
            WatcherSetEventPair pair = (WatcherSetEventPair) event;
            for (Watcher watcher : pair.watchers) {
                try {
                    watcher.process(pair.event);
                } catch (Throwable t) {
                    LOG.error("Error while calling watcher ", t);
                }
            }
            // ...
        }
    }
}

第三部分 Watcher应用示例

public class ZooKeeperWatcherTest implements Watcher {

    public static final Logger LOG = LoggerFactory.getLogger(ZooKeeperWatcherTest.class);

    private static final int SESSION_TIMEOUT = 10000;

    private ZooKeeper zk = null;

    private CountDownLatch connectedSemaphore = new CountDownLatch(1);

    /**
     * 连接Zookeeper
     * @param connectString  Zookeeper服务地址
     */
    public void connectionZookeeper(String connectString) {
        connectionZookeeper(connectString, SESSION_TIMEOUT);
    }

    public void connectionZookeeper(String connectString, int sessionTimeout) {
        this.releaseConnection();
        try {
            // ZK客户端允许我们将ZK服务器的所有地址都配置在这里
            zk = new ZooKeeper(connectString, sessionTimeout, this);
            // 使用CountDownLatch.await()的线程(当前线程)阻塞直到所有其它拥有CountDownLatch的线程执行完毕(countDown()结果为0)
            connectedSemaphore.await();
        } catch (InterruptedException e) {
            LOG.error("连接创建失败,发生 InterruptedException , e " + e.getMessage(), e);
        } catch (IOException e) {
            LOG.error("连接创建失败,发生 IOException , e " + e.getMessage(), e);
        }
    }

    /**
     * <p>创建zNode节点, String create(path<节点路径>, data[]<节点内容>, List(ACL访问控制列表), CreateMode<zNode创建类型>) </p><br/>
     * <pre>
     *     节点创建类型(CreateMode)
     *     1、PERSISTENT:持久化节点
     *     2、PERSISTENT_SEQUENTIAL:顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
     *     3、EPHEMERAL:临时节点客户端,session超时这类节点就会被自动删除
     *     4、EPHEMERAL_SEQUENTIAL:临时自动编号节点
     * </pre>
     * @param path zNode节点路径
     * @param data zNode数据内容
     * @return 创建成功返回true, 反之返回false.
     */
    public boolean createPath(String path, String data) {
        try {
            String zkPath = this.zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            LOG.info("节点创建成功, Path: " + zkPath + ", content: " + data);
            return true;
        } catch (KeeperException e) {
            LOG.error("节点创建失败, 发生KeeperException! path: " + path + ", data:" + data + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error(
                    "节点创建失败, 发生 InterruptedException! path: " + path + ", data:" + data + ", errMsg:" + e.getMessage(),
                    e);
        }
        return false;
    }

    /**
     * <p>删除一个zMode节点, void delete(path<节点路径>, stat<数据版本号>)</p><br/>
     * <pre>
     *     说明
     *     1、版本号不一致,无法进行数据删除操作.
     *     2、如果版本号与znode的版本号不一致,将无法删除,是一种乐观加锁机制;如果将版本号设置为-1,不会去检测版本,直接删除.
     * </pre>
     * @param path zNode节点路径
     * @return 删除成功返回true, 反之返回false.
     */
    public boolean deletePath(String path) {
        try {
            this.zk.delete(path, -1);
            LOG.info("节点删除成功, Path: " + path);
            return true;
        } catch (KeeperException e) {
            LOG.error("节点删除失败, 发生KeeperException! path: " + path + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error("节点删除失败, 发生 InterruptedException! path: " + path + ", errMsg:" + e.getMessage(), e);
        }
        return false;
    }

    /**
     * <p>更新指定节点数据内容, Stat setData(path<节点路径>, data[]<节点内容>, stat<数据版本号>)</p>
     * <pre>
     *     设置某个znode上的数据时如果为-1,跳过版本检查
     * </pre>
     * @param path zNode节点路径
     * @param data zNode数据内容
     * @return 更新成功返回true, 返回返回false
     */
    public boolean writeData(String path, String data) {
        try {
            Stat stat = this.zk.setData(path, data.getBytes(), -1);
            LOG.info("更新数据成功, path:" + path + ", stat: " + stat);
            return true;
        } catch (KeeperException e) {
            LOG.error("更新数据失败, 发生KeeperException! path: " + path + ", data:" + data + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error("更新数据失败, 发生InterruptedException! path: " + path + ", data:" + data + ", errMsg:" + e.getMessage(),
                    e);
        }
        return false;
    }

    /**
     * <p>读取指定节点数据内容,byte[] getData(path<节点路径>, watcher<监视器>, stat<数据版本号>)</p>
     * @param path zNode节点路径
     * @return 节点存储的值, 有值返回, 无值返回null
     */
    public String readData(String path) {
        String data = null;
        try {
            data = new String(this.zk.getData(path, false, null));
            LOG.info("读取数据成功, path:" + path + ", content:" + data);
        } catch (KeeperException e) {
            LOG.error("读取数据失败,发生KeeperException! path: " + path + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error("读取数据失败,发生InterruptedException! path: " + path + ", errMsg:" + e.getMessage(), e);
        }
        return data;
    }

    /**
     * <p>获取某个节点下的所有子节点,List getChildren(path<节点路径>, watcher<监视器>)该方法有多个重载</p>
     * @param path zNode节点路径
     * @return 子节点路径集合 说明,这里返回的值为节点名
     * <pre>
     *     eg.
     *     /node
     *     /node/child1
     *     /node/child2
     *     getChild( "node" )户的集合中的值为["child1","child2"]
     * </pre>
     *
     *
     *
     * @throws KeeperException
     * @throws InterruptedException
     */
    public List<String> getChild(String path) {
        try {
            List<String> list = this.zk.getChildren(path, false);
            if (list.isEmpty()) {
                LOG.info("中没有节点" + path);
            }
            return list;
        } catch (KeeperException e) {
            LOG.error("读取子节点数据失败,发生KeeperException! path: " + path + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error("读取子节点数据失败,发生InterruptedException! path: " + path + ", errMsg:" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * <p>判断某个zNode节点是否存在, Stat exists(path<节点路径>, watch<并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher>)</p>
     * @param path zNode节点路径
     * @return 存在返回true, 反之返回false
     */
    public boolean isExists(String path) {
        try {
            Stat stat = this.zk.exists(path, false);
            return null != stat;
        } catch (KeeperException e) {
            LOG.error("读取数据失败,发生KeeperException! path: " + path + ", errMsg:" + e.getMessage(), e);
        } catch (InterruptedException e) {
            LOG.error("读取数据失败,发生InterruptedException! path: " + path + ", errMsg:" + e.getMessage(), e);
        }
        return false;
    }

    /**
     * Watcher Server,处理收到的变更
     * @param watchedEvent
     */
    public void process(WatchedEvent watchedEvent) {
        LOG.info("收到事件通知:" + watchedEvent.getState());
        if (Event.KeeperState.SyncConnected == watchedEvent.getState()) {
            connectedSemaphore.countDown();
        }
    }

    /**
     * 关闭ZK连接
     */
    public void releaseConnection() {
        if (null != zk) {
            try {
                this.zk.close();
            } catch (InterruptedException e) {
                LOG.error("release connection error ," + e.getMessage(), e);
            }
        }
    }

    public static void main(String[] args) {

        // 定义父子类节点路径
        String rootPath = "/blog/watcher";
        String child1Path = rootPath + "/nodeChildren1";
        String child2Path = rootPath + "/nodeChildren2";

        ZooKeeperWatcherTest zkWatchAPI = new ZooKeeperWatcherTest();

        // 连接zk服务器
        zkWatchAPI.connectionZookeeper("127.0.0.1:2181");

        // 创建节点数据
        if (zkWatchAPI.createPath(rootPath, "<父>节点数据")) {
            System.out.println("节点[" + rootPath + "]数据内容[" + zkWatchAPI.readData(rootPath) + "]");
        }
        // 创建子节点, 读取 + 删除
        if (zkWatchAPI.createPath(child1Path, "<父-子(1)>节点数据")) {
            System.out.println("节点[" + child1Path + "]数据内容[" + zkWatchAPI.readData(child1Path) + "]");
            zkWatchAPI.deletePath(child1Path);
            System.out.println("节点[" + child1Path + "]删除值后[" + zkWatchAPI.readData(child1Path) + "]");
        }

        // 创建子节点, 读取 + 修改
        if (zkWatchAPI.createPath(child2Path, "<父-子(2)>节点数据")) {
            System.out.println("节点[" + child2Path + "]数据内容[" + zkWatchAPI.readData(child2Path) + "]");
            zkWatchAPI.writeData(child2Path, "<父-子(2)>节点数据,更新后的数据");
            System.out.println("节点[" + child2Path + "]数据内容更新后[" + zkWatchAPI.readData(child2Path) + "]");
        }

        // 获取子节点
        List<String> childPaths = zkWatchAPI.getChild(rootPath);
        if (null != childPaths) {
            System.out.println("节点[" + rootPath + "]下的子节点数[" + childPaths.size() + "]");
            for (String childPath : childPaths) {
                System.out.println(" |--节点名[" + childPath + "]");
            }
        }

        // 判断节点是否存在
        System.out.println("检测节点[" + rootPath + "]是否存在:" + zkWatchAPI.isExists(rootPath));
        System.out.println("检测节点[" + child1Path + "]是否存在:" + zkWatchAPI.isExists(child1Path));
        System.out.println("检测节点[" + child2Path + "]是否存在:" + zkWatchAPI.isExists(child2Path));

        zkWatchAPI.releaseConnection();
    }
}

注:示例来自https://www.cnblogs.com/dennisit/p/4340746.html

posted @ 2020-08-19 20:37  fancyfairy  阅读(147)  评论(0)    收藏  举报
翻到最前~