📨 Mensajería entre aplicaciones con RabbitMQ - Parte I

La mensajería aplicada al desarrollo de sistemas de software permite a nuestras aplicaciones escalar y comunicarse de forma consistente.

Las habilidades de escalamiento se deben a que los componentes de la aplicación ceden la responsabilidad de ciertas tareas a otros componentes más competentes para realizarlas. Estas tareas comúnmente corresponden a eventos o acciones que se llevan a cabo en el sistema, por ejemplo “usuario registrado” y son emitidos en forma de mensaje junto con la información correspondiente a dicho evento.

Un agente o broker es una aplicación que organiza la entrega correcta de mensajes entre el productor de estos eventos y su(s) consumidor(es).

RabbitMQ es un broker de mensajes que permite conectar aplicaciones creadas en diferentes lenguages de programación mediante el uso del protocolo AMQP 0.9.1. Sus características más importantes son:

  • Fiabilidad a través de sus características y opciones de configuración.
  • Enrutamiento flexible.
  • Soporte para clientes en múltiples lenguajes.
  • Una gran comunidad de usuarios.
  • Una interfaz gráfica de administración.
  • Un sistema de plugins que permite extender sus funcionalidades.

En esta serie de artículos nos enfocaremos en sus opciones de enrutamiento ejemplificadas mediante productores y consumidores escritos en librerías clientes de RabbitMQ.


Antes de comenzar abordaremos algunos conceptos clave de RabbitMQ.

Componentes de un modelo de aplicaciones con mensajería

En la figura anterior podemos observar los componentes que interactúan a través de RabbitMQ para generar un archivo PDF. Podemos distinguir en un extremo al productor, que en este caso es una aplicación web, y en el otro al consumidor, que es la aplicación que se encarga de generar el archivo PDF.

Además podemos observar otros componentes internos de RabbitMQ, descritos a continuación.

Conexiones y canales

Los clientes de mensajería se comunican con el broker mediante conexiones TCP/IP. Estas conexiones son multiplexadas, es decir, pueden contener varios canales a través de los cuales se transmiten los mensajes.

Los canales son el mecanismo principal para realizar operaciones como crear colas de mensajes, enviar mensajes, etc. Dado que pueden realizar varias operaciones es preferibe tener muchos canales que comparten la misma conexión a tener varias conexiones abiertas dados los costos asociados a ello.

La comunicación en cada canal está completamente separada de otros.

Las conexiones también soportan encriptación de datos mediante TLS.

Mensajes

Se trata de un conjunto de datos, expresado en arreglo de bytes, acompañado de atributos. Estos atributos pueden ser utilizados tanto por el broker como por el consumidor, algunos ejemplos son:

  • Tipo de contenido
  • Codificación del contenido
  • Prioridad
  • Indicación de fecha y hora
  • Expiración
  • Modo de entrega: persistente o no. Persistente indica que el mensaje se guarda en disco.
  • Clave de enrutamiento

Los atributos no usados por el broker son para fines de la aplicación y abiertos a cualquier interpretación.

Los datos enviados en el mensaje no son analizados por el broker, incluso se pueden publicar mensajes que constan sólo de atributos. Comúnmente los datos son serializados siguiendo un formato específico (por ejemplo JSON) que permite que las aplicaciones hagan el uso apropiado de la información.

Exchange

Es el que recibe los mensajes enviados desde el productor y se encarga de enrutarlos hacia una cola o hacia otro exchange para su almacenamiento y consumo. La forma en que decide hacia donde enrutarlos depende de las características de las conexiones entre en exchange y la cola, así como ciertos encabezados en los mensajes.

Existen cuatro tipos principales de exchange:

  • Direct: los mensajes son enrutados a una cola específica debido una igualdad en la clave de enrutamiento.
  • Topic: los mensajes van a una o varias colas comparando claves de enrutamiento usando comodines de substitución o igualdades (similar a direct).
  • Fanout: la clave de enrutamiento es ignorada y todas las colas pueden recibir los mensajes. Existen dos modalidades:
    1. cuando múltiples clientes declaran la misma cola el _exchan_ge se encarga de distribuir los mensajes usando round-robin
    2. cuando las colas tienen un nombre diferente todas reciben los mensajes
  • Headers: el enrutamiento está basado en uno o más atributos del mensaje (encabezados) en lugar de una clave de enrutamiento.

También tienen ciertas propiedades que indican su durabilidad:

  • Durable: que indica que sobrevivirá a los reinicios del sistema hasta que sea explícitamente borrado.
  • Temporary: que indica que vivirá hasta que el sistema se detenga.
  • Auto-delete: que indica que es removido una vez que el último cliente se desconecte.

Cabe mencionar que el broker crea siempre un exchange anónimo por defecto al cual todas las colas de mensajes se conectarán al momento de ser creadas. Este exchange es un exchange directo y usa el nombre de la cola como clave de enrutamiento para hacerle llegar mensajes.

Colas de mensajes (Queues)

Las colas de mensajes actúan como un búfer de almacenamiento para los mensajes que aún no han sido consumidos por un cliente. Una cola sólo puede ser utilizada después de ser declarada por un cliente y nunca es contactada directamente por un productor.

Las colas tienen propiedades que son definidas al momento de su declaración:

  • Nombre: que sirve como identificador. Si el nombre no es especificado el cliente se encarga de asignar uno aleatoriamente.
  • Durable: que indica si debe sobrevivir a un reinicio del broker. Los mensajes no sobreviven, sólo la cola.
  • Exclusive: que indica si debe ser asociada sólo con una conexión y ser borrada al cerrarse dicha conexión.
  • Auto-delete: que indica que la cola debe ser borrada cuando el último cliente se desconecte.
  • Time-to-live (TTL): que indica un tiempo de expiración de la cola.

Por defecto las colas almacenan los mensajes en memoria RAM para agilizar su entrega rápida. RabbitMQ elimina mensajes de una cola si esta almacena un número tal que comprometa los recursos de la RAM del nodo en que residen. A este proceso se le llama page out y durante su realización la cola no es capaz de procesar mensajes. Por esta razón una cola que contiene muchos mensajes puede tener un impacto negativo en el rendimiento del broker.

Existen también otros tipos de cola llamados lazy queues que almacenan sus mensajes en disco. RabbitMQ también soporta _streams_que son logs de mensajes agregados (append only, similar a Kafka) que también guarda los mensajes directamente en disco.

Enrutamientos (bindings)

Los enrutamientos se encargan de asociar un exchange con las colas de mensajes. Pueden estar definidos por el tipo de exchange, las claves de enrutamiento o por encabezados del mensaje.

Si un mensaje es recibido por un exchange y no existe una cola de mensajes asociada el mensaje puede seguir diferentes desenlaces: a) ser descartado, b) enviado a otro exchange (dead-letter) o c) retornado al productor. Cada caso depende de configuraciones específicas.

El uso de exchange y enrutamientos permite una gran variedad de aplicaciones que podrían ser difíciles de implementar si se tratase de publicar directamente a las colas de mensajes.

Confirmaciones y ACKs

Dada la naturaleza distribuida de los sistemas de mensajería los clientes del broker cuentan con confirmaciones tanto para el productor, cuando el mensaje es recibido en el broker, como del consumidor, informando al broker que el mensaje ha sido recibido o que ha sido procesado (momentos diferentes).

Una vez que el consumidor ha emitido la confirmación pertinente el mensaje es descartado de la cola. Si no se recibe dicha confirmación el broker puede decidir si debe reencolar el mensaje en la misma posición (definido en configuración) o reintentar de nuevo más tarde.

Los mecanismos de confirmación son opcionales y configurables al momento de definir los canales o los consumidores de mensajes.


Utilizando estos conceptos como base en los siguientes artículos ejemplificaremos flujos de mensaje entre distintas aplicaciones. Para ello utilizaremos las librerías disponibles para RabbitMQ.

El uso correcto del paradigma de mensajería en el diseño de sistemas permite una correcta aplicación de los principios de segregación y responsabilidad única.

También obtenemos patrones de respuesta predecibles y una mayor escalabilidad ya que somos capaces de responder a volúmenes de carga de trabajo o de número de peticiones incrementando el número de consumidores de los mensajes.

Con este patrón también vienen ciertos retos, principalmente si se trata de aplicaciones que requieren de sincronía para realizar sus tareas. De igual forma la responsabilidad del broker es enorme por lo cual debemos contar con mecanismos de recuperación inmediata, replicación o sharding si así se requiere.


https://www.rabbitmq.com/

https://www.rabbitmq.com/documentation.html

Comments:

Mensajería entre aplicaciones con RabbitMQ – Parte II. – Blog de Raymundo Vásquez Ruiz -

[…] el artículo anterior se describió la relevancia del paradigma de la mensajería en el desarrollo de sistemas de […]



Comentarios