Cuando se pulsa una tecla en el teclado de un ordenador se produce una interrupción. Lo que quiere decir que el ordenador debe dejar de hacer lo que estaba haciendo para atender dicha interrupción. El sistema operativo recoge el carácter correspondiente a la tecla pulsada y lo notifica a las aplicaciones. Esto permite por ejemplo que una aplicación web pueda indicarle a un navegador que ejecute un determinado trozo de código JavaScript cuando se produzca la pulsación de una tecla.

El término interrupción se utiliza habitualmente para referirse a la acción que sucede a bajo nivel. En un nivel más alto de abstracción estas acciones se conocen como eventos. La mayoría de aplicaciones procesan eventos tales como la pulsación de una tecla o un botón de un ratón de manera local. Algunas además los reenvían a un servidor remoto. El análisis de estos eventos permite conocer de forma muy precisa como interaccionan los usuarios con una aplicación.

Enviar eventos de teclado o ratón a un servidor remoto implica generar una gran cantidad de tráfico. Aunque no por ello deja de ser una réplica del modelo de más bajo nivel. Una aplicación notifica eventos a un servidor de igual forma que un teclado o ratón los notifica a un ordenador. Lo que se puede generalizar diciendo que todo sistema de información puede entenderse como un conjunto de procesos que se comunican generando y respondiendo a eventos. La tecnología o estrategia utilizada para procesar la información puede variar, pero hay patrones recurrentes. Utilizar algún tipo de almacén intermedio para evitar la pérdida de eventos es uno de ellos. El buffer que utiliza un teclado para almacenar las últimas teclas pulsadas es similar al que utiliza un servidor para almacenar los eventos recibidos. Y en este sentido, Apache Kafka puede verse como uno de estos almacenes de eventos.

Kafka

Apache Kafka es una plataforma altamente escalable, creada originalmente por LinkedIn, que facilita el proceso de ingentes cantidades de información en tiempo real. Su caso de uso más habitual es su utilización como sistema de mensajería. Donde la publicación de un mensaje por parte de un proceso representa un evento que se produce en el sistema, conteniendo el mensaje el detalle concreto del evento generado, y permitiendo que una aplicación lo procese con objeto de consumirlo completamente, o lo transforme con objeto de volver a publicarlo como parte de una cadena de procesamiento mayor.

La versión actual de Kafka es la 2.0.0, una major release liberada a finales de julio de este mismo año.

La introducción de tan bajo nivel de los primeros párrafos es para tratar de responder las preguntas habituales acerca de para qué sirve Kafka y cuando se debe utilizar. Se podría utilizar siempre, para procesar todos los eventos generados por un sistema de cualquier tamaño, pero se debe utilizar preferentemente cuando el volumen de información a gestionar, sobre todo en tiempo real, sea enorme. El rendimiento y la escalabilidad son dos de los factores clave que deberían tenerse en cuenta a la hora de tomar la decisión de utilizarlo.

Persistencia

Kafka está diseñado para almacenar y distribuir grandes volúmenes de eventos de forma muy eficiente. Una característica que lo distingue del resto de productos similares es que almacena los eventos de forma duradera. Una aplicación puede suscribirse para recibir los nuevos eventos que se produzcan, o recibir los eventos que se produjeron en un periodo de tiempo concreto dado. Esta característica permite ejecutar procesos sobre eventos generados en el pasado como si se estuvieran produciendo en tiempo real. Y posibilita la repetición a posteriori en caso de error de un mismo proceso con los mismos eventos.

En este punto es importante entender la visión que ofrece Kafka respecto a los eventos. Son las secuencias de información que representan la historia del proceso realizado por un sistema. Almacenando el histórico de eventos consumidos y producidos por un sistema se puede reproducir el proceso realizado por el mismo. Los eventos se pueden consumir en tiempo real, por ejemplo para responder de forma inmediata a las peticiones realizadas por un cliente a través de una aplicación, pero también pueden procesarse con una menor prioridad, por ejemplo para agregar información de cara a su posterior análisis con algún tipo de herramienta a modo de cuadro de mandos. Con esta visión en mente, es fácil entender que Kafka se denomine a sí mismo “structured commit log”, es decir, un sistema de log que almacena los mensajes de forma estructurada para facilitar el tratamiento de los mismos, a diferencia de un log tradicional donde los mensajes son cadenas de texto añadidos en un fichero sin ninguna estructura.

Estructura

Kafka escala horizontalmente gracias a su arquitectura distribuida en forma de cluster con una alta tolerancia a fallos basada en Zookeeper. Un cluster de Kafka almacena información organizada en categorías llamadas tópicos e identificadas por un nombre. Donde cada tópico es un almacén de secuencias ordenadas de una unidad mínima de información denominado registro. Y cada registro es un paquete de información compuesto de una clave, un valor y un timestamp.

Los tópicos se organizan internamente en estructuras llamadas particiones. Estas particiones son similares a las existentes en muchas bases de datos relacionales, permitiendo almacenar los registros agrupados por algún criterio determinado con objeto de mejorar los tiempos de acceso. Los clientes que producen registros eligen la partición en la que deben publicarse estos. Cuando se publica un registro en un tópico se almacena en una partición y se le asigna un offset que indica su posición dentro de la partición. Un offset es un número secuencial único que identifica de forma única a un registro. Una característica de Kafka a este respecto es que los clientes que consumen registros son los que llevan el control de los offsets; cuando se subscriben a un tópico indican el offset del primer registro que quieren recibir. El control lo lleva el cliente, no el servidor, lo que permite simplificar al máximo la lógica del servidor. De hecho, Kafka gestiona los offsets de los consumidores con un tópico, lo que le evita tener que mantener estructuras privadas dedicadas y le permite reutilizar toda la infraestructura existente.

Los registros son persistidos en los tópicos en base a una política de retención configurable. Los registros pueden almacenarse durante horas, días, o indefinidamente según cada caso de uso concreto. Kafka garantiza un tiempo de respuesta constante independientemente del número de registros almacenados.

Todas las lecturas y escrituras sobre una determinada partición son realizadas por un único servidor denominado bróker y que actúa como líder. Por cada líder pueden existir otros brokers actuando como seguidores que replican la partición de forma pasiva. Si el líder de una partición cae entonces uno de sus seguidores se promociona automáticamente a líder haciéndose cargo de gestionar la partición. Incluso es posible replicar un mismo cluster en varios datacenters, ya sea como mecanismo de backup, o con el propósito de tener la información distribuida por regiones geográficas distintas.

Grupos de Consumidores

Un concepto importante en Kafka son los denominados grupos de consumidores. Cada grupo de consumidores está ligado a un tópico, y cada consumidor dentro de un grupo está ligado a una partición. Si todos los consumidores de un tópico pertenecen al mismo grupo entonces los registros se distribuyen de forma que se garantiza que un mismo registro es consumido una única vez por un único consumidor del grupo. Si los consumidores pertenecen a varios grupos entonces un mismo registro se distribuye a un único consumidor de cada grupo.

Esto permite configurar Kafka siguiendo los patrones habituales utilizados en los sistemas de mensajería clásicos. Si se utiliza un único grupo entonces el sistema se comporta como una cola, donde un mensaje es consumido por único proceso. Si se utilizan múltiples grupos entonces el sistema se comporta como un sistema de publicación/suscripción, donde un mensaje es consumido por varios procesos. Tener un único consumidor por cada partición garantiza que los registros se consumen en el mismo orden que se publican, pero implica que Kafka sólo garantiza el orden a nivel de partición, no de tópico.

API

Kafka está escrito en Scala y Java, aunque existen clientes para prácticamente todos los lenguajes de programación de uso más habitual. El cliente para Java es desarrollado por el propio proyecto de forma oficial.

Kafka expone su funcionalidad a través de un protocolo sobre TCP mediante el que oferta servicios agrupados en base a su cometido. Cada grupo de servicios es un API distinto. Cinco de ellos constituyen el núcleo del sistema. Producer API permite publicar registros en los tópicos. Consumer API permite suscribirse a los tópicos y consumir registros. Streams API permite consumir registros de tópicos para volver a publicarlos transformados en otros tópicos. Connector API permite construir pasarelas entre Kafka y otros sistemas, como por ejemplo una base de datos relacional. Y AdminClient API que permite gestionar los distintos componentes que conforman una instancia o cluster de Kafka.

Para el caso de uso de Kafka como un sistema de procesamiento de streams, además del API, existe un proyecto llamado KSQL que se liberó este mismo año y permite escribir procesos que consuman streams con un lenguaje similar a SQL.

Resumiendo, Kafka es una plataforma que aúna distintas características que permiten utilizarlo como un sistema de almacenamiento, un sistema de mensajería, y un sistema de procesamiento de streams muy eficiente y altamente escalable.