En este tutorial vamos a presentar ZooKeeper, el proyecto de Apache que nos provee de un servicio centralizado para diversas tareas como: Mantenimiento de configuración, naming, sincronización distribuida o servicios de agrupación.
Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Características y Garantías
- 4. Modelo
- 5. API
- 6. Implementación
- 7. Instalación ZooKepper
- 8. Cliente Java ZooKeeper
- 9. Conclusiones
- 10. Referencias
1. Introducción
ZooKeeper es un proyecto de Apache que nos provee de un servicio centralizado para diversas
tareas como por ejemplo mantenimiento de configuración, naming, sincronización distribuida o servicios de agrupación, servicios que
normalmente son consumidos por otras aplicaciones distribuidas.
Veremos cuales son las características de ZooKeeper y que nos garantiza, su estructura general, sus operaciones básicas y echaremos un vistazo rápido para ver como funciona internamente.
Para finalizar el tutorial, mostraremos un pequeño proyecto donde implementaremos las operaciones básicas de un cliente de ZooKeeper.
¡Manos a la obra!
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3)
- Sistema Operativo: Mac OS Yosemite 10.11.4
- Entorno de desarrollo: IntelliJ IDEA 2016.1
- Apache Maven 3.3.0
- JDK 1.8.0_60
3. Características y Garantías
Cuatro son las principales características de ZooKeeper:
- Sencillo: Permite la coordinación entre procesos distribuidos mediante un namespace jerárquico que se organiza de manera similar a un file system.
El namespace consiste en registros (znodes) similares a ficheros o directorios.
En contraposición con el típico file system, ZooKeeper mantiene la
información en memoria permitiendo obtener latencias bajas y rendimientos altos. - Replicable: Permite las replicas instaladas en múltiples hosts, llamados conjuntos o agrupaciones.
Los servidores que conforman el servicio ZooKeeper deben conocerse todos entre ellos. Estos mantienen una imagen en memoria del estado, completado con un log
de transacciones y snapshots almacenados en un store persistente. Mientras la mayoría de servicios este disponible, ZooKeeper se mantendrá disponible. - Ordenado: ZooKeeper se encarga de marcar cada petición con un número que refleja su orden entre todas las transacciones de manera que permite mantener
por completo la trazabilidad de las operaciones realizadas. - Rápido: Sobre todo en entornos en los que predominen las operaciones de lectura.
Por otro lado, ZooKeeper nos garantiza los siguientes aspectos:
- Secuencialidad: Asegura que las operaciones de actualización se aplican en el mismo orden en el que se envían.
- Atomicidad: No existen los resultados parciales, las actualizaciones fallan o son un éxito.
- Único endpoint: Los clientes verán siempre el mismo servicio independientemente del servidor que lo contenga.
- Seguridad: En el momento en el que una actualización se ha aplicado, dicha modificación se mantendrá hasta el momento en que un cliente la sobreescriba.
- Actualización: El cliente tiene garantizado que, en un determinado intervalo temporal, los servicios estarán actualizados.
4. Modelo
Como hemos comentado con anterioridad, el modelo de datos que ZooKeeper permite definir es similar a un sistema de ficheros, en el que se pueden definir nodos de manerar jeraráquica: los Znodos.
Los Znodos mantienen información estadística que incluye: número de versión, cambios ACL (Access Control List) y timestamps que permitiran
crear validaciones de caché y actualizaciones coordinadas. Cada vez que la información de un Znodo se modifica, su número de versión aumenta.
La información almacenada de cada Znodo que se encuentre en el namespace se lee y escribe de manera automática.
5. API
5 son las operaciones básicas que nos ofrece ZooKeeper:
- create path data : Crea un nodo con una información (data) y en una localización (path) dados.
- delete path: Elimina un nodo en una localización (path) dada.
- exists path: Prueba si un nodo existe en una localización (path) dada.
- get path [watch]: Lee la información de un nodo en una localización (path) dada. De manera opcional se puede establecer un monitor (watch) que notifique cuando se produzca un cambio.
- set path data [version]: Escribe información (data) en un nodo localizado (path). De manera opcional se puede hacer uso de la versión del nodo [version] que se quiere modificar.
- sync path: Espera el cambio en un nodo localizado (path) para propagarlo.
6. Implementación
A continuación se muestran los componentes a alto nivel del servicio ZooKeeper. Con la excepción del Request Processor, cada servidor que conforma el servicio ZooKeeper replica su propia copia de cada componente.
La base de datos se encuentra replicada en cada servidor, cada copia es una base de datos en memoria que contiene por completo el árbol de nodos. Cuando se reciben actualizaciones en primer lugar se almacenan serializadas en disco para poder recuperar el estado en caso necesario y, con posterioridad, se aplican sobre la base de datos.
Todos los servidores ZooKeeper proveen a los clientes, pero un cliente se conecta unicamente a un servidor para realizar las peticiones.
Las peticiones de lectura se responden desde la replica local de la base de datos de cada servidor. Las peticiones que cambian el estado del servicio, las de escritura, se procesan bajo un protocolo de aceptación.
Como parte del protocolo de aceptación, todas las peticiones de escritura que provengan de los clientes se reenvían a un servidor denominado «lider», el resto de los servidores, los «seguidores», reciben las peticiones de mensajes del lider y aceptan acordar la entrega de mensajes. La capa de mensajería se encarga de reemplazar al lider en caso de error y sincronizar los seguidores con el lider.
7. Instalación ZooKeeper
A continuación se detallan los pasos para proceder con la instalación de un servidor standalone ZooKeeper en nuestros equipos Mac.
- Descargar la última release estable de ZooKeeper de su página oficial.
- Descomprimir el fichero descargado en el path deseado.
- Abrir un terminal y acceder al path de instalación de ZooKeeper que se establecio en el punto anterior.
- Acceder a la carpeta ./bin
- Lanzar el comando «./zkServer.sh start»
De esta manera habremos arrancado el servidor de ZooKeeper que se quedará a la espera de recibir mensajes.
8. Cliente Java ZooKeeper
A continuación se muestra, a modo de ejemplo, una clase para gestionar las principales acciones a realizar con un servidor de ZooKeeper:
@Component public class ZKClientManager implements ZKManager { private static final Logger LOGGER = LoggerFactory.getLogger(ZKClientManager.class); private static ZooKeeper zkeeper; private static ZKConnection zkConnection; public ZKClientManager() { } @PostConstruct private void initialize() { try { this.zkConnection = new ZKConnection(); this.zkeeper = zkConnection.connect("localhost"); } catch (IOException | InterruptedException e) { LOGGER.error(" =========> {}",e.getMessage()); } } public void closeConnection() { try { this.zkConnection.close(); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } public void create(String path, byte[] data) throws KeeperException, InterruptedException { this.zkeeper.create(path,data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } public Stat getZNodeStats(String path) throws KeeperException, InterruptedException { Stat stat = zkeeper.exists(path, Boolean.TRUE); if (stat != null) { LOGGER.info("Node exists and the node version is {}", stat.getVersion()); } else { LOGGER.info("Node does not exists"); } return stat; } public Object getZNodeData(String path, boolean watchFlag) throws KeeperException, InterruptedException { Stat stat = getZNodeStats(path); byte[] b = null; try { if (stat != null) { if (watchFlag) { ZKWatcher watch = new ZKWatcher(); b = this.zkeeper.getData(path, watch, null); watch.await(); } else { b = this.zkeeper.getData(path, null, null); } return new String(b, "UTF-8"); } else { LOGGER.info("Node does not exists"); } } catch (UnsupportedEncodingException e) { LOGGER.error(" =========> {}", e.getMessage()); } return null; } public void update(String path, byte[] data) throws KeeperException, InterruptedException { int version = this.zkeeper.exists(path, Boolean.TRUE).getVersion(); this.zkeeper.setData(path, data, version); } public List getZNodeChildern(String path) throws KeeperException, InterruptedException { Stat stat = getZNodeStats(path); List children = null; if (stat != null) { children = this.zkeeper.getChildren(path, Boolean.FALSE); for (int i = 0; i < children.size(); i++) { LOGGER.info(children.get(i)); } } else { LOGGER.info("Node does not exists"); } return children; } public void delete(String path) throws KeeperException, InterruptedException { int version = this.zkeeper.exists(path, Boolean.TRUE).getVersion(); this.zkeeper.delete(path, version); } }
El resto del código, además de algún otro ejemplo podéis encontrarlo en github.
9. Conclusiones
Como hemos visto Zookeeper es una herramienta que nos permite realizar múltiples tareas de coordinación de una manera fácil gracias a su sencilla API.
Además, se integra fácilmente con otros servicios como por ejemplo el sistema de almacenamiento distribuido Apache Kafka (como podemos ver en los tutoriales Primeros pasos con Apache Kafka ó Monitorización de Apache Kafka.
10. Referencias
- ZooKeeper Overview
- Apache Zookeeper Explained: Tutorial, Use Cases and Zookeeper Java API Examples. Binu George.