Understanding Core Concepts
Apache ZooKeeper is an open-source coordination service designed for distributed applications. It operates as a hierarchical namespace, similar to a standard filesystem, where every entry is identified as a ZNode (ZooKeeper node). These nodes facilitate synchronization, configuraton management, and naming services across networked machines.
Key responsibilities include:
- Centralized configuraton storage
- Distributed locking primitives
- Service discovery and leadership election
Local Environment Setup
Prerequisites
ZooKeeper runs on the Java Virtual Machine (JVM). Ensure JDK 7 or newer is installed before proceeding.
Installation Steps
- Download the binary release from the Apache archive.
- Move the archive to the target directory, typically
/opt/zookeeper/. - Extract the contents using
tar -zxvf.
# Navigate to install directory
cd /opt/
mkdir zookeeper
tar -zxvf apache-zookeeper-3.x.x-bin.tar.gz -C /opt/zookeeper/
Configuration
Locate the configuration file in the conf subdirectory. Rename zoo_sample.cfg to zoo.cfg and modify the data directory path.
# config file excerpt
dataDir=/var/lib/zookeeper/data
clientPort=2181
Starting the Service
Execute the server script to initialize the process.
cd bin
./zkServer.sh start
Verify the status with:
./zkServer.sh status
The output should indicate standalone mode for a single instance.
Command-Line Interface Operations
Server Commands
Manage the lifecycle of the server via CLI scripts.
- Start:
./zkServer.sh start - Status:
./zkServer.sh status - Stop:
./zkServer.sh stop
Client Interaction
Launch the CLI tool to connect and manipulate the registry.
./zkCli.sh -server localhost:2181
Node Management
Nodes are classified into four types based on persistence and sequencing behavior:
- PERSISTENT: Survives restarts unless explicitly deleted.
- EPHEMERAL: Auto-removed when the session ends.
- PERSISTENT_SEQUENTIAL: Auto-incrementing suffix appended.
- EPHEMERAL_SEQUENTIAL: Transient with sequential ordering.
Basic operations include:
ls / # List children
create /node-path value # Create node
data = get /node-path # Retrieve value
set /node-path new-value # Update value
delete /node-path # Remove node
Java Integration with Curator
Curator simplifies interacting with ZooKeeper by handling reconnection logic and providing high-level abstractions.
Establishing Connection
Initialize the framework with retry policies and session timeouts.
public class ZkClientConfig {
private static final String QUORUM_ADDRESS = "localhost:2181";
public static void main(String[] args) throws Exception {
RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
.connectString(QUORUM_ADDRESS)
.sessionTimeoutMs(60000)
.connectionTimeoutMs(15000)
.retryPolicy(policy)
.namespace("my-app")
.build();
zkClient.start();
// Perform operations here
}
}
Data Operations
Perform CRUD actions on ZNodes using fluent interfaces.
Create:
// Create persistent node with data
String createdPath = zkClient.create()
.forPath("/services/order-service", "active".getBytes());
// Create ephemeral node
zkClient.create().withMode(CreateMode.EPHEMERAL)
.forPath("/temp/lock");
Read:
// Get current data
byte[] data = zkClient.getData().forPath("/services/order-service");
// Monitor state changes
Stat stat = new Stat();
zkClient.getData().storingStatIn(stat).forPath("/services/order-service");
Update & Delete:
// Optimistic locking update
int version = stat.getVersion();
zkClient.setData().withVersion(version)
.forPath("/services/order-service", "inactive".getBytes());
// Recursive delete
zkClient.delete().deletingChildrenIfNeeded().forPath("/temp/old-data");
Watcher Mechanisms
Curator offers cache implementations to avoid manual listener registration.
- NodeCache: Monitors specific paths for data changes.
- PathChildrenCache: Observes child creation/deletion under a path.
- TreeCache: Tracks the entire subtree structure.
Example with NodeCache:
NodeCache cache = new NodeCache(zkClient, "/config/db-host");
cache.getListenable().addListener(() -> {
System.out.println("Config updated: " + new String(cache.getCurrentData().getData()));
});
cache.start(true);
Distributed Synchronization
Implementing locks across JVM boundaries requires coordination via the cluster. The core strategy involves creating ephemeral ordered nodes to determine lock ownership based on hierarchy.
Implementation Example
Using InterProcessMutex provides a semaphore-style lock mechanism suitable for non-reentrant scenarios.
public class DistributedTicketController {
private int availableSlots = 5;
private InterProcessMutex mutex;
public DistributedTicketController(CuratorFramework client, String lockPath) {
this.mutex = new InterProcessMutex(client, lockPath);
}
public void sellSlot() throws Exception {
if (mutex.acquire(5, TimeUnit.SECONDS)) {
try {
if (availableSlots > 0) {
availableSlots--;
System.out.println("Sold: " + availableSlots);
}
} finally {
mutex.release();
}
}
}
}
Cluster Architecture and Fault Tolerance
High availability is achieved through quorum configurations. A cluster consists of three or more servers communicating over specific ports for voting and data replication.
Configuration Requirements
Each instance requires a unique ID recorded in data/myid. The zoo.cfg must list all peers using the format server.ID=IP:SyncPort:LeaderElectionPort.
# Example cluster configuration
server.1=192.168.1.10:2881:3881
server.2=192.168.1.11:2882:3882
server.3=192.168.1.12:2883:3883
Failure Scenarios
- Follower Loss: If a follower stops, the remaining quorum functions normally.
- Majority Loss: If more than half the nodes fail, the leader cannot maintain a majority vote and shuts down to prevent data inconsistency.
- Leader Failure: A new election occurs automatically among surviving followers to select a replacement.
System Roles
- Leader: Processes write transactions and coordinates consensus.
- Follower: Handles read requests and participates in elections.
- Observer: Handles reads but does not vote, used to scale read throughput without affecting write performance.