Tecnologías para el procesamiento de eventos en arquitecturas de microservicios

12 marzo 2018

Tecnologías para el procesamiento de eventos en arquitecturas de microservicios

by Eugenio Concepción Cuevas

Introducción

Las arquitecturas basadas en microservicios son un subconjunto de las arquitecturas basadas en servicios (SBA). En cambio, las arquitecturas de microservicios no están necesariamente dirigidas por eventos (EDA). Mientras que, en una EDA, un agente es activado por la llegada de un evento, en la arquitectura microservicios un evento puede ser activado por una llamada explícita. Este procesamiento se puede abordar desde una perspectiva orientada a eventos, de forma que se saque partido de la tecnología para garantizar la necesaria consistencia de los datos. Otras situaciones en la que los eventos pueden formar parte del modelo de comunicación entre microservicios son la construcción de un modelo reactivo o la composición de capacidades mediante coreografía.

El modelo de eventos puede alcanzar distintos niveles de complejidad, admitiendo la existencia de varios receptores para un mismo mensaje y requiriendo en algunos casos el uso de sistemas de filtrado sofisticados basados en reglas. Cuando nos enfrentamos a la implantación de una arquitectura orientada a microservicios, es preciso considerar qué dificultades surgirán dentro de este tipo de arquitecturas:

  • La escalabilidad horizontal en servicios sin estado plantea un problema a la centralización de logs: garantizar la adecuada consistencia de los datos en su tratamiento. Este procesamiento se puede abordar desde una perspectiva orientada a eventos, de forma que se saque partido de la tecnología para garantizar la necesaria consistencia de los datos.
  • Otras situaciones en la que los eventos pueden formar parte del modelo de comunicación entre microservicios son la construcción de un modelo reactivo o la composición de capacidades mediante coreografía.

Por tecnología de procesamiento de eventos se entiende aquella que permite la captura de información operacional a través de eventos, su análisis, correlación compleja en base a reglas de negocio y el establecimiento de patrones de comportamiento.

Para este caso, se ha seleccionado un conjunto de tecnologías que se revisarán a continuación:

  • Apache Flume
  • Apache Kafka
  • Apache Spark

 

Apache Flume

Flume es una herramienta distribuida para la recolección, agregación y transmisión de grandes volúmenes de datos. Ofrece una arquitectura basada en la transmisión de datos por streaming altamente flexible y configurable pero a la vez simple.

Al tener un origen de datos configurable, se adapta prácticamente a cualquier tipo de situación:

  • Monitorización de logs
  • Descarga de información de redes sociales
  • Mensajes de correo electrónico

Apache Flume formaba parte originalmente del ecosistema Hadoop, aunque dispone de independencia. Los destinos de los datos son altamente configurables, de forma que el uso de Flume no va ligado exclusivamente al de HDFS o Hadoop.

La arquitectura de Flume está basada en agentes, que son procesos encargados de recolectar datos y enviarlos a su destino. A su vez, el flujo de datos viene determinado por una unidad denominada evento que está formado por datos y metadatos. Un agente Flume consta de tres componentes:

  • Sources
  • Sinks
  • Channels

Un source es una fuente de datos que queremos extraer. Es el encargado de recibir los datos, convertirlos en eventos y escribirlos en el channel. La ingestión se puede realizar con contenidos de un directorio (spooldir) con ejecuciones de comandos (exec) o recibiendo peticiones.

Un sink es al lugar donde queremos que vaya nuestro source para usarlo. Es el punto de recepción y entrega de los datos.  Esto puede incluir el log (logger), el HDFS (hdfs), el sistema local de archivos (file_roll).

El channel es el medio por el cual se distribuye el source al sink. Es un almacenamiento pasivo que recibe los eventos del source y espera a que el sink lo consuma, es decir que los sources añaden eventos mientras que los sinks los retiran. El mecanismo de almacenamiento puede ser la propia memoria (memory), una BBDD (jdbc) o un fichero en el disco duro (file).

 

Tipos de sources

  • Avro source: recibe los datos a través de un puerto mediante la utilidad de serialización de datos Avro. Los datos que recibe ya son eventos.
  • Thrift source: utiliza otra herramienta externa, Thrift, para ir recibiendo datos.
  • Exec source: ejecuta un comando Unix y espera a que genere datos de manera continua. Captura estos datos y los escribe en un channel. En caso de que el proceso Unix fallara y dejase de ejecutarse, el Exec source dejaría de emitir datos.
  • Netcat source: escucha a un puerto y convierte cada línea de texto que recibe por ese puerto en un evento. Recibe este nombre porqué su comportamiento es muy parecido al del comando de Unix netcat.
  • Sequence Generator Source: genera eventos de manera continua con un contador que empieza en 0 e incrementa de uno en uno. Es usado mayormente para realizar pruebas.
  • HTTP source: recibe eventos empleando HTTP, mediante los métodos GET y POST (se aconseja un uso experimental).
  • Custom Source: es un source implementado por el usuario y por lo tanto totalmente personalizable.

 

Tipos de channels

  • Memory channel: los eventos se almacenan en una cola en memoria con un tamaño máximo. Es el channel ideal para obtener rendimientos elevados pero no tiene demasiada tolerancia a errores ya que los datos se pierden al reiniciar el agente.
  • DBC channel: los eventos se almacenan en una base de datos permanente -es compatible con Derby-. Al usar un sistema de bases de datos no se obtiene un rendimiento tan elevado pero sí que permite la recuperación de eventos bajo fallos.
  • File channel: ofrece las mismas características que un channel JDBC pero almacena los eventos en ficheros temporales.
  • Custom channel: es un channel implementado por el usuario y totalmente personalizable.

 

Tipos de sink

  • HDFS sink: escribe los eventos en un fichero HDFS. Hay varios parámetros configurables: ruta del fichero, nombre y extensión, cuando se cierra cada fichero o cuántas réplicas debe tener mínimo cada bloque.
  • Logger sink: escribe los events en un log con el nivel de INFO.
  • Avro sink: envía los eventos al host y puertos indicados mediante Avro.
  • Thrift sink: como el anterior pero usando Thrift.
  • File roll sink: escribe los eventos en un fichero del sistema de ficheros local.
  • Null sink: descarta todos los eventos.
  • Custom sink: es un sink implementado por el usuario a la medida de sus necesidades.

 

Clientes Flume

Además de estos componentes del agente, Flume también ofrece una utilidad para facilitar la captura y generación de eventos llamado cliente.  Un cliente es un proceso que trabaja en el origen de datos, desde fuera del agente, y que se encarga de recolectar los datos para enviarlo al agente.

 

Apache Kafka

Es un sistema de mensajería desarrollado originalmente por LinkedIn en Scala, que está basado en el modelo de comunicación publish / subscribe. Kafka es persistente, escalable, replicado, tolerante a fallos, y con capacidad para atender volúmenes elevados de lecturas y escrituras procedentes de un número alto de clientes.

 

Características de Apache Kafka

  • Se pueden programar productores/consumidores en diferentes lenguajes: Java, Scala, Python, Ruby, C++, etc.
  • Escalable y tolerante a fallos.
  • Se puede utilizar para servicios de mensajería (tipo ActiveMQ o RabbitMQ), procesamiento de streams, web tracking, trazas operacionales, etc.

 

Modelo de operación de Apache Kafka

La comunicación entre los clientes y Kafka se realiza mediante un protocolo agnóstico que va sobre el protocolo HTTP. Esto facilita la creación e implementación de nuevos clientes realizados en cualquier lenguaje de programación. Es el mismo concepto seguido por otros protocolos de mensajería, como es el caso de AMQP. Funciona como un servicio de mensajería, categorizando los mensajes en topics. Los procesos que publican se denominan brokers y los consumidores, subscriptores. Utiliza un protocolo propio basado en TCP y Apache ZooKeeper para almacenar el estado de los brokers. Cada broker mantiene un conjunto de particiones (primaria y secundaria) de cada topic.

 

Componentes de Apache Kafka

  • Topic: Categorías en las que Kafka clasifica los mensajes.
  • Producer: Clientes conectados a Kafka responsables de publicar los mensajes. Estos mensajes son publicados sobre uno o varios topics.
  • Consumer: Clientes conectados a Kafka subcritos a uno o varios topics responsabes de consumir los mensajes.
  • Broker: Cada uno de los nodos de kafka que forman el cluster.

 

Arquitectura de Apache Kafka

Topics en Apache Kafka

Los topics en Kafka están particionados. Cada partición es una secuencia de mensajes inalterable, donde los mensajes son añadidos a una de las particiones según van llegando, y se les va asignando un número secuencial, llamado offset, que identifica de forma única a cada mensaje dentro de su partición. Todos los mensajes son mantenidos en las particiones para su consumo durante un periodo de tiempo configurado en a nivel de cluster. Una vez se supera ese periodo de tiempo los mensajes son eliminados para liberar espacio. El tamaño de estas particiones crece con el tiempo, pero no afecta al rendimiento de Kafka. El consumidor es responsable de mantener el offset del último mensaje consumido, no es mantenido por el cluster.

Esto permite que un consumidor pueda consumir los mensajes en cualquier orden.

Gracias a poder dividir un Topic en un log de varias particiones, nos permite escalar de forma horizontal, creando si fuera necesario más particiones en el resto de nodos del cluster.

 

Tolerancia a fallos

Kafka proporciona tolerancia a fallos replicando cada partición del log en un número configurable de servidores dentro del cluster. Gracias a este modelo, la caída de uno de los nodos no afecta al servicio. Uno de estos nodos actúa de leader mientras que el resto actúan de followers.  Este leader atiende todas las lecturas y escrituras de la partición mientras que los followers actúan como consumidores del leader replicando su contenido. Si el leader falla, automáticamente uno de los followers toma el rol del leader. En un cluster de Kafka cada servidor actúa como leader de una partición y como follower de una partición diferente, por lo que la carga del sistema se encuentra bien balanceada en todo el cluster.

 

Consumo de mensajes

Kafka proporciona un único nivel de abstración donde recoge el modelo de mensajería de queuing (colas) y publish-subscribe (editor-subscriptor). Los consumidores se inscriben o subscriben a un grupo consumidor. Un grupo consumidor tendrá 1 a N consumidores. Cada mensaje publicado en un topic es entregado solamente a uno de todos los consumidores que pertenecen a un mismo grupo consumidor. En el caso de que existan varios grupos de consumidores subscritos a un mismo topic, cada mensaje publicado en un topic es entregado a todos los grupos consumidores, y dentro de cada grupo consumidor a un solo consumidor. Si todos los consumidores tienen diferente grupo consumidor, cada mensaje es enviado a todos los consumidores. Si todos los consumidores tienen el mismo grupo consumidor cada mensaje es enviado a solo un consumidor del grupo de consumidores.

Normalmente los sistemas de mensajería como JMS guardan y van entregando los mensajes en el mismo orden en el que llegan. Sin embargo, una vez que llegan uno a uno al consumidor, el proceso del mensaje es asíncrono y por tanto el orden de salida del mensaje se puede llegar a perder.

 

Mecanismos de escalado

Para garantizar el orden en la entrega de los mensajes se asigna un único consumidor, lo que hace que se pierda la capacidad de escalado. Kafka aporta una solución al problema de tener réplicas sirviendo mensajes, permitiendo el escalado y la garantía de la entrega de los mensajes en el orden en el que llegan. Como se puede dividir un topic en varias particiones (cada partición podría estar replicada, para garantizar la tolerancia a fallos), es posible que el sistema escale. Los productores de mensajes pueden publicar los mensajes en base a una clave y por tanto son enviados a la misma partición.

Finalmente, a cada partición se subscribe un grupo consumidor, momento en el que solo un consumidor del grupo comienza a procesar los mensajes de esa partición, mientras se procesa también el resto de mensajes de las otras particiones consumidos por otros consumidores, logrando garantizar el orden de entrega de los mensajes, mientras se sigue balanceando y escalando el sistema.

Solamente en el caso de no poder dividir el topic en varias particiones, o de no poder enviar los mensajes a la misma partición usando una clave, reaparecería la limitación de un único consumidor y dejaría de escalar el sistema.

 

Casos de uso

  • Mensajería: Puede utilizarse como un agente de mensajeria o message bróker, como alternativa a RabbitMQ y ActiveMQ. Frente a estos, Kafka aporta mayor rendimiento, capacidad de particionado, replicación, persitencia y mejor tolerancia a fallos.
  • Monitorización y trazas: Estadísticas, monitorización, histórico de logs, seguimiento de la actividad Web, etc. Se puede utilizar Kafka para desacoplar todos estos sistemas de las aplicaciones que generan los logs. Las aplicaciones dejan los logs en Kafka, y todos los sistemas interesados en consumir y procesar estos logs pueden hacerlo en cualquier momento.
  • Sistema de motorización de sensores: Un sistema como Kafka podría utilizarse para guardar y almacenar información procedente de diferentes dispositivos, mientras otros sistemas consumidores podrían ir recogiendo toda esta información e ir enviándola a sistemas externos.

 

Apache Spark

Es una plataforma de código abierto para el procesamiento de datos en tiempo real, que puede ejecutarse y operarse con varios lenguajes distintos: Scala (empleado para desarrollar la plataforma), Python, R y Java. Spark presenta ventajas en el manejo de flujos de datos de entrada, con una velocidad muy por encima de las que ofrece Hadoop MapReduce. Spark ofrece un modelo de interactividad que mejora el modelo MapReduce incluyendo más operaciones: mappers, reducers, joins, groups by, filtros…

La principal ventaja de Spark es que persiste todas las operaciones sobre los datos en memoria, lo que es la clave de su buen rendimiento.

Apache Spark presenta una baja latencia computacional gracias al cacheo de los datos en memoria mediante datasets. Ejecuta algoritmos iterativos eficientes debido a que las sucesivas operaciones comparten los datos en memoria, o accediendo de manera repetida al mismo dataset.

 

Resilient Distributed Datasets

Los Resilient Distributed Datasets (RDD) de Spark, representan una colección inmutable y particionada de elementos sobre los que se puede operar de forma paralela.

Dependiendo del origen de los datos que contiene, se pueden diferenciar dos tipos de RDDs:

  • Colecciones paralelizadas basadas en colecciones de Scala.
  • Datasets de Hadoop creados a partir de ficheros almacenados en HDFS.

Son posibles dos operaciones bien diferenciadas sobre un RDD, dependiendo del resultado final:

  • Transformaciones
  • Acciones

Las transformaciones crean nuevos conjuntos de datos como, por ejemplo, la operación map() que pasa cada elemento por una determinada función y devuelve un nuevo RDD con el resultado.

Las acciones devuelven un valor al driver del clúster después de llevar a cabo una computación sobre el conjunto de datos. Un ejemplo de este tipo es la función reduce(), que agrega todos los elementos de un RDD mediante una función y devuelve el resultado.

 

SparkContext

Las aplicaciones para Spark se ejecutan como un grupo independiente de procesos en clústeres, coordinados por el objeto SparkContext. Es el contexto básico de Spark, desde donde se crean el resto de variables que maneja el framework. Sólo puede haber un SparkContext activo por JVM.

La configuración de SparkContext puede ser definida mediante un bundle específico llamado SparkConf.

 

Arquitectura de Apache Spark

SparkContext puede conectarse a gestores de clúster que son los encargados de asignar recursos en el sistema. Una vez conectados, Spark puede lanzar la creación de ejecutores o executors, encargados de la computación en los nodos del clúster. Las diferentes actividades realizadas por los ejecutores se agrupan en tasks o tareas.

Volver a la listaSiguiente artículo
arrow

Titulo

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.