Capítulo 1. Introducción Introducción

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Antes, cada aplicación era un único programa que se ejecutaba en un único ordenador con una única CPU. Hoy, las cosas han cambiado. En el mundo de Big Data y Cloud Computing, las aplicaciones están formadas por muchos programas independientes que se ejecutan en un conjunto de ordenadores en constante cambio.

Coordinar las acciones de estos programas independientes es mucho más difícil que escribir un único programa para que se ejecute en un único ordenador. Es fácil que los desarrolladores se enfrasquen en la lógica de la coordinación y carezcan de tiempo para escribir correctamente la lógica de su aplicación, o quizás lo contrario, que dediquen poco tiempo a la lógica de la coordinación y se limiten a escribir un coordinador maestro rápido y sucio que sea frágil y se convierta en un punto único de fallo poco fiable.

ZooKeeper se diseñó para ser un servicio robusto que permita a los desarrolladores de aplicaciones centrarse principalmente en la lógica de su aplicación, en lugar de en la coordinación. Expone una API sencilla, inspirada en la API del sistema de archivos, que permite a los desarrolladores implementar tareas comunes de coordinación, como elegir un servidor maestro, gestionar la pertenencia a grupos y gestionar metadatos. ZooKeeper es una biblioteca de aplicaciones con dos implementaciones principales de las API -Java y C- y un componente de servicio implementado en Java que se ejecuta en un conjunto de servidores dedicados. Tener un conjunto de servidores permite a ZooKeeper tolerar fallos y escalar el rendimiento.

Al diseñar una aplicación con ZooKeeper, lo ideal es separar los datos de la aplicación de los datos de control o coordinación. Por ejemplo, a los usuarios de un servicio de correo electrónico les interesa el contenido de su buzón, pero no les preocupa qué servidor gestiona las peticiones de un buzón concreto. El contenido del buzón es un dato de aplicación, mientras que la asignación del buzón a un servidor de correo concreto forma parte de los datos de coordinación (o metadatos). Un conjunto ZooKeeper gestiona estos últimos.

La misión de ZooKeeper

Intentar explicar lo que ZooKeeper hace por nosotros es como intentar explicar lo que un destornillador puede hacer por nosotros. En términos muy básicos, un destornillador nos permite girar o apretar tornillos, pero decirlo así no expresa realmente el poder de la herramienta. Nos permite montar muebles y aparatos electrónicos, y en algunos casos colgar cuadros en la pared. Poniendo algunos ejemplos, damos una idea de lo que puede hacer por nosotros, pero desde luego no es exhaustiva.

El argumento de lo que un sistema como ZooKeeper puede hacer por nosotros va en la misma línea: permite realizar tareas de coordinación para sistemas distribuidos. Una tarea de coordinación es una tarea en la que intervienen varios procesos. Dicha tarea puede tener fines de cooperación o de regulación de la contención. La cooperación significa que los procesos necesitan hacer algo juntos, y los procesos actúan para permitir que otros procesos avancen. Por ejemplo, en las típicas arquitecturas maestro-trabajador, el trabajador informa al maestro de que está disponible para realizar un trabajo. En consecuencia, el maestro asigna tareas al trabajador. La contención es diferente: se refiere a situaciones en las que dos procesos no pueden progresar simultáneamente, por lo que uno debe esperar al otro. Utilizando el mismo ejemplo de maestro-trabajador, en realidad queremos tener un único maestro, pero varios procesos pueden intentar convertirse en el maestro. En consecuencia, los múltiples procesos necesitan implementar la exclusión mutua. En realidad, podemos pensar en la tarea de adquirir la maestría como en la de adquirir un bloqueo: el proceso que adquiere el bloqueo de maestría ejerce el papel de maestro.

Si tienes alguna experiencia con programas multihilo, reconocerás que hay muchos problemas similares. De hecho, tener varios procesos ejecutándose en el mismo ordenador o en varios ordenadores no es conceptualmente diferente en absoluto. Las primitivas de sincronización que son útiles en el contexto de múltiples hilos también lo son en el contexto de sistemas distribuidos. Sin embargo, una diferencia importante se deriva del hecho de que los distintos ordenadores no comparten nada más que la red en una arquitectura típica de "nada compartido". Aunque existen varios algoritmos de paso de mensajes para implementar primitivas de sincronización, normalmente es mucho más fácil confiar en un componente que proporcione un almacén compartido con algunas propiedades especiales de ordenación, como hace ZooKeeper.

La coordinación no siempre adopta la forma de primitivas de sincronización, como la elección del líder o los bloqueos. Los metadatos de configuración se utilizan a menudo como una forma de que un proceso transmita lo que deben hacer los demás. Por ejemplo, en un sistema maestro-trabajador, los trabajadores necesitan conocer las tareas que se les han asignado, y esta información debe estar disponible incluso si el maestro se bloquea.

Veamos algunos ejemplos en los que ZooKeeper ha sido útil para tener una mejor idea de dónde es aplicable:

Apache HBase

HBase es un almacén de datos que suele utilizarse junto con Hadoop. En HBase, ZooKeeper se utiliza para elegir un maestro de clúster, llevar un registro de los servidores disponibles y mantener los metadatos del clúster .

Apache Kafka

Kafka es un sistema de mensajería pub-sub. Utiliza ZooKeeper para detectar fallos, implementar el descubrimiento de temas y mantener el estado de producción y consumo de los temas.

Apache Solr

Solr es una plataforma de búsqueda empresarial. En su forma distribuida, llamada SolrCloud, utiliza ZooKeeper para almacenar metadatos sobre el clúster y coordinar las actualizaciones de estos metadatos.

¡Servicio de búsqueda de Yahoo!

Como parte de la implementación de un rastreador, el Servicio de Obtención obtiene páginas web de forma eficiente almacenando en caché el contenido y asegurándose de que se conservan las políticas del servidor web, como las de los archivos robots.txt. Este servicio utiliza ZooKeeper para tareas como la elección maestra, la detección de fallos y el almacenamiento de metadatos.

Mensajes de Facebook

Se trata de una aplicación de Facebook que integra canales de comunicación: correo electrónico, SMS, chat de Facebook y la bandeja de entrada existente de Facebook. Utiliza ZooKeeper como controlador para implementar la fragmentación y la conmutación por error, y también para el descubrimiento de servicios.

Hay muchos más ejemplos; esto es sólo una muestra. Dada esta muestra, llevemos ahora la discusión a un nivel más abstracto. Al programar con ZooKeeper, los desarrolladores diseñan sus aplicaciones como un conjunto de clientes que se conectan a los servidores ZooKeeper e invocan operaciones sobre ellos a través de la API de cliente ZooKeeper. Entre los puntos fuertes de la API de ZooKeeper , se encuentran:

  • Garantías sólidas de coherencia, ordenación y durabilidad

  • La capacidad de implementar primitivas de sincronización típicas

  • Una forma más sencilla de tratar muchos aspectos de la concurrencia que a menudo conducen a un comportamiento incorrecto en los sistemas distribuidos reales

Sin embargo, ZooKeeper no es mágico; no resolverá todos los problemas nada más sacarlo de la caja. Es importante comprender lo que ofrece ZooKeeper y ser consciente de sus aspectos delicados. Uno de los objetivos de este libro es discutir las formas de abordar estas cuestiones. Cubrimos el material básico necesario para que el lector comprenda lo que ZooKeeper hace realmente por los desarrolladores. Además, tratamos varios problemas con los que nos hemos encontrado al implementar aplicaciones con ZooKeeper y ayudar a los desarrolladores nuevos en ZooKeeper.

Cómo sobrevivió el mundo sin ZooKeeper

¿Ha permitido ZooKeeper desarrollar toda una nueva clase de aplicaciones? No parece que sea así. En cambio, ZooKeeper simplifica el proceso de desarrollo, haciéndolo más ágil y permitiendo implementaciones más robustas.

Los sistemas anteriores han implementado componentes como gestores de bloqueos distribuidos o han utilizado bases de datos distribuidas para la coordinación. ZooKeeper, de hecho, toma prestados varios conceptos de estos sistemas anteriores. Sin embargo, no expone una interfaz de bloqueo ni una interfaz de uso general para almacenar datos. El diseño de ZooKeeper es especializado y muy centrado en tareas de coordinación. Al mismo tiempo, no trata de imponer al desarrollador un conjunto concreto de primitivas de sincronización, siendo muy flexible respecto a lo que se puede implementar.

Desde luego, es posible construir sistemas distribuidos sin utilizar ZooKeeper. Sin embargo, ZooKeeper ofrece a los desarrolladores la posibilidad de centrarse más en la lógica de la aplicación que en arcanos conceptos de sistemas distribuidos. Programar sistemas distribuidos sin ZooKeeper es posible, pero más difícil.

Lo que ZooKeeper no hace

El conjunto de servidores ZooKeeper gestiona los datos críticos de la aplicación relacionados con la coordinación. ZooKeeper no es para almacenamiento masivo. Para el almacenamiento masivo de datos de aplicación, hay varias opciones disponibles, como bases de datos y sistemas de archivos distribuidos. Cuando se diseña una aplicación con ZooKeeper, lo ideal es separar los datos de la aplicación de los datos de control o coordinación. A menudo tienen requisitos diferentes; por ejemplo, en cuanto a consistencia y durabilidad.

ZooKeeper implementa un conjunto básico de operaciones que permiten realizar tareas comunes a muchas aplicaciones distribuidas. ¿Cuántas aplicaciones conoces que tengan un maestro o necesiten realizar un seguimiento de los procesos que responden? ZooKeeper, sin embargo, no implementa las tareas por ti. No elige un maestro ni realiza un seguimiento de los procesos vivos de la aplicación de forma inmediata. En su lugar, proporciona las herramientas para implementar dichas tareas. El desarrollador decide qué tareas de coordinación implementar.

El Proyecto Apache

ZooKeeper es un proyecto de código abierto auspiciado por la Apache Software Foundation. Tiene un Comité de Gestión del Proyecto (CGP) que es responsable de la gestión y supervisión del proyecto. Sólo los committers pueden registrar parches, pero cualquier desarrollador puede contribuir con un parche. Los desarrolladores pueden convertirse en committers tras contribuir al proyecto. Las contribuciones al proyecto no se limitan a los parches, sino que pueden adoptar otras formas e interactuar con otros miembros de la comunidad. Tenemos muchas discusiones en las listas de correo sobre nuevas funciones, preguntas de nuevos usuarios, etc. Animamos encarecidamente a los desarrolladores interesados en participar en la comunidad a que se suscriban a las listas de correo y participen en los debates. Si quieres tener una relación a largo plazo con ZooKeeper a través de algún proyecto, puede que también te merezca la pena convertirte en committer.

Construir sistemas distribuidos con ZooKeeper

Existen múltiples definiciones de sistema distribuido, pero a efectos de este libro, lo definimos como un sistema formado por múltiples componentes de software que se ejecutan de forma independiente y concurrente en varias máquinas físicas. Hay varias razones para diseñar un sistema de forma distribuida. Un sistema distribuido es capaz de explotar la capacidad de múltiples procesadores ejecutando componentes, quizá replicados, en paralelo. Un sistema puede estar distribuido geográficamente por razones estratégicas, como la presencia de servidores en varias ubicaciones que participan en una misma aplicación.

Tener un componente de coordinación independiente tiene un par de ventajas importantes. En primer lugar, permite que el componente se diseñe e implemente de forma independiente. Un componente independiente de este tipo puede compartirse en muchas aplicaciones. En segundo lugar, permite a un arquitecto de sistemas razonar más fácilmente sobre el aspecto de la coordinación, que no es trivial (como intenta exponer este libro). Por último, permite que un sistema ejecute y gestione el componente de coordinación por separado. Ejecutar dicho componente por separado simplifica la tarea de resolver problemas en producción.

Los componentes de software se ejecutan en procesos del sistema operativo, en muchos casos ejecutando múltiples hilos. Así, los servidores y clientes de ZooKeeper son procesos. A menudo, un único servidor físico (ya sea una máquina independiente o un sistema operativo en un entorno virtual) ejecuta un único proceso de aplicación, aunque el proceso pueda ejecutar múltiples hilos para explotar la capacidad multinúcleo de los procesadores modernos.

Los procesos de un sistema distribuido tienen dos amplias opciones para comunicarse: pueden intercambiar mensajes directamente a través de una red, o leer y escribir en algún almacenamiento compartido. ZooKeeper utiliza el modelo de almacenamiento compartido para permitir que las aplicaciones implementen primitivas de coordinación y sincronización. Pero el propio almacenamiento compartido requiere la comunicación en red entre los procesos y el almacenamiento. Es importante destacar el papel de la comunicación de red porque es una fuente importante de complicaciones en el diseño de un sistema distribuido.

En los sistemas reales, es importante tener cuidado con los siguientes problemas:

Retrasos en los mensajes

Los mensajes pueden retrasarse arbitrariamente; por ejemplo, debido a la congestión de la red. Estos retrasos arbitrarios pueden introducir situaciones indeseables. Por ejemplo, el proceso P puede enviar un mensaje antes de que otro proceso Q envíe el suyo, según un reloj de referencia, pero el mensaje de Qpodría entregarse primero.

Velocidad del procesador

La programación y la sobrecarga del sistema operativo pueden inducir retrasos arbitrarios en el procesamiento de mensajes.Cuando un proceso envía un mensaje a otro, la latencia global de este mensaje es aproximadamente la suma del tiempo de procesamiento en el emisor, el tiempo de transmisión y el tiempo de procesamiento en el receptor. Si el proceso emisor o receptor requiere tiempo de programación para su procesamiento, entonces la latencia del mensaje es mayor.

Deriva del reloj

No es infrecuente encontrar sistemas que utilizan alguna noción del tiempo, por ejemplo para determinar el momento en que se producen los acontecimientos en el sistema. Los relojes de los procesadores no son fiables y pueden alejarse arbitrariamente unos de otros. En consecuencia, confiar en los relojes de los procesadores puede llevar a decisiones incorrectas.

Una consecuencia importante de estos problemas es que, en la práctica, es muy difícil saber si un proceso se ha colgado o si alguno de estos factores está introduciendo algún retraso arbitrario. No recibir un mensaje de un proceso podría significar que se ha colgado, que la red está retrasando su último mensaje arbitrariamente, que hay algo que está retrasando el proceso o que el reloj del proceso se está desviando. Un sistema en el que no puede hacerse tal distinción se dice que es asíncrono.

Los centros de datos se construyen generalmente utilizando grandes lotes de hardware mayoritariamente uniforme. Pero incluso en los centros de datos, hemos observado el impacto de todas estas cuestiones en las aplicaciones, debido al uso de varias generaciones de hardware en una misma aplicación, y a las sutiles pero significativas diferencias de rendimiento incluso dentro del mismo lote de hardware. Todas estas cosas complican la vida de un diseñador de sistemas distribuidos.

ZooKeeper se ha diseñado precisamente para simplificar el tratamiento de estos problemas. ZooKeeper no hace desaparecer los problemas ni los hace completamente transparentes para las aplicaciones, pero los hace más manejables. ZooKeeper implementa soluciones a importantes problemas de computación distribuida y empaqueta estas implementaciones de forma intuitiva para los desarrolladores... al menos, ésta ha sido nuestra esperanza desde el principio.

Ejemplo: Solicitud de Maestro-Trabajador

Hemos hablado de los sistemas distribuidos en abstracto, pero ha llegado el momento de concretarlo un poco más. Consideremos una arquitectura común que se ha utilizado mucho en el diseño de sistemas distribuidos: una arquitectura maestro-trabajador(Figura 1-1). Un ejemplo importante de sistema que sigue esta arquitectura es HBase, un clon de Bigtable de Google. A un nivel muy alto, el servidor maestro (HMaster) es responsable de llevar la cuenta de los servidores de regiones disponibles (HRegionServer) y de asignar las regiones a los servidores. Como no lo cubrimos aquí, te animamos a que consultes la documentación de HBase para obtener más detalles sobre cómo utiliza ZooKeeper. Nuestra discusión se centra, en cambio, en una arquitectura maestro-trabajador genérica.

Master–worker example.
Figura 1-1. Ejemplo de maestro-trabajador

En general, en una arquitectura de este tipo, un proceso maestro se encarga de llevar la cuenta de los trabajadores y tareas disponibles, y de asignar tareas a los trabajadores. Para ZooKeeper, este estilo de arquitectura es representativo porque ilustra una serie de tareas populares, como la elección de un maestro, el seguimiento de los trabajadores disponibles y el mantenimiento de los metadatos de la aplicación .

Para implantar un sistema de maestros-trabajadores, debemos resolver tres problemas clave:

El maestro se bloquea

Si el maestro falla y deja de estar disponible, el sistema no puede asignar nuevas tareas ni reasignar tareas de trabajadores que también hayan fallado.

Accidentes laborales

Si un trabajador se bloquea, las tareas que tiene asignadas no se completarán.

Fallos de comunicación

Si el maestro y un trabajador no pueden intercambiar mensajes, el trabajador podría no enterarse de las nuevas tareas que se le asignen.

Para hacer frente a estos problemas, el sistema debe ser capaz de elegir de forma fiable un nuevo maestro si el anterior es defectuoso, determinar qué trabajadores están disponibles y decidir cuándo el estado de un trabajador es obsoleto con respecto al resto del sistema. Veremos cada tarea brevemente en las secciones siguientes.

Fallos maestros

Para enmascarar las caídas del maestro, necesitamos tener un maestro de reserva. Cuando el maestro principal falla, el maestro de reserva asume el papel de maestro principal. Sin embargo, el relevo no es tan sencillo como empezar a procesar las peticiones que llegan al maestro. El nuevo maestro primario debe ser capaz de recuperar el estado del sistema en el momento en que se averió el antiguo maestro primario. Para poder recuperar el estado del maestro, no podemos confiar en sacarlo del maestro defectuoso porque se haya estrellado; necesitamos tenerlo en otro lugar. Este otro lugar es ZooKeeper.

Recuperar el estado no es la única cuestión importante. Supongamos que el maestro primario está activo, pero el maestro de respaldo sospecha que el maestro primario se ha bloqueado. Esta falsa sospecha puede deberse, por ejemplo, a que el maestro principal esté muy cargado y sus mensajes se retrasen arbitrariamente (consulta la discusión en "Creación de sistemas distribuidos con ZooKeeper"). El maestro de reserva ejecutará todos los procedimientos necesarios para asumir el papel de maestro principal y puede que, con el tiempo, empiece a ejecutar el papel de maestro principal, convirtiéndose en un segundo maestro principal. Peor aún, si algunos trabajadores no pueden comunicarse con el maestro principal, por ejemplo debido a una partición de la red, pueden acabar siguiendo al segundo maestro principal. Este escenario conduce a un problema comúnmente llamado "cerebro dividido": dos o más partes del sistema avanzan de forma independiente, lo que conduce a un comportamiento incoherente. Al idear una forma de hacer frente a los fallos de los maestros, es fundamental que evitemos los escenarios de cerebro dividido.

Fallos de los trabajadores

Los clientes envían tareas al maestro, que las asigna a los trabajadores disponibles. Los trabajadores reciben las tareas asignadas e informan del estado de la ejecución una vez ejecutadas. A continuación, el maestro informa a los clientes de los resultados de la ejecución.

Si un trabajador se bloquea, deben reasignarse todas las tareas que se le asignaron y no se completaron. El primer requisito aquí es dar al maestro la capacidad de detectar las caídas de los trabajadores. El maestro debe ser capaz de detectar cuando un trabajador se bloquea y debe ser capaz de determinar qué otros trabajadores están disponibles para ejecutar sus tareas. Si un trabajador se bloquea, puede acabar ejecutando parcialmente las tareas o incluso ejecutarlas completamente pero no informar de los resultados. Si el cálculo tiene efectos secundarios, puede ser necesario algún procedimiento de recuperación para limpiar el estado.

Fallos de comunicación

Si un trabajador se desconecta del maestro, por ejemplo debido a una partición de la red, la reasignación de una tarea podría dar lugar a que dos trabajadores ejecutaran la misma tarea. Si ejecutar una tarea más de una vez es aceptable, podemos reasignarla sin verificar si el primer trabajador ha ejecutado la tarea. Si no es aceptable, entonces la aplicación debe ser capaz de acomodar la posibilidad de que varios trabajadores acaben intentando ejecutar la tarea.

Otro problema importante de los fallos de comunicación es el impacto que tienen en las primitivas de sincronización, como los bloqueos. Como los nodos pueden fallar y los sistemas son propensos a las particiones de red, los bloqueos pueden ser problemáticos: si un nodo falla o se particiona, el bloqueo puede impedir que los demás progresen. En consecuencia, ZooKeeper necesita implementar mecanismos para hacer frente a estas situaciones. En primer lugar, permite a los clientes decir que algunos datos del estado ZooKeeper son efímeros. En segundo lugar, el conjunto ZooKeeper requiere que los clientes notifiquen periódicamente que están vivos. Si un cliente no notifica al conjunto a tiempo, se borra todo el estado efímero que pertenezca a ese cliente. Utilizando estos dos mecanismos, podemos evitar que los clientes, individualmente, paralicen la aplicación en presencia de caídas y fallos de comunicación.

Recordemos que argumentamos que en los sistemas en los que no podemos controlar el retraso de los mensajes no es posible saber si un cliente se ha colgado o si simplemente es lento. En consecuencia, cuando sospechamos que un cliente se ha colgado, en realidad tenemos que reaccionar asumiendo que podría ser simplemente lento, y que puede ejecutar algunas otras acciones en el futuro.

Resumen de tareas

De las descripciones anteriores, podemos extraer los siguientes requisitos para nuestra arquitectura maestro-trabajador:

Elección maestra

Es fundamental para el progreso disponer de un maestro que asigne tareas a los trabajadores.

Detección de colisiones

El maestro debe ser capaz de detectar cuando los trabajadores se bloquean o desconectan.

Gestión de pertenencia a grupos

El maestro debe ser capaz de averiguar qué trabajadores están disponibles para ejecutar tareas.

Gestión de metadatos

El maestro y los trabajadores deben poder almacenar asignaciones y estados de ejecución de forma fiable.

Idealmente, cada una de estas tareas se expone a la aplicación en forma de una primitiva, ocultando completamente los detalles de implementación al desarrollador de la aplicación. ZooKeeper proporciona mecanismos clave para implementar dichas primitivas, de modo que los desarrolladores puedan implementar las que mejor se adapten a sus necesidades y centrarse en la lógica de la aplicación. A lo largo de este libro, a menudo nos referimos a las implementaciones de tareas como la elección del maestro o la detección de fallos como primitivas, porque son tareas concretas sobre las que se basan las aplicaciones distribuidas.

¿Por qué es difícil la coordinación distribuida?

Algunas de las complicaciones de escribir aplicaciones distribuidas son evidentes de inmediato. Por ejemplo, cuando nuestra aplicación se inicia, de alguna manera todos los diferentes procesos necesitan encontrar la configuración de la aplicación. Con el tiempo, esta configuración puede cambiar. Podríamos apagar todo, redistribuir los archivos de configuración y reiniciar, pero eso podría provocar largos periodos de inactividad de la aplicación durante la reconfiguración.

Relacionado con el problema de la configuración está el de la pertenencia a grupos. A medida que cambia la carga, queremos poder añadir o eliminar nuevas máquinas y procesos.

Los problemas que acabamos de describir son problemas funcionales para los que puedes diseñar soluciones a medida que implementas tu aplicación distribuida; puedes probar tus soluciones antes de la implementación y estar bastante seguro de que has resuelto los problemas correctamente. Los problemas verdaderamente difíciles con los que te encontrarás mientras desarrollas aplicaciones distribuidas tienen que ver con los fallos , en concreto, los fallos de funcionamiento y de comunicación. Estos fallos pueden surgir en cualquier momento, y puede ser imposible enumerar todos los diferentes casos que hay que tratar.

Faltas bizantinas

Los fallos bizantinos son fallos que pueden hacer que un componente se comporte de alguna manera arbitraria (y a menudo imprevista). Un componente defectuoso de este tipo podría, por ejemplo, corromper el estado de la aplicación o incluso comportarse de forma maliciosa. Los sistemas que se construyen bajo el supuesto de que pueden producirse estos fallos requieren un mayor grado de replicación y el uso de primitivas de seguridad. Aunque reconocemos que ha habido avances significativos en el desarrollo de técnicas para tolerar fallos bizantinos en la literatura académica, no hemos sentido la necesidad de adoptar dichas técnicas en ZooKeeper y, en consecuencia, hemos evitado la complejidad adicional en la base de código.

Los fallos también ponen de manifiesto una gran diferencia entre las aplicaciones que se ejecutan en una sola máquina y las aplicaciones distribuidas: en las aplicaciones distribuidas, pueden producirse fallos parciales. Cuando falla una sola máquina, fallan todos los procesos que se ejecutan en ella. Si hay varios procesos ejecutándose en la máquina y un proceso falla, los demás procesos pueden enterarse del fallo a través del sistema operativo. El sistema operativo también puede proporcionar sólidas garantías de mensajería entre procesos. Todo esto cambia en un entorno distribuido: si falla una máquina o un proceso, otras máquinas seguirán funcionando y puede que tengan que tomar el relevo de los procesos defectuosos. Para manejar los procesos defectuosos, los procesos que siguen en marcha deben ser capaces de detectar el fallo; pueden perderse mensajes e incluso puede haber desviación del reloj.

Idealmente, diseñamos nuestros sistemas bajo el supuesto de que la comunicación es asíncrona: las máquinas que utilizamos pueden experimentar desviaciones de reloj y pueden sufrir fallos de comunicación. Hacemos esta suposición porque estas cosas ocurren. Los relojes se desvían constantemente, todos hemos experimentado problemas ocasionales en la red y, por desgracia, también se producen fallos. ¿Qué límites impone esto a lo que podemos hacer?

Tomemos el caso más sencillo. Supongamos que tenemos una configuración distribuida que ha ido cambiando. Esta configuración es lo más simple que puede ser: un bit. Los procesos de nuestra aplicación pueden ponerse en marcha una vez que todos los procesos en ejecución se hayan puesto de acuerdo sobre el valor del bit de configuración.

Resulta que un famoso resultado en informática distribuida, conocido como FLP por los autores Fischer, Lynch y Patterson, demostró que en un sistema distribuido con comunicación asíncrona y colisiones de procesos, éstos no siempre pueden estar de acuerdo en el único bit de configuración.1 Un resultado similar conocido como CAP, que significa Consistencia, Disponibilidad y Tolerancia a la Partición, dice que al diseñar un sistema distribuido podemos querer las tres propiedades, pero que ningún sistema puede manejar las tres.2 ZooKeeper se ha diseñado pensando sobre todo en la coherencia y la disponibilidad, aunque también proporciona capacidad de sólo lectura en presencia de particiones de red.

De acuerdo, no podemos tener un sistema ideal tolerante a fallos, distribuido, en el mundo real, que se ocupe de forma transparente de todos los problemas que puedan surgir. Pero podemos aspirar a un objetivo algo menos ambicioso. En primer lugar, tenemos que relajar algunas de nuestras suposiciones y/o nuestros objetivos. Por ejemplo, podemos suponer que el reloj está sincronizado dentro de unos límites; podemos optar por ser siempre coherentes y sacrificar la capacidad de tolerar algunas particiones de la red; puede haber momentos en que un proceso esté en marcha, pero deba actuar como si tuviera fallos porque no puede estar seguro del estado del sistema. Aunque se trata de compromisos, son compromisos que nos han permitido construir algunos sistemas distribuidos bastante impresionantes.

ZooKeeper es un éxito, con advertencias

Una vez señalado que la solución perfecta es imposible, podemos repetir que ZooKeeper no va a resolver todos los problemas a los que se enfrenta el desarrollador de aplicaciones distribuidas. Sin embargo, sí que proporciona al desarrollador un buen marco para enfrentarse a estos problemas. A lo largo de los años se ha trabajado mucho en la informática distribuida sobre la base de ZooKeeper. Paxos3 y la sincronía virtual4 han influido especialmente en el diseño de ZooKeeper. Se ocupa de los cambios y las situaciones a medida que surgen de la forma más fluida posible, y ofrece a los desarrolladores un marco para hacer frente a las situaciones que surgen y que no se pueden manejar automáticamente.

ZooKeeper se desarrolló originalmente en Yahoo!, donde abundan las grandes aplicaciones distribuidas. Nos dimos cuenta de que los aspectos de coordinación distribuida de algunas aplicaciones no se trataban adecuadamente, por lo que los sistemas se implementaban con puntos únicos de fallo o eran frágiles. Por otra parte, otros desarrolladores dedicaban tanto tiempo a la coordinación distribuida que no tenían recursos suficientes para centrarse en la funcionalidad de la aplicación. También nos dimos cuenta de que todas estas aplicaciones tenían en común algunos requisitos básicos de coordinación, así que nos propusimos idear una solución general que contuviera algunos elementos clave que pudiéramos implementar una vez y utilizar en muchas aplicaciones diferentes. ZooKeeper ha demostrado ser mucho más general y popular de lo que habíamos creído posible.

A lo largo de los años, hemos comprobado que la gente puede implementar fácilmente un clúster ZooKeeper y desarrollar aplicaciones para él -tan fácilmente, de hecho, que algunos desarrolladores lo utilizan sin entender completamente algunos de los casos que requieren que el desarrollador tome decisiones que ZooKeeper no puede tomar por sí mismo-. Uno de los propósitos de escribir este libro es asegurarnos de que los desarrolladores entienden lo que tienen que hacer para utilizar ZooKeeper con eficacia y por qué tienen que hacerlo así.

1 Michael J. Fischer, Nancy A. Lynch y Michael S. Paterson, "Impossibility of Distributed Consensus with One Faulty Process", Actas del 2º Simposio ACM SIGACT-SIGMOD sobre Principios de Sistemas de Bases de Datos (1983), doi:10.1145/588058.588060.

2 Seth Gilbert y Nancy Lynch, "Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services", ACM SIGACT News 33:2 (2002), doi:10.1145/564585.564601.

3 Leslie Lamport, "El Parlamento a Tiempo Parcial", ACM Transactions on Computer Systems 16:2 (1998): 133-169.

4 K. Birman y T. Joseph, "Exploiting Virtual Synchrony in Distributed Systems", Actas del 11º Simposio ACM sobre Principios de Sistemas Operativos (1987): 123-138.

Get ZooKeeper now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.