Zookeeper hardly needs an introduction — it is a distributed coordination service. A few questions I was curious about:
- How does ZooKeeper implement asynchronous watcher callbacks? (code-level details)
- How does ZooKeeper implement distributed locks?
- How do Queue, barrier, and similar recipes work?
I read the Python client kazoo and got the general picture.
A simple client example
#!/usr/bin/python
import logging
from time import sleep
from kazoo.client import KazooClient
# print log to console
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
zk = KazooClient('127.0.0.1:2181')
zk.start()
def children_callback(children):
print '****' , children
children = zk.get_children('/zookeeper', children_callback)
zk.create('/zookeeper/goodboy')
#zk.delete('/zookeeper/goodboy')
while True: sleep(1)
How Kazoo handles asynchrony
First, a premise: every request from a client carries an xid. Each new request increments xid by 1, and the ZooKeeper server processes a single client’s requests strictly in increasing xid order before responding. Under that guarantee, the client can enqueue (request, async_object, xid) before sending (request holds the RPC details; async_object holds the callback). When any response arrives, dequeuing the head of the pending queue is enough to run the matching callback.
A more general pattern stores (request, async_object, xid) in a map keyed by xid, then looks up the entry when a response returns. Because ZooKeeper preserves order, Kazoo can get away with a single pending queue.
A few design constraints:
- Every API can be called synchronously or asynchronously; sync can be built on top of async.
- Watchers on each znode must fire asynchronously.
- The main thread must not block, because it runs application code.
How Kazoo works (using the example above)

A few notes:
In step 2, the KazooClient main thread uses an OS pipe for inter-thread signaling. The main thread writes one byte to the write pipe to wake thread_1.
In steps 3 and 4, thread_1 uses
select([socket, readpipe], [], [])to watch for read events on the socket and read pipe.- A readable socket means the ZooKeeper server has a response; read from the socket.
- A readable read pipe means the main thread enqueued another request (it pushes to the queue, then writes a byte to the write pipe).
In step 5, thread_1 increments the client xid and sends the request to ZooKeeper, then returns to its
while Trueloop watching the socket and write pipe — the two cases above.In steps 7 and 8, when the socket is readable, read the response and compare its xid with the request at the head of the pending queue; they must match to pair request and response.
In steps 9 and 10, route the response to either a watcher handler (thread-2) or a normal API callback (thread-3), completing the async call.
Kazoo recipes and tricks
Most ZooKeeper patterns rely on one idea: when an ephemeral znode disappears, the server sends a ChildrenChangeEvent to every client that registered a watcher on its parent. Each live client can then run a shared algorithm to decide who acts — for example, on ChildrenChangeEvent, the client with the smallest sequence number does the work. Everyone else keeps watching whether that leader is still alive; if not, they negotiate again.
Lock
The classic use case is high availability. A stateful service should not run in multiple active instances at once. A ZooKeeper lock ensures only one machine serves traffic at a time; the rest block. When the active instance dies, the lock is released and another instance grabs it and takes over — giving HA within one ZooKeeper session timeout.
The lock recipe follows the pattern above: clients elect a leader; the leader holds the lock while others watch the leader’s parent znode with exists and block.
Semaphore
Allow at most N clients to hold the lock at once. Kazoo builds this on the lock recipe plus ephemeral nodes. You could also try registering ephemeral sequential nodes — would that work?
Counter
A distributed counter: N clients can increment concurrently. Kazoo does not use ephemeral nodes; it relies on znode versions. Clients read the same version, increment locally, and only one write succeeds (ZooKeeper’s versioning enforces this). Failed clients retry.
Barrier
A barrier blocks all N clients until some condition is met. Kazoo watches a znode with exists, then blocks on an event until that znode is deleted, which unblocks everyone.
DoubleBarrier
A double barrier: no client proceeds until all N have arrived; no client leaves until all N have left. Early arrivals or early departures stay blocked.
Party
A party is just a set of ephemeral nodes. Creating an ephemeral node joins the party; deleting it leaves. Nothing more to it.
Queue
Blocking and non-blocking variants:
- Non-blocking queue: create child znodes under a parent and hand them to clients in lexicographic order (POP).
- Blocking queue: concurrent PUT is allowed, but only one client may POP at a time.