This crate provides a client for interacting with Apache ZooKeeper, a highly reliable distributed service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
The ZooKeeper Overview provides
a thorough introduction to ZooKeeper, but we'll repeat the most important points here. At its
heart, ZooKeeper
is a hierarchical key-value
store (that
is, keys can have "sub-keys"), which additional mechanisms that guarantee consistent operation
across client and server failures. Keys in ZooKeeper look like paths (e.g., /key/subkey
), and
every item along a path is called a
"Znode".
Each Znode (including those with children) can also have associated data, which can be queried
and updated like in other key-value stores. Along with its data and children, each Znode stores
meta-information such as access-control
lists,
modification
timestamps,
and a version number
that allows clients to avoid stepping on each other's toes when accessing values (more on that
later).
ZooKeeper's API consists of the same basic operations you would expect to find in a
file-system: create
for creating new Znodes,
delete
for removing them,
exists
for checking if a node exists,
get_data
and
set_data
for getting and setting a node's associated
data respectively, and get_children
for
retrieving the children of a given node (i.e., its subkeys). For all of these operations,
ZooKeeper gives strong
guarantees
about what happens when there are multiple clients interacting with the system, or even what
happens in response to system and network failures.
When you create a Znode, you also specify a [CreateMode
]. Nodes that are created with
[CreateMode::Persistent
] are the nodes we have discussed thus far. They remain in the server
until you delete them. Nodes that are created with [CreateMode::Ephemeral
] on the other hand
are special. These ephemeral
nodes are
automatically deleted by the server when the client that created them disconnects. This can be
handy for implementing lease-like mechanisms, and for detecting faults. Since they are
automatically deleted, and nodes with children cannot be deleted directly, ephemeral nodes are
not allowed to have children.
In addition to the methods above, [ZooKeeper::exists
], [ZooKeeper::get_data
], and
[ZooKeeper::get_children
] also support setting
"watches" on
a node. A watch is one-time trigger that causes a [WatchedEvent
] to be sent to the client
that set the watch when the state for which the watch was set changes. For example, for a
watched get_data
, a one-time notification will be sent the first time the data of the target
node changes following when the response to the original get_data
call was processed. You
should see the "Watches" entry in the Programmer's
Guide for
details.
To get ZooKeeper up and running, follow the official Getting Started
Guide. In most Linux
environments, the procedure for getting a basic setup working is usually just to install the
zookeeper
package and then run systemctl start zookeeper
. ZooKeeper will then be running at
127.0.0.1:2181
.
This library is analogous to the asynchronous API offered by the official Java implementation, and for most operations the Java documentation should apply to the Rust implementation. If this is not the case, it is considered a bug, and we'd love a bug report with as much relevant information as you can offer.
Note that since this implementation is asynchronous, users of the client must take care to not re-order operations in their own code. There is some discussion of this in the official documentation of the Java bindings.
For more information on ZooKeeper, see the ZooKeeper Programmer's Guide and the Confluence ZooKeeper wiki. There is also a basic tutorial (that uses the Java client) here.
The futures in this crate expect to be running under a tokio::runtime::Runtime
. In the common case,
they would be executed by .await
ing them in a context that is executed via #[tokio::main]
or #[tokio::test]
. You can also explicitly create a tokio::runtime::Runtime
and then use
Runtime::block_on
or Runtime::spawn
.
use tokio_zookeeper::*;
use futures::prelude::*;
let (zk, default_watcher) = ZooKeeper::connect(&"127.0.0.1:2181".parse().unwrap())
.await
.unwrap();
// let's first check if /example exists. the .watch() causes us to be notified
// the next time the "exists" status of /example changes after the call.
let stat = zk.watch().exists("/example").await.unwrap();
// initially, /example does not exist
assert_eq!(stat, None);
// so let's make it!
let path = zk
.create(
"/example",
&b"Hello world"[..],
Acl::open_unsafe(),
CreateMode::Persistent,
)
.await
.unwrap();
assert_eq!(path.as_deref(), Ok("/example"));
// does it exist now?
let stat = zk.watch().exists("/example").await.unwrap();
// looks like it!
// note that the creation above also triggered our "exists" watch!
assert_eq!(stat.unwrap().data_length as usize, b"Hello world".len());
// did the data get set correctly?
let res = zk.get_data("/example").await.unwrap();
let data = b"Hello world";
let res = res.unwrap();
assert_eq!(res.0, data);
assert_eq!(res.1.data_length as usize, data.len());
// let's update the data.
let stat = zk
.set_data("/example", Some(res.1.version), &b"Bye world"[..])
.await
.unwrap();
assert_eq!(stat.unwrap().data_length as usize, "Bye world".len());
// create a child of /example
let path = zk
.create(
"/example/more",
&b"Hello more"[..],
Acl::open_unsafe(),
CreateMode::Persistent,
)
.await
.unwrap();
assert_eq!(path.as_deref(), Ok("/example/more"));
// it should be visible as a child of /example
let children = zk.get_children("/example").await.unwrap();
assert_eq!(children, Some(vec!["more".to_string()]));
// it is not legal to delete a node that has children directly
let res = zk.delete("/example", None).await.unwrap();
assert_eq!(res, Err(error::Delete::NotEmpty));
// instead we must delete the children first
let res = zk.delete("/example/more", None).await.unwrap();
assert_eq!(res, Ok(()));
let res = zk.delete("/example", None).await.unwrap();
assert_eq!(res, Ok(()));
// no /example should no longer exist!
let stat = zk.exists("/example").await.unwrap();
assert_eq!(stat, None);
// now let's check that the .watch().exists we did in the very
// beginning actually triggered!
let (event, _w) = default_watcher.into_future().await;
assert_eq!(
event,
Some(WatchedEvent {
event_type: WatchedEventType::NodeCreated,
keeper_state: KeeperState::SyncConnected,
path: String::from("/example"),
})
);
This crate was originally developed by Jon Gjengset (@jonhoo) as part of his long-standing series of streams, starting at this video.