Capítulo 1. Una rápida introducción a Kafka
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
La cantidad de datos en el mundo está creciendo exponencialmente y, según el Foro Económico Mundial, el número de bytes que se almacenan en el mundo ya supera con creces el número de estrellas del universo observable.
Cuando piensas en estos datos, puedes pensar en montones de bytes en almacenes de datos, en bases de datos relacionales o en sistemas de archivos distribuidos. Estos sistemas nos han enseñado a pensar en los datos en estado de reposo. En otras palabras, los datos están sentados en algún lugar, descansando, y cuando necesitas procesarlos, ejecutas alguna consulta o trabajo contra el montón de bytes.
Esta visión del mundo es la forma más tradicional de pensar sobre los datos. Sin embargo, aunque es cierto que los datos pueden acumularse en algunos lugares, lo más frecuente es que estén en movimiento. Verás, muchos sistemas generan flujos continuos de datos, incluidos sensores IoT, sensores médicos, sistemas financieros, software de análisis de usuarios y clientes, registros de aplicaciones y servidores, y mucho más. Incluso los datos que acaban encontrando un buen lugar para descansar, probablemente viajan por la red en algún momento antes de encontrar su hogar definitivo.
Si queremos procesar los datos en tiempo real, mientras se mueven, no podemos limitarnos a esperar a que se amontonen en algún lugar y luego ejecutar una consulta o un trabajo en el intervalo que elijamos. Este enfoque puede servir para algunos casos de uso empresarial, pero muchos casos de uso importantes requieren que procesemos, enriquezcamos, transformemos y respondamos a los datos de forma incremental, a medida que están disponibles. Por tanto, necesitamos algo que tenga una visión muy diferente de los datos: una tecnología que nos dé acceso a los datos en su estado de flujo, y que nos permita trabajar con estos flujos de datos continuos y sin límites de forma rápida y eficaz. Aquí es donde entra Apache Kafka.
Apache Kafka (o, simplemente, Kafka) es una plataforma de streaming para ingerir, almacenar, acceder y procesar flujos de datos. Aunque toda la plataforma es muy interesante, este libro se centra en lo que considero la parte más atractiva de Kafka la capa de procesamiento deflujos. Sin embargo, para entender Kafka Streams y ksqlDB (ambos operan en esta capa, y este último también opera en la capa de ingestión de flujos), es necesario tener un conocimiento práctico de cómo funciona Kafka, comoplataforma.
Por lo tanto, este capítulo te introducirá en algunos conceptos y terminología importantes que necesitarás para el resto del libro. Si ya tienes un conocimiento práctico de Kafka, puedes saltarte este capítulo. De lo contrario, sigue leyendo.
Algunas de las preguntas a las que responderemos en este capítulo son:
-
¿Cómo simplifica Kafka la comunicación entre sistemas?
-
¿Cuáles son los principales componentes de la arquitectura de Kafka?
-
¿Qué abstracción de almacenamiento modela mejor los flujos?
-
¿Cómo almacena Kafka los datos de forma duradera y tolerante a fallos?
-
¿Cómo se consigue una alta disponibilidad y tolerancia a fallos en lascapas de procesamiento de datos?
Concluiremos este capítulo con un tutorial que muestra cómo instalar y ejecutar Kafka. Pero antes, empecemos por ver el modelo de comunicación de Kafka.
Modelo de comunicación
Quizás el patrón de comunicación más común entre sistemas sea el modelo síncrono, cliente-servidor. Cuando hablamos de sistemas en este contexto, nos referimos a aplicaciones, microservicios, bases de datos y cualquier otra cosa que lea y escriba datos a través de una red. El modelo cliente-servidor es sencillo en un principio, e implica la comunicación directa entre sistemas, como se muestra en la Figura 1-1.
Por ejemplo, puedes tener una aplicación que consulte de forma sincrónica una base de datos para obtener algunos datos, o una colección de microservicios que hablen entre sí directamente.
Sin embargo, cuando más sistemas necesitan comunicarse, la comunicación punto a punto se vuelve difícil de escalar. El resultado es una compleja red de vías de comunicación que puede ser difícil de razonar y mantener. La Figura 1-2 muestra lo confuso que puede llegar a ser, incluso con un número relativamente pequeño de sistemas.
Algunos de los inconvenientes del modelo cliente-servidor son:
-
Los sistemas se acoplan estrechamente porque su comunicación depende del conocimiento de los demás. Esto hace que mantener y actualizar estos sistemas sea más difícil de lo necesario.
-
La comunicación síncrona deja poco margen de error, ya que no hay garantías de entrega si uno de los sistemas se desconecta.
-
Los sistemas pueden utilizar distintos protocolos de comunicación, estrategias de escalado para hacer frente al aumento de la carga, estrategias de gestión de fallos, etc. Como resultado, puedes acabar teniendo múltiples especies de sistemas que mantener(especiación del software), lo que perjudica la mantenibilidad y desafía la sabiduría común de que deberíamos tratar a las aplicaciones como ganado en lugar de como mascotas.
-
Los sistemas receptores pueden verse desbordados fácilmente, ya que no controlan el ritmo al que llegan las nuevas peticiones o datos. Sin un búfer de peticiones, funcionan a capricho de las aplicaciones que las realizan.
-
No existe una noción sólida de lo que se comunica entre estos sistemas. La nomenclatura del modelo cliente-servidor ha puesto demasiado énfasis en las peticiones y las respuestas, y no lo suficiente en los propios datos. Los datos deben ser el punto central de los sistemas basados en datos.
-
La comunicación no es reproducible. Esto dificulta la reconstrucción del estado de un sistema.
Kafka simplifica la comunicación entre sistemas actuando como un eje de comunicación centralizado (a menudo comparado con un sistema nervioso central), en el que los sistemas pueden enviar y recibir datos sin conocerse entre sí. El patrón de comunicación que implementa se denomina patrón publicar-suscribir (o simplemente, pub/sub), y el resultado es un modelo de comunicación drásticamente más sencillo, como se muestra en la Figura 1-3.
Si añadimos más detalles al diagrama anterior, podemos empezar a identificar los principales componentes que intervienen en el modelo de comunicación de Kafka, como se muestra en la Figura 1-4.
En lugar de hacer que varios sistemas se comuniquen directamente entre sí, los productores simplemente publican sus datos en uno o varios temas, sin preocuparse de quién viene a leerlos.
Los temas son flujos (o canales) nombrados de datos relacionados que se almacenan en un clúster Kafka. Cumplen una función similar a la de las tablas de una base de datos (es decir, agrupar datos relacionados). Sin embargo, no imponen un esquema concreto, sino que almacenan los bytes de datos en bruto, lo que hace que sea muy flexible trabajar con ellos.1
Los consumidores son procesos que leen (o se suscriben) a datos en uno o varios temas. No se comunican directamente con los productores, sino que escuchan los datos de cualquier flujo que les interese.
Los consumidores pueden trabajar juntos como un grupo (llamado grupo de consumidores) para distribuir el trabajo de entre varios procesos.
El modelo de comunicación de Kafka, que pone más énfasis en los flujos de datos que pueden ser leídos y escritos fácilmente por múltiples procesos, tiene varias ventajas, entre ellas:
-
Los sistemas se desacoplan y son más fáciles de mantener porque pueden producir y consumir datos sin conocimiento de otros sistemas.
-
La comunicación asíncrona ofrece mayores garantías de entrega. Si un consumidor se cae, simplemente lo retomará desde donde lo dejó cuando vuelva a estar en línea (o, cuando se ejecute con varios consumidores en un grupo de consumidores, el trabajo se redistribuirá a uno de los otros miembros).
-
Los sistemas pueden estandarizar el protocolo de comunicación (se utiliza un protocolo TCP binario de alto rendimiento cuando se habla con los clusters de Kafka), así como las estrategias de escalado y los mecanismos de tolerancia a fallos (que son impulsados por los grupos de consumidores). Esto nos permite escribir software que es ampliamente coherente, y que cabe en nuestra cabeza.
-
Los consumidores pueden procesar los datos a un ritmo que puedan manejar. Los datos no procesados se almacenan en Kafka, de forma duradera y tolerante a fallos, hasta que el consumidor esté preparado para procesarlos. En otras palabras, si el flujo del que está leyendo tu consumidor se convierte de repente en una manguera, el clúster Kafka actuará como un búfer, evitando que tus consumidores se vean desbordados.
-
Una noción más sólida de los datos que se comunican, en la forma de eventos. Un evento es un dato con una estructura determinada, de la que hablaremos en "Eventos". Lo principal, por ahora, es que podemos centrarnos en los datos que fluyen a través de nuestros flujos, en lugar de dedicar tanto tiempo a desentrañar la capa de comunicación como haríamos en el modelo cliente-servidor.
-
Los sistemas pueden reconstruir su estado en cualquier momento reproduciendo los acontecimientos de un tema.
Una diferencia importante entre el modelo pub/sub y el modelo cliente-servidor es que la comunicación no es bidireccional en el modelo pub/sub de Kafka. En otras palabras, los flujos fluyen en un solo sentido. Si un sistema produce algunos datos en un tema de Kafka, y depende de otro sistema para hacer algo con los datos (es decir, enriquecerlos o transformarlos), los datos enriquecidos tendrán que escribirse en otro tema y ser consumidos posteriormente por el proceso original. Esto es sencillo de coordinar, pero cambia la forma en que pensamos sobre la comunicación.
Siempre que recuerdes que los canales de comunicación (temas) son de naturaleza similar a los flujos (es decir, fluyen unidireccionalmente, y pueden tener múltiples fuentes y múltiples consumidores descendentes), es fácil diseñar sistemas que simplemente escuchen cualquier flujo de bytes que les interese, y produzcan datos en temas (llamados flujos) siempre que quieran compartir datos con uno o más sistemas. Trabajaremos mucho con temas Kafka en los siguientes capítulos (cada aplicación Kafka Streams y ksqlDB que construyamos leerá, y normalmente escribirá en, uno o más temas Kafka), así que cuando llegues al final de este libro, esto será algo natural para ti.
Ahora que hemos visto cómo el modelo de comunicación de Kafka simplifica la forma en que los sistemas se comunican entre sí, y que los flujos con nombre, llamados temas, actúan como medio de comunicación entre los sistemas, vamos a comprender mejor cómo entran en juego los flujos en la capa de almacenamiento de Kafka.
¿Cómo se almacenan los flujos?
Cuando un equipo de ingenieros de LinkedIn2 vieron el potencial de una plataforma de datos basada en flujos, tuvieron que responder a una pregunta importante: ¿cómo deben modelarse los flujos de datos continuos e ilimitados en la capa de almacenamiento?
En última instancia, la abstracción de almacenamiento que identificaron ya estaba presente en muchos tipos de sistemas de datos, incluidas las bases de datos tradicionales, los almacenes de valores clave, los sistemas de control de versiones y otros. La abstracción es el sencillo pero potente registro de confirmaciones (o, simplemente, registro).
Nota
Cuando hablamos de registros en este libro, no nos referimos a los registros de aplicaciones, que emiten información sobre un proceso en ejecución (por ejemplo, los registros del servidor HTTP). En su lugar, nos referimos a una estructura de datos específica que se describe en los párrafos siguientes.
Los registros son estructuras de datos de sólo apéndice que capturan una secuencia ordenada de eventos. Examinemos los atributos en cursiva con más detalle, y construyamos alguna intuición sobrelos registros, creando un registro sencillo desde la línea de comandos. Por ejemplo, creemos un registro llamado user_purchases
, y rellenémoslo con algunos datos ficticios utilizando el siguientecomando:
# create the logfile
touch users.log# generate four dummy records in our log
echo
"timestamp=1597373669,user_id=1,purchases=1"
>> users.logecho
"timestamp=1597373669,user_id=2,purchases=1"
>> users.logecho
"timestamp=1597373669,user_id=3,purchases=1"
>> users.logecho
"timestamp=1597373669,user_id=4,purchases=1"
>> users.log
Ahora, si miramos el registro que hemos creado, contiene cuatro usuarios que han realizado una única compra:
# print the contents of the log
cat users.log# output
timestamp
=
1597373669,user_id=
1,purchases=
1timestamp
=
1597373669,user_id=
2,purchases=
1timestamp
=
1597373669,user_id=
3,purchases=
1timestamp
=
1597373669,user_id=
4,purchases=
1
El primer atributo de los registros es que se escriben sólo como apéndice. Esto significa que si user_id=1
llega y hace una segunda compra, no actualizamos el primer registro, ya que cada registro es inmutable en un registro. En su lugar, simplemente añadimos el nuevo registro al final:
# append a new record to the log
echo
"timestamp=1597374265,user_id=1,purchases=2"
>>
users.log
# print the contents of the log
cat
users.log
# output
timestamp
=
1597373669,user_id
=
1,purchases
=
1
timestamp
=
1597373669,user_id
=
2,purchases
=
1
timestamp
=
1597373669,user_id
=
3,purchases
=
1
timestamp
=
1597373669,user_id
=
4,purchases
=
1
timestamp
=
1597374265,user_id
=
1,purchases
=
2
Una vez que un registro se escribe en el registro, se considera inmutable. Por lo tanto, si necesitamos realizar una actualización (por ejemplo, para cambiar el recuento de compras de un usuario), el registro original queda intacto.
Para modelar la actualización, simplemente añadimos el nuevo valor al final del registro. El registro contendrá tanto el registro antiguo como el nuevo, ambos inmutables.
Cualquier sistema que quiera examinar el recuento de compras de cada usuario puede simplemente leer cada registro del registro, en orden, y el último registro que verá de user_id=1
contendrá el importe de compra actualizado. Esto nos lleva al segundo atributo de los registros: están ordenados.
Resulta que el registro anterior está ordenado por fecha y hora (véase la primera columna), pero eso no es lo que entendemos por ordenado. De hecho, Kafka almacena una marca de tiempo para cada registro del registro, pero los registros no tienen por qué estar en orden de marca de tiempo. Cuando decimos que un registro está ordenado, lo que queremos decir es que la posición de un registro en el registro es fija, y nunca cambia. Si volvemos a imprimir el registro, esta vez con números de línea, podrás ver la posición en la primera columna:
# print the contents of the log, with line numbers
cat -n users.log# output
1timestamp
=
1597373669,user_id=
1,purchases=
1 2timestamp
=
1597373669,user_id=
2,purchases=
1 3timestamp
=
1597373669,user_id=
3,purchases=
1 4timestamp
=
1597373669,user_id=
4,purchases=
1 5timestamp
=
1597374265,user_id=
1,purchases=
2
Ahora, imagina un escenario en el que no se pudiera garantizar el orden. Múltiples procesos podrían leer las actualizaciones de user_id=1
en un orden diferente, creando desacuerdos sobre el recuento real de compras de este usuario. Garantizando que los registros estén ordenados, los datos pueden ser procesados de forma determinista3 por múltiples procesos.4
Además, mientras que la posición de cada entrada del registro en el ejemplo anterior utiliza números de línea, Kafka se refiere a la posición de cada entrada en su registro distribuido como un desplazamiento. Los desplazamientos empiezan en 0 y permiten un comportamiento importante: permiten que varios grupos de consumidores lean cada uno del mismo registro y mantengan sus propias posiciones en el registro/flujo del que están leyendo. Esto se muestra en la Figura 1-5.
Ahora que hemos adquirido cierta intuición sobre la capa de almacenamiento basada en registros de Kafka creando nuestro propio registro desde la línea de comandos, vamos a relacionar estas ideas con las construcciones de nivel superior que identificamos en el modelo de comunicación de Kafka. Empezaremos continuando nuestra discusión sobre los temas, y aprendiendo sobre algo llamado particiones.
Temas y Particiones
En nuestra discusión sobre el modelo de comunicación de Kafka, aprendimos que Kafka tiene el concepto de flujos con nombre llamados temas. Además, los temas de Kafka son extremadamente flexibles con lo que almacenas en ellos. Por ejemplo, puedes tener temas homogéneos que contengan sólo un tipo de datos, o temas heterogéneos que contengan múltiples tipos de datos.5 En la Figura 1-6 se muestra una representación de estas diferentes estrategias.
También hemos aprendido que los registros de confirmación de sólo apéndice se utilizan para modelar flujos en la capa de almacenamiento de Kafka. Entonces, ¿significa esto que cada tema se correlaciona con un archivo de registro? No exactamente. Verás, Kafka es un registro distribuido, y es difícil distribuir sólo uno de algo. Así que si queremos conseguir cierto nivel de paralelismo con la forma en que distribuimos y procesamos los logs, necesitamos crear muchos de ellos. Por eso los temas de Kafka se dividen en unidades más pequeñas llamadas particiones.
Las particiones son registros individuales (es decir, las estructuras de datos de las que hablamos en la sección anterior) desde donde se producen y consumen los datos. Dado que la abstracción del registro de confirmaciones se implementa a nivel de partición, éste es el nivel en el que se garantiza el orden, teniendo cada partición su propio conjunto de desplazamientos. El orden global no está soportado a nivel de tema, por lo que los productores a menudo dirigen registros relacionados a la misma partición.6
Lo ideal es que los datos se distribuyan de forma relativamente uniforme por todas las particiones de un tema. Pero también podrías acabar con particiones de diferentes tamaños. La Figura 1-7 muestra un ejemplo de un tema con tres particiones diferentes.
El número de particiones para un tema determinado es configurable, y tener más particiones en un tema generalmente se traduce en más paralelismo y rendimiento, aunque hay algunas desventajas de tener demasiadas particiones.7 Hablaremos más de esto a lo largo del libro, pero lo importante es que sólo un consumidor de por grupo de consumidores puede consumir de una partición (sin embargo, miembros individuales de diferentes grupos de consumidores pueden consumir de la misma partición, como se muestra en la Figura 1-5).
Por tanto, si quieres repartir la carga de procesamiento entre N consumidores de un mismo grupo de consumidores, necesitas N particiones. Si tienes menos miembros en un grupo de consumidores que particiones en el tema fuente (es decir, el tema del que se está leyendo), no pasa nada: cada consumidor puede procesar varias particiones. Si tienes más miembros en un grupo de consumidores que particiones en el tema de origen, algunos consumidores estarán inactivos.
Teniendo esto en cuenta, podemos mejorar nuestra definición de lo que es un tema. Un tema es un flujo con nombre, compuesto de múltiples particiones. Y cada partición se modela como un registro de confirmaciones que almacena datos en una secuencia totalmente ordenada y sólo anexable. Entonces, ¿qué se almacena exactamente en la partición de un tema? Exploraremos esto en la siguiente sección.
Eventos
En este libro, pasamos mucho tiempo hablando del procesamiento de datos en temas. Sin embargo, aún no hemos desarrollado una comprensión completa de qué tipo de datos se almacenan en un tema de Kafka (y, más concretamente, en las particiones de un tema).
Gran parte de la literatura existente sobre Kafka, incluida la documentación oficial, utiliza diversos términos para describir los datos de un tema, como mensajes, registros y eventos. Estos términos se utilizan a menudo indistintamente, pero el que hemos favorecido en este libro (aunque seguimos utilizando los otros términos ocasionalmente) es evento. Un evento es un par clave-valor con marca de tiempo que registra algo que ha sucedido. La anatomía básica de cada evento capturado en una partición temática se muestra en la Figura 1-8.
Las cabeceras a nivel de aplicación contienen metadatos opcionales sobre un evento. En este libro no trabajamos con ellas muy a menudo.
Las claves también son opcionales, pero desempeñan un papel importante en cómo se distribuyen los datos entre las particiones. Lo veremos en los próximos capítulos, pero en general se utilizan para identificar registros relacionados.
Cada evento está asociado a una marca de tiempo. Aprenderemos más sobre las marcas de tiempo en el Capítulo 5.
El valor contiene el contenido real del mensaje, codificado como una matriz de bytes. Corresponde a los clientes deserializar los bytes en bruto en una estructura más significativa (por ejemplo, un objeto JSON o un registro Avro). Hablaremos de la deserialización de matrices de bytes en detalle en "Serialización/Deserialización".
Ahora que comprendemos bien qué datos se almacenan en un tema, vamos a profundizar en el modelo de implementación en clúster de Kafka. Esto nos proporcionará más información sobre cómo se almacenan físicamente los datos en Kafka.
Clúster Kafka y Brokers
Tener un punto de comunicación centralizado significa que la fiabilidad y la tolerancia a fallos son extremadamente importantes. También significa que la red troncal de comunicación tiene que ser escalable, es decir, capaz de manejar cantidades crecientes de carga. Por eso Kafka funciona como un clúster, y varias máquinas, llamadas brokers, participan en el almacenamiento y recuperación de datos.
Los clusters Kafka pueden ser bastante grandes, e incluso abarcar múltiples centros de datos y regiones geográficas. Sin embargo, en este libro, normalmente trabajaremos con un clúster Kafka de un solo nodo, ya que es todo lo que necesitamos para empezar a trabajar con Kafka Streams y ksqlDB. En producción, probablemente querrás al menos tres brokers, y querrás configurar la replicación de tu tema Kafka para que tus datos se repliquen en varios brokers (lo veremos más adelante en el tutorial de este capítulo). Esto nos permite conseguir una alta disponibilidad y evitar la pérdida de datos en caso de que una máquina se caiga.
Ahora bien, cuando hablamos de que los datos se almacenan y replican entre brokers, en realidad estamos hablando de particiones individuales de un tema. Por ejemplo, un tema puede tener tres particiones repartidas entre tres brokers, como se muestra en la Figura 1-9.
Como puedes ver, esto permite que los temas sean bastante grandes, ya que pueden crecer más allá de la capacidad de una sola máquina. Para conseguir tolerancia a fallos y alta disponibilidad, puedes establecer un factor de replicación al configurar el tema. Por ejemplo, un factor de replicación de 2 permitirá que la partición se almacene en dos brokers diferentes. Esto se muestra en la Figura 1-10.
Siempre que una partición se replique en varios corredores, se designará a uno de ellos como líder, lo que significa que procesará todas las solicitudes de lectura/escritura de productores/consumidores para la partición dada. Los demás corredores que contienen las particiones replicadas se denominan seguidores, y se limitan a copiar los datos del líder. Si el líder falla, uno de los seguidores será promovido como nuevo líder.
Además, a medida que la carga de tu clúster aumente con el tiempo, puedes ampliar tu clúster añadiendo aún más brokers, y activando una reasignación de particiones. Esto te permitirá migrar los datos de las máquinas antiguas a una máquina nueva y fresca.
Por último, los intermediarios también desempeñan un papel importante en el mantenimiento de la afiliación de las agrupaciones de consumidores. Exploraremos esto en la siguiente sección.
Grupos de consumidores
Kafka está optimizado para un alto rendimiento y una baja latencia. Para aprovechar esto en el lado del consumidor, necesitamos poder paralelizar el trabajo en varios procesos. Esto se consigue con los grupos de consumidores.
Los grupos de consumidores están formados por varios consumidores que cooperan, y la composición de estos grupos puede cambiar con el tiempo. Por ejemplo, pueden conectarse nuevos consumidores para escalar la carga de procesamiento, y los consumidores también pueden desconectarse, ya sea por mantenimiento planificado o debido a un fallo inesperado. Por tanto, Kafka necesita alguna forma de mantener la pertenencia a cada grupo, y redistribuir el trabajo cuando sea necesario.
Para facilitarlo, a cada grupo de consumidores se le asigna a un corredor especial llamado coordinador de grupo, que se encarga de recibir los latidos de los consumidores y de activar un reequilibrio del trabajo cada vez que un consumidor se marca como muerto. En la Figura 1-11 se muestra una representación de los latidos de los consumidores al coordinador de grupo.
Cada miembro activo del grupo de consumidores puede recibir una asignación de partición. Por ejemplo, la distribución del trabajo entre tres consumidores sanos puede parecerse al diagrama de la Figura 1-12.
Sin embargo, si una instancia de consumidor se vuelve insana y no puede volver a latir en el clúster, el trabajo se reasignará automáticamente a los consumidores sanos. Por ejemplo, en la Figura 1-13, al consumidor medio se le ha asignado la partición que antes gestionaba el consumidor no sano.
Como puedes ver, los grupos de consumidores son extremadamente importantes para conseguir una alta disponibilidad y tolerancia a fallos en la capa de procesamiento de datos. Con esto, comencemos ahora nuestro tutorial aprendiendo a instalar Kafka.
Instalar Kafka
Existen instrucciones detalladas para instalar Kafka manualmente en la documentación oficial. Sin embargo, para simplificar al máximo las cosas, la mayoríade los tutoriales de este libro utilizan Docker, que nospermite implementar Kafka y nuestras aplicaciones de procesamiento de flujos dentro de unentorno en contenedores.
Por lo tanto, instalaremos Kafka utilizando Docker Compose, y utilizaremos imágenes Docker publicadas por Confluent.8 El primer paso es descargar e instalar Docker desde la página de instalación de Docker.
A continuación, guarda la siguiente configuración en un archivo llamado docker-compose.yml:
---
version
:
'
2
'
services
:
zookeeper
:
image
:
confluentinc/cp-zookeeper:6.0.0
hostname
:
zookeeper
container_name
:
zookeeper
ports
:
-
"
2181:2181
"
environment
:
ZOOKEEPER_CLIENT_PORT
:
2181
ZOOKEEPER_TICK_TIME
:
2000
kafka
:
image
:
confluentinc/cp-enterprise-kafka:6.0.0
hostname
:
kafka
container_name
:
kafka
depends_on
:
-
zookeeper
ports
:
-
"
29092:29092
"
environment
:
KAFKA_BROKER_ID
:
1
KAFKA_ZOOKEEPER_CONNECT
:
'
zookeeper:2181
'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
:
|
PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS
:
|
PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
:
1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR
:
1
El primer contenedor, al que hemos llamado
zookeeper
, contendrá la instalación de ZooKeeper. No hemos hablado de ZooKeeper en esta introducción porque, en el momento de escribir esto, se está eliminando activamente de Kafka. Sin embargo, es un servicio centralizado para almacenar metadatos como la configuración de temas. Pronto dejará de estar incluido en Kafka, pero lo incluimos aquí porque este libro se publicó antes de que ZooKeeper se eliminara por completo.El segundo contenedor, llamado
kafka
, contendrá la instalación de Kafka. Aquí es donde se ejecutará nuestro broker (que comprende nuestro clúster de un solo nodo) y donde ejecutaremos algunos de los scripts de consola de Kafka para interactuar con el clúster.
Por último, ejecuta el siguiente comando para iniciar un clúster local de Kafka:
docker-compose up
Con nuestro clúster Kafka en funcionamiento, ya estamos listos para continuar con nuestro tutorial.
Hola, Kafka
En este sencillo tutorial, demostraremos cómo crear un tema Kafka, escribir datos en un tema utilizando un productor y, por último, leer datos de un tema utilizando un consumidor. Lo primero que tenemos que hacer es iniciar sesión en el contenedor que tiene instalado Kafka. Podemos hacerlo ejecutando el siguiente comando:
docker-compose exec kafka bash
Ahora, vamos a crear un tema, llamado users
. Utilizaremos uno de los scripts de consola (kafka-topics
) que se incluyen con Kafka. El siguiente comando muestra cómo hacerlo:
kafka-topics
\
--bootstrap-server
localhost:9092
\
--create
\
--topic
users
\
--partitions
4
\
--replication-factor
1
# output
Created
topic
users.
kafka-topics
es un script de consola que se incluye con Kafka.Un servidor de arranque es el par host/IP para uno o más brokers.
Hay muchas banderas para interactuar con los temas de Kafka, como
--list
,--describe
y--delete
. Aquí utilizamos la bandera--create
, ya que estamos creando un nuevo tema.El nombre del tema es
users
.Divide nuestro tema en cuatro partes.
Como estamos ejecutando un clúster de un solo nodo, estableceremos el factor de replicación en 1. En producción, querrás establecerlo en un valor más alto (como
3
) para garantizaruna alta disponibilidad.
Nota
Los scripts de consola que utilizamos en esta sección se incluyen en la distribución del código fuente de Kafka. En una instalación de Kafka vainilla, estos scripts incluyen la extensión de archivo .sh (por ejemplo, kafka-topics.sh, kafka-console-producer.sh, etc.). Sin embargo, la extensión de archivo se omite en Confluent Platform (por eso ejecutamos kafka-topics en lugar de kafka-topics.sh en el fragmento de código anterior).
Una vez creado el tema, puedes imprimir una descripción del mismo, incluyendo su configuración, utilizando el siguiente comando:
kafka-topics
\
--bootstrap-server
localhost:9092
\
--describe
\
--topic
users
# output
Topic:
users
PartitionCount:
4
ReplicationFactor:
1
Configs:
Topic:
users
Partition:
0
Leader:
1
Replicas:
1
Isr:
1
Topic:
users
Partition:
1
Leader:
1
Replicas:
1
Isr:
1
Topic:
users
Partition:
2
Leader:
1
Replicas:
1
Isr:
1
Topic:
users
Partition:
3
Leader:
1
Replicas:
1
Isr:
1
Ahora, vamos a producir algunos datos utilizando el script incorporado kafka-console-producer
:
kafka-console-producer
\
--bootstrap-server
localhost:9092
\
--property
key.separator
=
,
\
--property
parse.key
=
true
\
--topic
users
El script
kafka-console-producer
, que se incluye con Kafka, puede utilizarse para producir datos a un tema. Sin embargo, una vez que empecemos a trabajar con Kafka Streams y ksqlDB, los procesos productores estarán integrados en la biblioteca Java subyacente, por lo que no necesitaremos utilizar este script fuera de los fines de prueba y desarrollo.Produciremos un conjunto de pares clave-valor para nuestro tema
users
. Esta propiedad establece que nuestra clave y nuestros valores se separarán utilizando el carácter,
.
El comando anterior te dejará en un prompt interactivo. Desde aquí, podemos introducir varios pares clave-valor para producir al tema users
. Cuando hayas terminado, pulsa Control-C en tu teclado para salir del prompt:
>1,mitch >2,elyse >3,isabelle >4,sammy
Después de producir los datos para nuestro tema, podemos utilizar el script kafka-console-consumer
para leer los datos. El siguiente comando muestra cómo hacerlo:
kafka-console-consumer
\
--bootstrap-server
localhost:9092
\
--topic
users
\
--from-beginning
# output
mitch
elyse
isabelle
sammy
El script
kafka-console-consumer
también se incluye en la distribución de Kafka. De forma similar a lo que hemos mencionado para el scriptkafka-console-producer
, la mayoría de los tutoriales de este libro aprovecharán los procesos consumidores que están integrados en Kafka Streams y ksqlDB, en lugar de utilizar este script de consola independiente (que es útil para realizar pruebas).La bandera
--from-beginning
indica que debemos empezar a consumir desde el principio del tema Kafka.
Por defecto, la kafka-console-consumer
sólo imprimirá el valor del mensaje. Pero como hemos aprendido antes, los eventos contienen en realidad más información, incluyendo una clave, una marca de tiempo y cabeceras. Vamos a pasar algunas propiedades adicionales al consumidor de la consola para que podamos ver también los valores de la marca de tiempo y la clave:9
kafka-console-consumer\
--bootstrap-server localhost:9092\
--topic users\
--property print.timestamp=
true
\
--property print.key=
true
\
--property print.value=
true
\
--from-beginning# output
CreateTime:1598226962606 1 mitch CreateTime:1598226964342 2 elyse CreateTime:1598226966732 3 isabelle CreateTime:1598226968731 4 sammy
Ya está. Ya has aprendido a realizar algunas interacciones muy básicas conun clústerKafka. El paso final es desmontar nuestro clúster local utilizando el siguientecomando:
docker-compose down
Resumen
El modelo de comunicación de Kafka facilita la comunicación entre múltiples sistemas, y su capa de almacenamiento, rápida, duradera y de sólo apéndices, permite trabajar con flujos de datos en rápido movimiento con facilidad. Al utilizar una implementación en clúster, Kafka puede lograr una alta disponibilidad y tolerancia a fallos en la capa de almacenamiento replicando los datos en varias máquinas, denominadas brokers. Además, la capacidad del clúster para recibir los latidos de los procesos consumidores y actualizar la pertenencia a los grupos consumidores, permite una alta disponibilidad, tolerancia a fallos y escalabilidad de la carga de trabajo en la capa de procesamiento y consumo de flujos. Todas estas características han hecho de Kafka una de las plataformas de procesamiento de flujos más populares que existen.
Ahora ya tienes suficientes conocimientos sobre Kafka para empezar a trabajar con Kafka Streams y ksqlDB. En la siguiente sección, comenzaremos nuestro viaje con Kafka Streams viendo cómo encaja en el ecosistema más amplio de Kafka, y aprendiendo cómo podemos utilizar esta biblioteca para trabajar con datos en la capa de procesamiento de flujos.
1 Hablamos de las matrices de bytes en bruto que se almacenan en los temas, así como del proceso de deserialización de los bytes en estructuras de nivel superior como objetos JSON/registros Avro, en el Capítulo 3.
2 Jay Kreps, Neha Narkhede y Jun Rao dirigieron inicialmente el desarrollo de Kafka.
3 Determinista significa que las mismas entradas producirán las mismas salidas.
4 Por eso las bases de datos tradicionales utilizan registros para la replicación. Los registros se utilizan para capturar cada operación de escritura en la base de datos líder, y procesar las mismas escrituras, en orden, en una base de datos réplica para recrear de forma determinista el mismo conjunto de datos en otra máquina.
5 Martin Kleppmann tiene un interesante artículo sobre este tema, que puedes encontrar en https://oreil.ly/tDZMm. Habla de las distintas compensaciones y de las razones por las que se puede elegir una estrategia en lugar de otra. Además, el artículo de seguimiento de Robert Yokota profundiza en cómo admitir varios tipos de eventos cuando se utiliza Confluent Schema Registry para la gestión/evolución de esquemas.
6 La estrategia de partición es configurable, pero una estrategia popular, incluida la que se implementa en Kafka Streams y ksqlDB, consiste en establecer la partición basándose en la clave del registro (que puede extraerse de la carga útil del registro o establecerse explícitamente). Hablaremos de esto con más detalle en los próximos capítulos.
7 Las contrapartidas incluyen periodos de recuperación más largos tras determinados escenarios de fallo, mayor utilización de recursos (descriptores de archivo, memoria) y mayor latencia de extremo a extremo.
8 Hay muchas imágenes Docker entre las que elegir para ejecutar Kafka. Sin embargo, las imágenes de Confluent son una opción conveniente, ya que Confluent también proporciona imágenes Docker para algunas de las otras tecnologías que utilizaremos en este libro, incluidas ksqlDB y Confluent Schema Registry.
9 A partir de la versión 2.7, también puedes utilizar la bandera --property print.headers=true
para imprimir las cabeceras de los mensajes.
Get Dominar Kafka Streams y ksqlDB 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.