Algunos de los monolitos que usan Apache Kafka
Kafka es una palabra que se escucha mucho hoy en día... Muchas de las principales empresas digitales parecen usarla también. Pero, ¿qué es en realidad?
Kafka se desarrolló originalmente en LinkedIn en 2011 y ha mejorado mucho desde entonces. Hoy en día es una plataforma completa, lo que le permite almacenar cantidades absurdas de datos de manera redundante, tener un bus de mensajes con un rendimiento enorme (millones/seg) y usar el procesamiento de flujo en tiempo real en los datos que pasan por todos a la vez.
Todo esto está muy bien, pero reducido a su esencia, Kafka es un registro de compromiso distribuido, escalable horizontalmente y tolerante a fallas.
Esas fueron algunas palabras elegantes, analicémoslas una por una y veamos qué significan. Después, profundizaremos en cómo funciona.
Un sistema distribuido es uno que se divide en varias máquinas en ejecución, todas las cuales trabajan juntas en un clúster para aparecer como un solo nodo para el usuario final. Kafka se distribuye en el sentido de que almacena, recibe y envía mensajes en diferentes nodos (llamados intermediarios).
También tengo una introducción completa sobre esto.
Los beneficios de este enfoque son la alta escalabilidad y la tolerancia a fallas.
Definamos primero el término escalabilidad vertical. Digamos, por ejemplo, que tiene un servidor de base de datos tradicional que está empezando a sobrecargarse. La forma de resolver esto es simplemente aumentar los recursos (CPU, RAM, SSD) en el servidor. Esto se llama escalamiento vertical , donde agrega más recursos a la máquina. Hay dos grandes desventajas de escalar hacia arriba:
La escalabilidad horizontal está resolviendo el mismo problema arrojándole más máquinas. Agregar una nueva máquina no requiere tiempo de inactividad ni hay límites en la cantidad de máquinas que puede tener en su clúster. El problema es que no todos los sistemas admiten la escalabilidad horizontal, ya que no están diseñados para funcionar en un clúster y los que lo están suelen ser más complejos para trabajar.
El escalado horizontal se vuelve mucho más barato después de cierto umbral
Algo que surge en los sistemas no distribuidos es que tienen un único punto de falla (SPoF). Si su servidor de base de datos único falla (como lo hacen las máquinas) por cualquier motivo, está jodido.
Los sistemas distribuidos están diseñados de tal manera que se adaptan a las fallas de manera configurable. En un clúster de Kafka de 5 nodos, puede hacer que siga funcionando incluso si 2 de los nodos están inactivos. Vale la pena señalar que la tolerancia a fallas está en una compensación directa con el rendimiento, ya que cuanto más tolerante a fallas es su sistema, menos rendimiento tiene.
Un registro de compromiso (también conocido como registro de escritura anticipada, registro de transacciones) es una estructura de datos ordenada persistente que solo admite anexos. No se pueden modificar ni eliminar registros del mismo. Se lee de izquierda a derecha y garantiza la ordenación de los artículos.
Ejemplo de ilustración de un registro de confirmación, tomado de aquí
- ¿Me estás diciendo que Kafka es una estructura de datos tan simple?
En muchos sentidos, sí. Esta estructura está en el corazón de Kafka y tiene un valor incalculable, ya que proporciona ordenación, que a su vez proporciona un procesamiento determinista. Ambos son problemas no triviales en sistemas distribuidos.
Kafka realmente almacena todos sus mensajes en el disco (más sobre eso más adelante) y tenerlos ordenados en la estructura le permite aprovechar las lecturas secuenciales del disco.
Estos dos puntos tienen enormes beneficios de rendimiento, ya que el tamaño de los datos está completamente desvinculado del rendimiento. Kafka tiene el mismo rendimiento ya sea que tenga 100 KB o 100 TB de datos en su servidor.
Las aplicaciones ( productores ) envían mensajes ( registros ) a un nodo Kafka ( broker ) y dichos mensajes son procesados por otras aplicaciones llamadas consumidores . Dichos mensajes se almacenan en un tema y los consumidores se suscriben al tema para recibir nuevos mensajes.
Como los temas pueden volverse bastante grandes, se dividen en particiones de menor tamaño para mejorar el rendimiento y la escalabilidad. (por ejemplo: _digamos que estaba almacenando solicitudes de inicio de sesión de usuario, podría dividirlas por el primer carácter del nombre de usuario del usuario)_Kafka garantiza que todos los mensajes dentro de una partición se ordenen en la secuencia en que llegaron. La forma en que distingue un mensaje específico es a través de su desplazamiento , que podría verse como un índice de matriz normal, un número de secuencia que se incrementa para cada mensaje nuevo en una partición.
Kafka sigue el principio de un corredor tonto y un consumidor inteligente. Esto significa que Kafka no realiza un seguimiento de los registros que lee el consumidor y los elimina, sino que los almacena durante un período de tiempo determinado (por ejemplo, un día) o hasta que se alcanza un umbral de tamaño. Los propios consumidores encuestan a Kafka en busca de nuevos mensajes y dicen qué registros quieren leer. Esto les permite incrementar/disminuir el desplazamiento en el que se encuentran como deseen, pudiendo así reproducir y reprocesar eventos.
Vale la pena señalar que los consumidores son en realidad grupos de consumidores que tienen uno o más procesos de consumo en su interior. Para evitar que dos procesos lean el mismo mensaje dos veces, cada partición está vinculada a un solo proceso consumidor por grupo.
Representación del flujo de datos
Como mencioné anteriormente, Kafka almacena todos sus registros en el disco y no guarda nada en la RAM. Quizás se pregunte cómo es esto, en lo más mínimo, una elección sensata. Hay numerosas optimizaciones detrás de esto que lo hacen factible:
Todas estas optimizaciones permiten que Kafka entregue mensajes casi a la velocidad de la red.
Hablemos de cómo Kafka logra la tolerancia a fallas y cómo distribuye los datos entre los nodos.
Los datos de la partición se replican entre varios intermediarios para preservar los datos en caso de que fallezca un intermediario.
En todo momento, un intermediario "posee" una partición y es el nodo a través del cual las aplicaciones escriben/leen desde la partición. Esto se llama un líder de partición . Replica los datos que recibe a otros N corredores, llamados seguidores . También almacenan los datos y están listos para ser elegidos como líder en caso de que el nodo líder muera.
Esto le ayuda a configurar la garantía de que no se perderá ningún mensaje publicado con éxito. Tener la opción de cambiar el factor de replicación le permite cambiar el rendimiento por garantías de durabilidad más sólidas, según la criticidad de los datos.
4 corredores de Kafka con un factor de replicación de 3
De esta manera, si un líder alguna vez falla, un seguidor puede tomar su lugar.
Sin embargo, es posible que te estés preguntando:
- ¿Cómo sabe un productor/consumidor quién es el líder de una partición?
Para que un productor/consumidor escriba/lea desde una partición, necesita conocer a su líder, ¿verdad? Esta información debe estar disponible en algún lugar. Kafka almacena dichos metadatos en un servicio llamado Zookeeper .
Zookeeper es un almacén de clave-valor distribuido. Está altamente optimizado para lecturas, pero las escrituras son más lentas. Se usa más comúnmente para almacenar metadatos y manejar la mecánica de la agrupación (latidos, distribución de actualizaciones/configuraciones, etc.).
Permite a los clientes del servicio (los corredores de Kafka) suscribirse y recibir los cambios una vez que ocurren. Así es como los corredores saben cuándo cambiar los líderes de partición. Zookeeper también es extremadamente tolerante a fallas y debería serlo, ya que Kafka depende en gran medida de ello.
Se utiliza para almacenar todo tipo de metadatos, por mencionar algunos:
El Productor y los Consumidores solían conectarse directamente y hablar con Zookeeper para obtener esta (y otra) información. Kafka se ha alejado de este acoplamiento y, desde las versiones 0.8 y 0.9, respectivamente, los clientes obtienen información de metadatos directamente de los corredores de Kafka, quienes hablan con Zookeeper.
Flujo de metadatos
En Kafka, un procesador de flujo es cualquier cosa que toma flujos continuos de datos de los temas de entrada, realiza algún procesamiento en esta entrada y produce un flujo de datos para los temas de salida (o servicios externos, bases de datos, la papelera, donde sea realmente...)
Es posible realizar un procesamiento simple directamente con las API de productor/consumidor; sin embargo, para transformaciones más complejas, como unir flujos, Kafka proporciona una biblioteca API de flujos integrada.
Esta API está diseñada para usarse dentro de su propia base de código, no se ejecuta en un intermediario. Funciona de manera similar a la API del consumidor y lo ayuda a escalar el trabajo de procesamiento de secuencias en varias aplicaciones (similar a los grupos de consumidores).
Un procesamiento sin estado de un flujo es un procesamiento determinista que no depende de nada externo. Usted sabe que para cualquier dato dado, siempre producirá el mismo resultado independientemente de cualquier otra cosa. Un ejemplo de eso sería una transformación de datos simple: agregar algo a una cadena "Hello"
-> "Hello, World!"
.
Es importante reconocer que los flujos y las tablas son esencialmente lo mismo. Una secuencia se puede interpretar como una tabla y una tabla se puede interpretar como una secuencia.
Una secuencia se puede interpretar como una serie de actualizaciones de datos, en la que el agregado es el resultado final de la tabla. Esta técnica se llama Event Sourcing .
Si observas cómo se logra la replicación síncrona de bases de datos, verás que es a través de la denominada replicación por streaming , donde cada cambio en una tabla se envía a un servidor réplica. Otro ejemplo de abastecimiento de eventos son los libros de contabilidad de Blockchain: un libro de contabilidad también es una serie de cambios.
Un flujo de Kafka se puede interpretar de la misma manera: eventos que, cuando se acumulan, forman el estado final. Estas agregaciones de secuencias se guardan en un RocksDB local (de forma predeterminada) y se denominan KTable.
Cada registro incrementa el conteo agregado
Una tabla se puede ver como una instantánea del valor más reciente para cada clave en una secuencia. De la misma manera que los registros de flujo pueden producir una tabla, las actualizaciones de tablas pueden producir un flujo de registro de cambios.
Cada actualización produce un registro de instantánea en el flujo
Algunas operaciones simples como map()
o filter()
no tienen estado y no requieren que guardes ningún dato relacionado con el procesamiento. Sin embargo, en la vida real, la mayoría de las operaciones que realizará tendrán estado (p. ej count()
) y, como tal, requerirá que almacene el estado acumulado actualmente.
El problema de mantener el estado en los procesadores de flujo es que los procesadores de flujo pueden fallar. ¿Dónde necesitaría mantener este estado para ser tolerante a fallas?
Un enfoque ingenuo es simplemente almacenar todo el estado en una base de datos remota y unirse a través de la red a esa tienda. El problema con esto es que no hay localidad de datos y muchos viajes de ida y vuelta en la red, los cuales ralentizarán significativamente su aplicación. Un problema más sutil pero importante es que el tiempo de actividad de su trabajo de procesamiento de secuencias estaría estrechamente relacionado con la base de datos remota y el trabajo no sería autónomo (un cambio en la base de datos de otro equipo podría interrumpir su procesamiento) .
Entonces, ¿cuál es un mejor enfoque? Recuerde la dualidad de tablas y flujos. Esto nos permite convertir flujos en tablas que se ubican junto con nuestro procesamiento. También nos proporciona un mecanismo para manejar la tolerancia a fallas, al almacenar los flujos en un corredor de Kafka.
Un procesador de flujo puede mantener su estado en una tabla local (por ejemplo, RocksDB), que se actualizará desde un flujo de entrada (quizás después de alguna transformación arbitraria). Cuando el proceso falla, puede restaurar sus datos reproduciendo la transmisión.
Incluso podría tener una base de datos remota que sea el productor de la transmisión, transmitiendo de manera efectiva un registro de cambios con el que reconstruirá la tabla localmente.
Procesamiento con estado, uniendo un KStream con un KTable
Normalmente, se vería obligado a escribir el procesamiento de su transmisión en un lenguaje JVM, ya que ahí es donde se encuentra el único cliente API oficial de Kafka Streams.
Ejemplo de configuración de KSQL
Lanzado en abril de 2018 , KSQL es una característica que le permite escribir sus trabajos de transmisión simples en un lenguaje familiar similar a SQL.
Configura un servidor KSQL y lo consulta de forma interactiva a través de una CLI para administrar el procesamiento. Funciona con las mismas abstracciones (KStream y KTable), garantiza los mismos beneficios de la API de Streams (escalabilidad, tolerancia a fallos) y simplifica enormemente el trabajo con flujos.
Esto puede no parecer mucho, pero en la práctica es mucho más útil para probar cosas e incluso permite que personas ajenas al desarrollo (por ejemplo, propietarios de productos) jueguen con el procesamiento de secuencias. Le animo a que eche un vistazo al vídeo de inicio rápido y vea lo sencillo que es .
Kafka streams es una combinación perfecta de poder y simplicidad. Podría decirse que tiene las mejores capacidades para trabajos de transmisión en el mercado y se integra con Kafka mucho más fácilmente que otras alternativas de procesamiento de transmisión ( Storm , Samza , Spark , Wallaroo ).
El problema con la mayoría de los otros marcos de procesamiento de flujo es que son complejos para trabajar e implementar. Un marco de procesamiento por lotes como Spark necesita:
Desafortunadamente, abordar estos problemas hace que los marcos sean bastante invasivos. Quieren controlar muchos aspectos de cómo se implementa, configura, supervisa y empaqueta el código.
Kafka Streams le permite implementar su propia estrategia de implementación cuando la necesite, ya sea Kubernetes , Mesos , Nomad , Docker Swarm u otros.
La motivación subyacente de Kafka Streams es permitir que todas sus aplicaciones realicen el procesamiento de secuencias sin la complejidad operativa de ejecutar y mantener otro clúster. El único inconveniente potencial es que está estrechamente relacionado con Kafka, pero en el mundo moderno, donde la mayoría, si no todo el procesamiento en tiempo real, funciona con Kafka, eso puede no ser una gran desventaja.
Como ya cubrimos, Kafka le permite tener una gran cantidad de mensajes a través de un medio centralizado y almacenarlos sin preocuparse por cosas como el rendimiento o la pérdida de datos.
Esto significa que es perfecto para usar como el corazón de la arquitectura de su sistema, actuando como un medio centralizado que conecta diferentes aplicaciones. Kafka puede ser la pieza central de una arquitectura basada en eventos y le permite realmente desacoplar las aplicaciones entre sí.
Kafka le permite desacoplar fácilmente la comunicación entre diferentes (micro)servicios. Con la API de Streams, ahora es más fácil que nunca escribir lógica empresarial que enriquezca los datos de temas de Kafka para el consumo de servicios. Las posibilidades son enormes y lo insto a explorar cómo las empresas están usando Kafka.
El alto rendimiento, la disponibilidad y la escalabilidad por sí solos no son razones suficientes para que una empresa adopte una nueva tecnología. Hay otros sistemas que cuentan con propiedades similares, pero ninguno se ha vuelto tan ampliamente utilizado. ¿Porqué es eso?
La razón por la que Kafka ha crecido en popularidad (y continúa haciéndolo) es una cosa clave: las empresas de hoy en día se benefician enormemente de la arquitectura basada en eventos. Esto se debe a que el mundo ha cambiado: muchos servicios diferentes (Internet de las cosas, aprendizaje automático, dispositivos móviles, microservicios) producen y consumen una cantidad enorme (y en constante crecimiento) de datos.
Una única plataforma de transmisión de eventos en tiempo real con almacenamiento duradero es la forma más limpia de lograr dicha arquitectura. Imagínese el tipo de desastre que sería si la transmisión de datos hacia/desde cada servicio utilizara una tecnología diferente que se adaptara específicamente a él.
Esto, junto con el hecho de que Kafka proporciona las características apropiadas para un sistema tan generalizado (almacenamiento duradero, transmisión de eventos, primitivas de tabla y flujo, abstracción a través de KSQL, código abierto, desarrollado activamente) lo convierte en una opción obvia para las empresas.
Apache Kafka es una plataforma de transmisión distribuida capaz de manejar billones de eventos al día. Kafka proporciona canalizaciones de publicación y suscripción de baja latencia, alto rendimiento y tolerancia a fallas, y puede procesar flujos de eventos.
Repasamos su semántica básica (productor, corredor, consumidor, tema), aprendimos sobre algunas de sus optimizaciones (pagecache), aprendimos cómo es tolerante a fallas mediante la replicación de datos y conocimos sus poderosas capacidades de transmisión en constante crecimiento.
Kafka ha visto una gran adopción en miles de empresas en todo el mundo, incluido un tercio de Fortune 500. Con el desarrollo activo de Kafka y la primera versión principal 1.0 lanzada recientemente (1 de noviembre de 2017), hay predicciones de que esta plataforma de transmisión va a ser tan grande y central en una plataforma de datos como lo son las bases de datos relacionales.
Espero que esta introducción lo haya ayudado a familiarizarse con Apache Kafka y su potencial.
La madriguera del conejo es más profunda de lo que este artículo pudo cubrir. Aquí hay algunas características que no tuve la oportunidad de mencionar pero que, sin embargo, es importante conocer:
Controller Broker, réplicas sincronizadas : la forma en que Kafka mantiene el clúster en buen estado y garantiza una consistencia y durabilidad adecuadas.
Connector API : API que lo ayuda a conectar varios servicios a Kafka como fuente o receptor (PostgreSQL, Redis, ElasticSearch)
Compactación de registros : una optimización que reduce el tamaño de los registros. Extremadamente útil en flujos de registro de cambios
Semántica de mensajes exactamente una vez : garantiza que los mensajes se reciban exactamente una vez. Esto es un gran problema, ya que es difícil de lograr.
Bombero de sistemas distribuidos de Apache Kafka: el intermediario del controlador : otra publicación de blog mía en la que me sumerjo en cómo funciona la coordinación entre el intermediario y mucho más.
Blog de Confluent : una gran cantidad de información sobre Apache Kafka
Documentación de Kafka: documentación excelente, extensa y de alta calidad.
Vídeos de la Cumbre Kafka 2017
Gracias por tomarse el tiempo de leer esto.
Si cree que esta información fue útil, considere darle una gran cantidad de aplausos para aumentar su visibilidad y ayudar a que la gente nueva la encuentre.
~ Stanislav Kozlovski
Este artículo me abrió la puerta para unirme a Confluent . Estoy inmensamente agradecido por la oportunidad que me han brindado: actualmente trabajo en Kafka, ¡lo cual es más que increíble! ¡Confluent es una empresa de big data fundada por los propios creadores de Apache Kafka! Actualmente trabajamos en todo el ecosistema de Kafka, incluida una oferta en la nube administrada de Kafka como servicio.
¡Estamos contratando para muchos puestos (especialmente SRE/Ingenieros de software) en Europa y EE. UU.! Si está interesado en trabajar en Kafka en sí mismo, busca nuevas oportunidades o simplemente tiene curiosidad, asegúrese de enviarme un mensaje en Twitter y compartiré todas las excelentes ventajas que se obtienen al trabajar en una empresa del área de la bahía.