viernes, 3 de abril de 2020

Apache Kafka. una pequeña introducción

Seguro que en los últimos años has oído hablar de Apache Kafka. En esta serie de post intentaremos explicar qué es Kafka y como funciona.

Un poco de historia
Kafka fue creado en Linkedin por un equipo lideardo por Jay Kreps y entre los que destacan Neha Narkhende and Jun Rao.
Este equipo desarrolló Kafka para solucionar un problema de integración entre distintos sistemas dentro de LinkedIn.

Jay explica como y porqué diseñó Kafka en este post
Muy, muy, pero que muy resumido, en vez de hacer una integración de sistemas uno a uno, como sería esto:
 

Asi que en vez de todas estas sincronizaciones personalizadas, se propone un único punto de integración que acepte  propuso un único punto de integración, donde todos los sistemas publicasen la información en ese punto y todos los sistemas que consumían información serían capaces de suscribirse y consumir la información que ellos necesitasen:
Esa caja central (Unifield Log) acabaría siendo lo que es hoy Kafka.
El invento funcionó tan bien que Jay, Neha y Jay fundaron Confluent una empresa que se dedica a vender servicios y soporte profesional de Kafka

¿Qué es Apache Kafka?
En la pagina del proyecto apache, kafka se define como una plataforma de streaming distribuida con estas tres características:
  • Flujos de mensajes basado en productores y subscriptores, parecidos a una cola de mensajería o un sistema de mensajes empresarial
  • Almacena flujos de registros de manera tolerante a fallos y duradera
  • Procesa  flujos de registros a medida que se generan.

Mientras que Jay en el post que vimos anteriormente define kafka como un log distribuido y escalable.

Mensajes, topics, productores y consumidores

La unidad básica de kafka es el mensaje. Un mensaje en kafka es un array de bytes, no tiene formato específico. Los mensajes se almacenan en topics. Un topic estará dividido en una o más particiones. Pero para simplificar las explicaciones, por ahora hablaremos de topics con una única partición.


Un mensaje de kafka tiene un timestamp y mensaje y opcionalmente puede llevar una cabecera con metadatos y una clave.
Un mensaje en kafka es inmutable, una vez escrito no se puede modificar.
Cuando un productor escribe un mensaje lo pone en la última posición del fichero, de la misma manera que cuando escribes en un log de una aplicación cada nuevo mensaje va al final del fichero.


Los nodos de un cluster de kafka se llaman brokers. En los brokers se almacenan los topics. Cuando un productor se le manda un mensaje a un broker, este coloca el nuevo mensaje siempre al final del topic.
Al igual que los mensajes de log de las aplicaciones, los productores añaden los mensajes al final de cada partición y no pueden ser modificados.



Los consumidores de Kafka leen los mensajes desde en el mismo sentido desde el más antiguo hasta al el último publicado.


Kafka no es una cola de mensajería
Antes de seguir me gustaría dejar claro una cosa, Kafka no es una cola de mensajería. Es algo bastante común, mucha gente cuando empieza a hablar o trabajar con kafka piensa que es una cola de mensajería, pero no lo es.

  • Una cola de mensajería siempre garantiza que los mensajes de entregan al consumidor en orden. Kafka no garantiza siempre el orden de los mensajes (veremos como funciona el orden en kafka más adelante)
  • Una cola de mensajería sirve un mensaje una única vez. Cuando un consumidor leer un mensaje, este se desencola, así que desaparece. En kafka, un mensaje puede ser leído varias veces. 
Escalabilidad, Particiones y Orden
Kafka permite dividir el topic en distintas particiones.
Cuando tenemos un topic dividido en particiones, el productor decide en qué partición poner el mensaje. Si los mensajes llevan clave, el productor hará un hash de la clave módulo del numero de particiones. por ejemplo para un topic de 3 particiones los restos de la división son 0, 1 o 2.
Mientras que si el mensaje no tiene clave, aplicará una estrategia de round robin de manera que el reparto de mensajes es uniforme entre particiones.

Kafka también permite agrupar los consumidores en consumergroups. Los consumidores del mismo grupo no comparten particiones de kafka. Esto significa, por ejemplo que puedes tener dos procesos en paralelo procesando mensajes de kafka de un mismo topic y te garantiza que no van a procesar el mismo mensaje.
En resumidas cuentas, las particiones te indican el numero máximo de consumidores por consumer group. Es decir si quieres escalar una aplicación que consume de kafka lo podrás hacer hasta que tengas el mismo numero de consumidores que particiones del topic.
En el siguiente gráfico podemos ver como el consumergroup App1 tiene tres consumidores leyendo de un topic. Ese topic tiene dos particiones, el consumidor 2 está asignado a la partición 0 y el consumidor 1 está asignado a la partición 1. Como el topic tiene solo dos particiones, el consumidor 3 no está consumiendo mensajes.


De aquí sacamos dos conclusiones:
  • El numero de particiones de un tópic indica el número máximo de consumidres dentro de un consumer group. Así que la escalabilidad de las aplicaciones consumidoras depende del numero de particiones de un tópic.
  • No se garantiza el orden. Únicamente kafka garantiza el orden de lectura dentro de una partición.

Recordad que el productor por defecto utiliza la clave del mensaje para decidir en qué partición está. Así que si necesitas orden puedes tenerlo en cierta medida. Por ejemplo si estás haciendo un sistema logístico y quieres saber tener todos los eventos un pedido en orden, deberías publicar todos los eventos en kafka usando como clave el id unico del pedido.
O si estas haciendo streaming de una tabla de base de datos deberías usar como clave la PK de la tabla para que todos los cambios del registro lleguen en orden, etc..

Vida de los mensajes, retención y compactación
En una cola de mensajería los mensajes desaparecen cuando son leídos. En kafka no. Una de las propiedades de un topic de kafka es la cleanup.policy. Esta propiedad tiene tres posibles valores:
  • delete: Es el valor por defecto, Pasado un periodo de tiempo definido en la propiedad retention.ms (no es exactamente así, pero no vamos a entrar en más detalles por ahora)
  • compact: Se garantiza que se mantendrá el último valor de los mensajes con una misma clave. el resto de mensajes se eliminarán. si el último mensaje es un tombstone (clave con mensaje nulo) el mensaje también se eliminará 
  • both: Ejecuta las dos estrategias, así que tienes una compactación por clave y además pasado cierto tiempo se descartan los mensajes.
Siguiendo con el símil del log de una aplicación, es muy posible que si guardabas el log en ficheros usases alguna estrategia de rolling, creando un dichero por día, mes o semana, o partiendolo por tamaño del fichero.
En las particiones de kafka se hace lo mismo. Las particiones se reparten en segmentos. El segmento activo es el último, donde se escriben los nuevos mensajes. dependiendo de la configruracion del topìc, pasado un tiempo o un tamaño determinado el segmento se cierra y se abre otro.

Es sobre los segmentos cerrados donde se aplican las operaciones de compact y delete. En concreto delete no borra mensajes, borra segmentos de la partición.


Aun faltan muchas cosas que contar para esta introducción a kafka, pero creo que el post está quedando muy largo así que una vez revisado partes básicas como mensajes, topics, particiones, consumidores y productores... es un buen momento para parar.

Próximo post... las Réplicas.

No hay comentarios:

Publicar un comentario