This is article 30 in the Big Data series. Demonstrates how to operate ZooKeeper using Java code via ZkClient library, including establishing connection, node CRUD, child node monitoring, and data change monitoring.

Complete illustrated version: CSDN Original | Juejin

Why Use ZkClient

ZooKeeper official Java client (org.apache.zookeeper.ZooKeeper) has relatively low-level API, requires manually handling Session reconnection, Watcher re-registration, and other details. ZkClient is a high-level wrapper of the official client, providing:

  • Automatic reconnection and Session recovery
  • Persistent Watcher (internally auto re-registers)
  • Synchronous API, reduces callback nesting

Maven Dependencies

<dependencies>
    <!-- ZooKeeper core library, version should match server -->
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.8.4</version>
    </dependency>

    <!-- ZkClient high-level wrapper -->
    <dependency>
        <groupId>com.101tec</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.11</version>
    </dependency>
</dependencies>

Establish Session Connection

import org.I0Itec.zkclient.ZkClient;

public class ZkDemo {
    // Connection string: multiple nodes separated by comma, client will auto load balance
    private static final String ZK_SERVERS = "h121.wzk.icu:2181,h122.wzk.icu:2181,h123.wzk.icu:2181";

    public static void main(String[] args) throws InterruptedException {
        ZkClient zkClient = new ZkClient(ZK_SERVERS);
        System.out.println("ZooKeeper session created.");

        // Keep main thread running, wait for async notifications
        Thread.sleep(Long.MAX_VALUE);
    }
}

ZkClient constructor blocks until connection is established.

Node Operations

Create Persistent Node (Recursive)

// Second parameter true means recursive create parent directories (like mkdir -p)
// If /wzk-java doesn't exist, will auto create
zkClient.createPersistent("/wzk-java/temp", true);
System.out.println("ZooKeeper create ZNode: /wzk-java/temp");

Equivalent zkCli command:

create -p /wzk-java ""
create /wzk-java/temp ""

Create Ephemeral Node

// Ephemeral node: auto deleted after Session disconnects, cannot have child nodes
zkClient.createEphemeral("/wzk-java/session-lock");
System.out.println("Ephemeral node created.");

Write Data

// Write when node exists, error if doesn't exist (need createPersistent first)
zkClient.writeData("/wzk-java/temp", "hello-zookeeper");

Read Data

String data = zkClient.readData("/wzk-java/temp");
System.out.println("Node data: " + data);

Delete Node (Recursive)

// deleteRecursive deletes node and all child nodes recursively
zkClient.deleteRecursive("/wzk-java");
System.out.println("ZooKeeper delete recursive: /wzk-java");

Equivalent zkCli command:

deleteall /wzk-java

Monitoring: Child Node Changes

subscribeChildChanges monitors child node list add/delete changes under specified path, doesn’t monitor target node’s own data changes.

// First create parent node
zkClient.createPersistent("/wzk-data", true);

zkClient.subscribeChildChanges("/wzk-data", new IZkChildListener() {
    @Override
    public void handleChildChange(String parentPath, List<String> currentChilds) {
        System.out.println("子节点变化!");
        System.out.println("  父节点路径: " + parentPath);
        System.out.println("  当前子节点列表: " + currentChilds);
    }
});

System.out.println("已注册子节点监听,等待变更...");

Trigger test (zkCli):

# Create child node → trigger notification
create /wzk-data/child-1 "data1"

# Create another one
create /wzk-data/child-2 "data2"

# Delete child node → trigger again
delete /wzk-data/child-1

Output example:

子节点变化!
  父节点路径: /wzk-data
  当前子节点列表: [child-1]

子节点变化!
  父节点路径: /wzk-data
  当前子节点列表: [child-1, child-2]

子节点变化!
  父节点路径: /wzk-data
  当前子节点列表: [child-2]

ZkClient’s subscribeChildChanges internally auto re-registers after each notification, no manual handling needed.

Monitoring: Node Data Changes

subscribeDataChanges monitors specified node’s data modification and node deletion events.

Since ZkClient default serializer cannot deserialize ordinary strings, need to switch to SerializableSerializer (or custom serializer):

import org.I0Itec.zkclient.serialize.SerializableSerializer;

// Switch serializer (String implements Serializable, can use directly)
zkClient.setZkSerializer(new SerializableSerializer());

// First write initial data
zkClient.createPersistent("/wzk-data/test-data", true);
zkClient.writeData("/wzk-data/test-data", "initial-value");

// Register data change monitoring
zkClient.subscribeDataChanges("/wzk-data/test-data", new IZkDataListener() {
    @Override
    public void handleDataChange(String dataPath, Object data) {
        System.out.println("数据改变: " + dataPath + " → " + data);
    }

    @Override
    public void handleDataDeleted(String dataPath) {
        System.out.println("节点被删除: " + dataPath);
    }
});

System.out.println("已注册数据监听,等待变更...");

Trigger test (zkCli):

# Modify data → trigger handleDataChange
set /wzk-data/test-data "updated-value"

# Modify again
set /wzk-data/test-data "final-value"

# Delete node → trigger handleDataDeleted
delete /wzk-data/test-data

Output example:

数据改变: /wzk-data/test-data → updated-value
数据改变: /wzk-data/test-data → final-value
节点被删除: /wzk-data/test-data

ZkClient vs Native API Comparison

CapabilityNative ZooKeeper APIZkClient
Watcher Re-registrationManual, need re-call after each triggerAuto, internal persistent monitoring
Session ReconnectionNeed implement reconnection logic yourselfAuto reconnection
Exception HandlingNeed handle KeeperExceptionWrapped as ZkException
API StyleMostly async callbacksBoth sync and async supported
Recursive Create/DeleteNot supportedcreatePersistent(path, true) / deleteRecursive

Complete Example Code Structure

public class ZooKeeperJavaDemo {
    public static void main(String[] args) throws InterruptedException {
        ZkClient zkClient = new ZkClient("h121.wzk.icu:2181");

        // 1. Create node
        zkClient.createPersistent("/wzk-java/data", true);
        zkClient.writeData("/wzk-java/data", "hello");

        // 2. Register child node monitoring
        zkClient.subscribeChildChanges("/wzk-java", (parent, children) -> {
            System.out.println("Children changed: " + children);
        });

        // 3. Register data monitoring
        zkClient.setZkSerializer(new SerializableSerializer());
        zkClient.subscribeDataChanges("/wzk-java/data", new IZkDataListener() {
            public void handleDataChange(String path, Object data) {
                System.out.println("Data changed: " + data);
            }
            public void handleDataDeleted(String path) {
                System.out.println("Node deleted: " + path);
            }
        });

        // 4. Wait for events
        Thread.sleep(60_000);

        // 5. Cleanup
        zkClient.deleteRecursive("/wzk-java");
        System.out.println("Cleanup done.");
    }
}

Summary

ZkClient significantly simplifies ZooKeeper Java programming complexity: recursive node operations, auto reconnection, persistent Watcher are all out-of-the-box. In production, Curator Framework (Apache maintained) provides more complete high-level abstractions (distributed locks, Leader election, etc.), is the advanced alternative to ZkClient. ZooKeeper basics series ends here, next phase will enter Kafka message queue learning.