ZooKeeper API

Explains the Java API in ZooKeeper.

ZooKeeper provides official APIs for Java and C, and the ZooKeeper community provides unofficial APIs for several languages such as .NET and Python.

ZooKeeper API Overview

Znode is a core component of a ZooKeeper ensemble, and the ZooKeeper API provides simple methods for detailed manipulation of the ZooKeeper ensemble and znodes.

When a client connects to the ZooKeeper ensemble, the ensemble issues one Session ID to the client. The client periodically sends heartbeats to the server. Otherwise, the ZooKeeper ensemble expires the Session ID and the client must reconnect. Znode Get/Set operations remain active as long as the session ID exists. When all operations are complete, close the connection to the ZooKeeper ensemble. If a client remains inactive for a long time, the ZooKeeper ensemble automatically closes the connection to the client.

Add the ZooKeeper API Library

First, add the following library to use the ZooKeeper API.

dependencies {
    implementation 'org.apache.zookeeper:zookeeper:3.8.1'
}

Java Binding

The central part of the ZooKeeper API is the ZooKeeper class. It provides options through constructors and methods for connecting to the ZooKeeper ensemble.

  • connect: connect to the ZooKeeper ensemble
  • create: create a znode
  • exists: check whether a znode exists and get its information
  • getData: get data from a specific znode
  • setData: set data in a specific znode
  • getChildren: get all available child nodes under a specific znode
  • delete: delete a specific znode and all of its children
  • close: disconnect

connect: Connect to the ZooKeeper Ensemble

The ZooKeeper class provides connection functionality through its constructor.

ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
  • connectionString: ZooKeeper ensemble host
  • sessionTimeout: session timeout in milliseconds
  • watcher: an object that implements the Watcher interface. The ZooKeeper ensemble returns connection status through the watcher object.

Create a new helper class named ZooKeeperConnection and add the connect method. The connect method creates one ZooKeeper object, connects it to the ZooKeeper ensemble, and returns the object.

Here, CountDownLatch calls await to make the main process wait until the client is connected.

The ZooKeeper ensemble receives connection status through the Watcher callback. The Watcher callback is called once when the client connects to the ZooKeeper ensemble, and the callback calls the countDown method of CountDownLatch to release the await lock of the main process.

package com.devkuma.zookeeper;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

public class ZooKeeperConnection {

    private ZooKeeper zoo;
    final CountDownLatch connectedSignal = new CountDownLatch(1);

    public ZooKeeper connect(String host) throws IOException, InterruptedException {
        zoo = new ZooKeeper(host, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if (event.getState() == Event.KeeperState.SyncConnected) {
                    connectedSignal.countDown();
                }
            }
        });

        connectedSignal.await();
        return zoo;
    }

    public void close() throws InterruptedException {
        zoo.close();
    }
}

create: Create a Znode

The ZooKeeper class provides the create method for creating a new znode in the ZooKeeper ensemble.

create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
  • path: path of the znode.
    • For example, /myapp1, /myapp2, /myapp1/mydata1, myapp2/mydata1/myanothersubdata
  • data: data stored at the specific znode path
  • acl: access control list of the created node. The ZooKeeper API provides the ZooDefs.Ids static interface to get the default ACL list.
    • For example, ZooDefs.Ids.OPEN_ACL_UNSAFE returns the ACL list for an open znode.
  • createMode: the type of node. It can be ephemeral, sequential, or both. This is an enum value.

To check the behavior of the create method, create a new Java application. In the main method, create an object of type ZooKeeperConnection and connect to the ZooKeeper ensemble through the connect method.
The connect method returns zk, a ZooKeeper object. Call the create method of the zk object with a custom path and data.

package com.devkuma.zookeeper;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

public class ZKCreate {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static void create(String path, byte[] data) throws KeeperException, InterruptedException {
        zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";

        byte[] data = "My first zookeeper app".getBytes();

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            create(path, data);
            conn.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

exists: Check Whether a Znode Exists

The ZooKeeper class provides the exists method to check whether a znode exists. If the znode exists, it returns the znode metadata.

exists(String path, boolean watcher)
  • path: znode path
  • watcher: boolean value specifying whether to watch the specified znode
package com.devkuma.zookeeper;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZKExist {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static Stat znode_exists(String path) throws KeeperException, InterruptedException {
        return zk.exists(path, true);
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            Stat stat = znode_exists(path);

            if (stat != null) {
                System.out.println("Node exists and the node version is " + stat.getVersion());
            } else {
                System.out.println("Node does not exists.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

getData: Query Znode Data

The ZooKeeper class provides the getData method to obtain data and status information stored in a specified znode.

getData(String path, Watcher watcher, Stat stat)
  • path: path of the znode.
  • watcher: callback function of type Watcher. The ZooKeeper ensemble notifies through this Watcher callback when data in the specified znode changes.
  • stat: returns znode metadata.
package com.devkuma.zookeeper;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZKGetData {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static Stat znode_exists(String path) throws KeeperException, InterruptedException {
        return zk.exists(path, true);
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";
        final CountDownLatch connectedSignal = new CountDownLatch(1);

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            Stat stat = znode_exists(path);

            if (stat != null) {
                byte[] b = zk.getData(path, event -> {
                    if (event.getType() == Watcher.Event.EventType.None) {
                        switch (event.getState()) {
                            case Expired:
                                connectedSignal.countDown();
                                break;
                        }
                    } else {
                        String path1 = "/MyFirstZnode";

                        try {
                            byte[] bn = zk.getData(path1, false, null);
                            String data = new String(bn, "UTF-8");
                            System.out.println(data);
                            connectedSignal.countDown();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }, null);

                String data = new String(b, "UTF-8");
                System.out.println(data);

                connectedSignal.await();
            } else {
                System.out.println("Node does not exists.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

When the application runs, the currently set data is printed to the console and the application waits in the await state. A callback function for Watcher has also been specified, so if you set data using zkCli, the Watcher callback function is executed.

bin/zkCli.sh
>>> set /MyFirstZnode Hello

Then the callback function is called, Hello is printed to the console, and the await lock is released through the countDown method, so the application exits.

Hello

setData: Set Znode Data

The ZooKeeper class provides the setData method to change and add data to the specified znode.

setData(String path, byte[] data, int version)
  • path: path of the znode.
  • data: data stored at the specified znode path.
  • version: current version of the znode. ZooKeeper updates the znode version number when data changes.
package com.devkuma.zookeeper;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;

public class ZKSetData {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static void update(String path, byte[] data) throws KeeperException, InterruptedException {
        zk.setData(path, data, zk.exists(path, true).getVersion());
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";
        byte[] data = "Success".getBytes();

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            update(path, data);
            System.out.println("Done.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

When the application runs, the data of the specified znode changes. You can check it using zkCli.sh, the ZooKeeper CLI.

cd /path/to/zookeeper
bin/zkCli.sh
>>> get /MyFirstZnode

getChildren: Get Child Node Data

The ZooKeeper class provides the getChildren method to get all child nodes of a specific znode.

getChildren(String path, Watcher watcher)
  • path: path of the znode.
  • watcher: callback function of type Watcher. The ZooKeeper ensemble notifies through this Watcher callback when the data of the specified znode is deleted or when children under the znode are created or deleted.
package com.devkuma.zookeeper;

import java.util.List;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ZKGetChildren {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static Stat znode_exists(String path) throws KeeperException, InterruptedException {
        return zk.exists(path, true);
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            Stat stat = znode_exists(path);

            if (stat != null) {
                List<String> children = zk.getChildren(path, false);
                children.forEach(System.out::println);
            } else {
                System.out.println("Node does not exists.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If no child nodes exist, add them using zkCli.

bin/zkCli.sh
>>> create /MyFirstZnode/myfirstsubnode Hi
>>> create /MyFirstZnode/mysecondsubnode Hi

delete: Delete a Znode

The ZooKeeper class provides the delete method to remove a specified znode.

delete(String path, int version)
  • path: path of the znode.
  • version: current version of the znode.
package com.devkuma.zookeeper;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;

public class ZKDelete {
    private static ZooKeeper zk;
    private static ZooKeeperConnection conn;

    public static void delete(String path) throws KeeperException, InterruptedException {
        zk.delete(path, zk.exists(path, true).getVersion());
    }

    public static void main(String[] args) {
        String path = "/MyFirstZnode";

        try {
            conn = new ZooKeeperConnection();
            zk = conn.connect("localhost");
            delete(path);
            System.out.println("Done.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

At this point, if child nodes exist under the specified node, an exception occurs. Therefore, delete all child nodes before running delete.

org.apache.zookeeper.KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /MyFirstZnode
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:125)
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
	at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:873)
	at ZKDelete.delete(ZKDelete.java:11)
	at ZKDelete.main(ZKDelete.java:20)

References