1. Información general
Apache ZooKeeper es un servicio de coordinación distribuida que facilita el desarrollo de aplicaciones distribuidas. Es utilizado por proyectos como Apache Hadoop, HBase y otros para diferentes casos de uso como elección de líder, administración de configuración, coordinación de nodos, administración de arrendamiento de servidores, etc.
Los nodos dentro del clúster de ZooKeeper almacenan sus datos en un espacio de nombres jerárquico compartido que es similar a un sistema de archivos estándar o una estructura de datos de árbol.
En este artículo, exploraremos cómo usar la API de Java de Apache Zookeeper para almacenar, actualizar y eliminar información almacenada en ZooKeeper.
2. Configuración
La última versión de la biblioteca Java de Apache ZooKeeper se puede encontrar aquí:
org.apache.zookeeper zookeeper 3.4.11
3. Modelo de datos de ZooKeeper: ZNode
ZooKeeper tiene un espacio de nombres jerárquico, muy parecido a un sistema de archivos distribuido donde almacena datos de coordinación como información de estado, información de coordinación, información de ubicación, etc. Esta información se almacena en diferentes nodos.
Cada nodo de un árbol de ZooKeeper se denomina ZNode.
Cada ZNode mantiene números de versión y marcas de tiempo para cualquier cambio de datos o ACL. Además, esto permite a ZooKeeper validar la caché y coordinar las actualizaciones.
4. Instalación
4.1. Instalación
La última versión de ZooKeeper se puede descargar desde aquí. Antes de hacer eso, debemos asegurarnos de cumplir con los requisitos del sistema que se describen aquí.
4.2. Modo autónomo
Para este artículo, ejecutaremos ZooKeeper en modo independiente ya que requiere una configuración mínima. Siga los pasos descritos en la documentación aquí.
Nota: En el modo autónomo, no hay replicación, por lo que si el proceso de ZooKeeper falla, el servicio dejará de funcionar.
5. Ejemplos de CLI de ZooKeeper
Ahora usaremos la interfaz de línea de comandos (CLI) de ZooKeeper para interactuar con ZooKeeper:
bin/zkCli.sh -server 127.0.0.1:2181
El comando anterior inicia una instancia independiente de forma local. Veamos ahora cómo crear un ZNode y almacenar información dentro de ZooKeeper:
[zk: localhost:2181(CONNECTED) 0] create /MyFirstZNode ZNodeVal Created /FirstZnode
Acabamos de crear un ZNode 'MyFirstZNode' en la raíz del espacio de nombres jerárquico de ZooKeeper y le escribimos 'ZNodeVal' .
Dado que no hemos pasado ninguna bandera, un ZNode creado será persistente.
Vamos a emitir ahora un comando 'get' para recuperar los datos y los metadatos asociados con un ZNode:
[zk: localhost:2181(CONNECTED) 1] get /FirstZnode “Myfirstzookeeper-app” cZxid = 0x7f ctime = Sun Feb 18 16:15:47 IST 2018 mZxid = 0x7f mtime = Sun Feb 18 16:15:47 IST 2018 pZxid = 0x7f cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 22 numChildren = 0
Podemos actualizar los datos de un ZNode existente usando la operación set .
Por ejemplo:
set /MyFirstZNode ZNodeValUpdated
Esto actualizará los datos en MyFirstZNode de ZNodeVal a ZNodeValUpdated.
6. Ejemplo de API Java de ZooKeeper
Veamos ahora la API de Java de Zookeeper y creemos un nodo, actualice el nodo y recuperemos algunos datos.
6.1. Paquetes de Java
Los enlaces Java de ZooKeeper se componen principalmente de dos paquetes Java:
- org.apache.zookeeper : que define la clase principal de la biblioteca cliente de ZooKeeper junto con muchas definiciones estáticas de los tipos y estados de eventos de ZooKeeper
- org.apache.zookeeper.data : que define las características asociadas con ZNodes, como Listas de control de acceso (ACL), ID, estadísticas, etc.
También hay API de Java de ZooKeeper que se utilizan en la implementación del servidor, como org.apache.zookeeper.server , org.apache.zookeeper.server.quorum y org.apache.zookeeper.server.upgrade .
Sin embargo, están más allá del alcance de este artículo.
6.2. Conexión a una instancia de ZooKeeper
Ahora creemos la clase ZKConnection que se utilizará para conectarse y desconectarse de un ZooKeeper que ya se esté ejecutando:
public class ZKConnection { private ZooKeeper zoo; CountDownLatch connectionLatch = new CountDownLatch(1); // ... public ZooKeeper connect(String host) throws IOException, InterruptedException { zoo = new ZooKeeper(host, 2000, new Watcher() { public void process(WatchedEvent we) { if (we.getState() == KeeperState.SyncConnected) { connectionLatch.countDown(); } } }); connectionLatch.await(); return zoo; } public void close() throws InterruptedException { zoo.close(); } }
Para utilizar un servicio de ZooKeeper, una aplicación primero debe crear una instancia de un objeto de la clase ZooKeeper , que es la clase principal de la biblioteca cliente de ZooKeeper .
In connect method, we're instantiating an instance of ZooKeeper class. Also, we've registered a callback method to process the WatchedEvent from ZooKeeper for connection acceptance and accordingly finish the connect method using countdown method of CountDownLatch.
Once a connection to a server is established, a session ID gets assigned to the client. To keep the session valid, the client should periodically send heartbeats to the server.
The client application can call ZooKeeper APIs as long as its session ID remains valid.
6.3. Client Operations
We'll now create a ZKManager interface which exposes different operations like creating a ZNode and saving some data, fetching and updating the ZNode Data:
public interface ZKManager { public void create(String path, byte[] data) throws KeeperException, InterruptedException; public Object getZNodeData(String path, boolean watchFlag); public void update(String path, byte[] data) throws KeeperException, InterruptedException; }
Let's now look at the implementation of the above interface:
public class ZKManagerImpl implements ZKManager { private static ZooKeeper zkeeper; private static ZKConnection zkConnection; public ZKManagerImpl() { initialize(); } private void initialize() { zkConnection = new ZKConnection(); zkeeper = zkConnection.connect("localhost"); } public void closeConnection() { zkConnection.close(); } public void create(String path, byte[] data) throws KeeperException, InterruptedException { zkeeper.create( path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } public Object getZNodeData(String path, boolean watchFlag) throws KeeperException, InterruptedException { byte[] b = null; b = zkeeper.getData(path, null, null); return new String(b, "UTF-8"); } public void update(String path, byte[] data) throws KeeperException, InterruptedException { int version = zkeeper.exists(path, true).getVersion(); zkeeper.setData(path, data, version); } }
In the above code, connect and disconnect calls are delegated to the earlier created ZKConnection class. Our create method is used to create a ZNode at given path from the byte array data. For demonstration purpose only, we've kept ACL completely open.
Once created, the ZNode is persistent and doesn't get deleted when the client disconnects.
The logic to fetch ZNode data from ZooKeeper in our getZNodeData method is quite straightforward. Finally, with the update method, we're checking the presence of ZNode on given path and fetching it if it exists.
Beyond that, for updating the data, we first check for ZNode existence and get the current version. Then, we invoke the setData method with the path of ZNode, data and current version as parameters. ZooKeeper will update the data only if the passed version matches with the latest version.
7. Conclusion
When developing distributed applications, Apache ZooKeeper plays a critical role as a distributed coordination service. Specifically for use cases like storing shared configuration, electing the master node, and so on.
ZooKeeper también proporciona una elegante API basada en Java para usar en el código de la aplicación del lado del cliente para una comunicación fluida con ZooKeeper ZNodes.
Y como siempre, todas las fuentes de este tutorial se pueden encontrar en Github.