Introducción a RabbitMQ

Agustin Bassi

Jul 25, 2021 ‧ 17 min estimados ‧ #rabbitmq #amqp #broker #queue

Contenido

Objetivos

RabbitMQ

AMQP 0-9-1

Exchanges

Default exchange

Direct exchange

Fanout exchange

Topic exchange

Header exchange

Queues

Bindings

Conexiones

Canales

Hosts virtuales

Atributos de mensajes

Consumidores

Reconocimientos de mensajes (ACK)

Rechazar mensajes (NACK)

Precarga de mensajes

Ecosistema RabbitMQ

Conclusiones

Bibliografía

Licencia

Objetivos

RabbitMQ es un broker muy popular que implementa la especificación AMQP 0-9-1 y otros protocolos. Lo que vas a ver en este documento son los siguientes temas:

RabbitMQ

RabbitMQ es un broker que implementa la especificación AMQP 0-9-1, y además de soportar el comportamiento estándar, posee extensiones a modo plugins donde se pueden interconectar diferentes protocolos como MQTT, MQTT sobre WebSockets, STOMP, HTTP, y más. Además cuenta con un administrador web que lo hace muy conveniente para configurarlo.

Dentro de RabbitMQ se utiliza una terminología bien marcada que define el comportamiento de cada una de las entidades involucradas  y que son las siguientes:

Lo bueno de tener una arquitectura distribuida que separa al productor del mensaje del consumidor, a través del broker, es que estas aplicaciones no necesitan estar en el mismo host, y pueden ubicarse en diferentes máquinas y arquitecturas, y ser implementadas con diferentes lenguajes de programación y múltiples protocolos de comunicación. Así mismo, una aplicación puede ser productora y consumidora a la vez.

La siguiente imagen ejemplifica cómo puede ser utilizado RabbitMQ para establecer un bus de mensajería mediante múltiples protocolos, que conecta diferentes dispositivos, aplicaciones y lenguajes. Esto lo convierte en una opción muy potente y flexible a la hora de diseñar aplicaciones.

AMQP 0-9-1

Tal como vimos en el documento de Introducción a AMQP, existen diferentes versiones del protocolo, las cuales fueron adoptadas de diferentes maneras según las distintas tecnologías y herramientas disponibles. RabbitMQ implementa la versión 0-9-1 del protocolo, y en esta sección veremos algunos detalles de esta versión.

Dentro del modelo AMQP 0-9-1, los productores envían mensajes hacia los exchanges indicando diferentes propiedades que son utilizadas por el broker para realizar el enrutamiento. Los exchanges distribuyen copias de los mensajes a las colas vinculadas utilizando diferentes criterios. Los criterios de asociación entre exchanges y colas se denominan binding, y cada tipo de exchange posee sus propias reglas para enrutar los mensajes hacia las colas. Finalmente, el broker envía los mensajes a los consumidores suscritos a las colas mediante una notificación push, o bien mediante un mecanismo de polling. Veamos en esta imagen este modelo simplificado.

Los exchanges, las colas y los bindings conforman colectivamente las entidades principales dentro de AMQP 0-9-1. El funcionamiento de las entidades puede ser programado libremente y se pueden implementar diferentes criterios de intercambio de mensajes dentro de una misma aplicación, lo que da gran flexibilidad para seleccionar los mecanismos más adecuados. Así mismo, las aplicaciones pueden ser productoras y consumidoras al mismo tiempo.

Exchanges

Los exchanges son las entidades donde los productores envían los mensajes. Su tarea es tomar un mensaje y enrutarlo a cero o más colas. El algoritmo de enrutamiento utilizado depende del tipo de exchange y de las propiedades del binding que los vincula.

Los exchanges AMQP 0-9-1 ofrecen cuatro tipos de exchange que son Direct Exchange, Fanout Exchange, Topic Exchange y Header Exchange. Además, RabbitMQ implementa un exchange llamado Default Exchange que es configurado por defecto en caso de no implementar explícitamente ninguno estándar.

Además del tipo, los exchanges se declaran con una serie de atributos. Los más importantes son el nombre, la durabilidad (que determina si sobreviven al reinicio del broker), la eliminación automática (si se elimina el exchange cuando la última cola asociada al mismo se libera) y argumentos opcionales utilizados por extensiones y funciones específicas del broker.

Veamos ahora los diferentes tipos de exchanges que soporta RabbitMQ.

Default exchange

El Default Exchange es un tipo de exchange predefinido por el broker. Es muy útil para aplicaciones simples, ya que cada cola que se crea se vincula automáticamente con una routing_key que es la misma que el nombre de la cola.

Por ejemplo, cuando se declara una cola con el nombre "system-logs", el broker RabbitMQ la vincula al default exchange utilizando "system-logs" como routing_key. Cuando un mensaje es publicado en el default exchange con la routing_key "system-logs", se enrutará a la cola "system-logs". El default exchange hace que lógicamente parezca posible entregar mensajes directamente a las colas, aunque técnicamente no es lo que está sucediendo. Por debajo, el default exchange es del tipo direct.

Direct exchange

Un direct exchange entrega mensajes a las colas según la routing_key con la que se publican los mensajes. Es ideal para el enrutamiento de unidifusión de mensajes, aunque también se pueden utilizar para el enrutamiento de multidifusión.

El mecanismo con un direct exchange funciona con una cola que se vincula al exchange con una routing_key K. Cuando un mensaje nuevo con la routing_key R llega al exchange, el exchange lo enruta a la cola si K = R.

Los direct exchange se suelen usar para distribuir tareas entre varios workers (instancias de la misma aplicación) de forma rotatoria, balanceando la carga entre los consumidores. En la siguiente imagen podemos ver un esquema gráfico de este exchange.

Fanout exchange

Un fanout exchange enruta los mensajes a todas las colas que están vinculadas a él y sin tomar en cuenta la routing_key del mensaje ni del binding. Si N colas están vinculadas a un fanout exchange, cuando se publica un nuevo mensaje, se entrega una copia del mensaje a las N colas.

Debido a que un fanout exchange entrega una copia de un mensaje a cada cola vinculada, tiene casos de uso de difusión masiva que pueden resultar muy útiles, tales como actualizar resultados de juegos en tiempo real a todos los participantes, difundir estadísticas sobre eventos deportivos minuto a minuto, transmitir configuraciones de dispositivos y servicios, chats grupales, entre otros.

Para que tengas un mejor entendimiento, en la siguiente figura podemos ver un esquema gráfico que representa el uso de un fanout exchange.

Topic exchange

Los topic exchange enrutan los mensajes a una o varias colas según la coincidencia entre la routing_key del mensaje y la routing_key con que se realizó el binding entre el exchange y las colas.

Se suele usar para implementar patrones de publicación/suscripción y para multidifusión, aunque tienen gran cantidad de casos de uso. Siempre que se debe enviar mensajes a consumidores que eligen selectivamente qué tipo de mensajes desean recibir, se debe considerar el uso de topic exchanges.

Los mensajes enviados a un topic exchange tienen una routing_key que son una lista de palabras delimitadas por puntos. Se puede implementar cualquier combinación de palabras siempre y cuando no superen los 255 bytes. Algunos ejemplos de routing_key válidas son "building1.room1.sensor3.temperature" o "chat5.user3.alert".

Para que los mensajes publicados en un topic exchange puedan llegar a una cola, la routing_key utilizada en el binding que los vincula debe tener el mismo formato que la de publicación del mensaje. Así mismo, hay caracteres especiales que pueden dar flexibilidad a este esquema. El caracter asterisco (*) puede sustituir exactamente una palabra, mientras que el caracter numeral (#) puede sustituir varias palabras.

En esta figura podemos ver un ejemplo de dos colas que se suscriben a un mismo exchange utilizando diferentes patrones, de manera que pueden recibir diferentes mensajes.

Header exchange

Un header exchange está diseñado para enrutar mensajes a través de varios atributos que se expresan en el encabezado del mensaje en vez de como routing_key. Un mensaje se considera coincidente si el valor del header es igual al valor especificado en el binding entre la cola y el exchange.

Es posible vincular una cola a un header exchange usando más de un atributo en el header, lo que permite implementar mecanismos más complejos de enrutamiento de los que vimos hasta ahora.

En esta imagen podemos ver un ejemplo de implementación, donde sobre el header del mensaje se envía el formato de un archivo, que puede utilizarse para ser recibido por una cola u otra.

Queues

Las colas son las entidades a las cuales los exchanges envían los mensajes - siempre y cuando exista un binding - y desde donde las aplicaciones los consumen. Antes de utilizar una cola es necesario declararla, y en la declaración se deben especificar sus propiedades asociadas.

En AMQP 0-9-1, las colas se pueden declarar como duraderas o transitorias. Los metadatos de una cola duradera se almacenan en el disco, mientras que los metadatos de una cola transitoria se almacenan en la memoria volátil. En los casos donde la durabilidad es importante, las aplicaciones deben usar colas duraderas y asegurarse que el publicador marque los mensajes como persistentes.

El tema de las queues es un factor fundamental a la hora de entender y utilizar RabbitMQ, por eso es conveniente que leas la documentación oficial sobre el tema en este link.

Bindings

Los bindings son las reglas que usan los exchanges para enrutar mensajes a las colas. Para indicar a un exchange E que enrute mensajes a una cola Q, Q debe estar vinculada a E mediante un binding.

Los bindings pueden tener asociados una routing_key que es utilizada por los exchanges default, direct y topic para enrutar los mensajes a las colas correspondientes. En el tipo de exchange fanout no se tiene en cuenta la routing_key, ya que cualquier mensaje publicado en esos exchange se envían a todas las colas. En el header exchange se utilizan las propiedades del header del mensaje para realizar el enrutamiento correspondiente.

Si un mensaje no se puede enrutar a ninguna cola - porque por ejemplo no hay bindings para el exchange en el que se publicó -, se puede descartar, ser devuelto al publicador, o bien publicarlo en otra cola, según los atributos con que el mensaje haya sido publicado.

Si bien puede parecer complejo, tener esta capa de indirección habilita escenarios de enrutamiento que son difíciles de implementar usando la publicación directamente en las colas. Además, elimina trabajo innecesario que los desarrolladores deberían realizar para desplegar escenarios más complejos de comunicación.

Conexiones

Dentro de la especificación AMQP 0-9-1, las conexiones son el canal físico sobre TCP que se utiliza para conectar al broker con los clientes. Las conexiones utilizan autenticación y se pueden proteger mediante TLS. Cuando una aplicación ya no necesita estar conectada al servidor, debe cerrar su conexión explícitamente con el broker - conocido como graceful shutdown - en lugar de cerrar abruptamente la conexión. Veamos algunos detalles sobre las conexiones.

Canales

Las conexiones AMQP 0-9-1 se multiplexan con canales que pueden considerarse como conexiones lógicas que comparten una única conexión TCP.

Cada operación realizada por un cliente ocurre en un canal. La comunicación en un canal en particular está completamente separada de la comunicación en otro canal. Cada método de protocolo tiene asociado un ID de canal que tanto el broker como los clientes usan para determinar para qué canal es el método.

Un canal solo existe dentro de una conexión. Cuando una conexión se cierra, también se cierran todos los canales que tiene asociados. Para las aplicaciones que utilizan múltiples subprocesos o threads, es común abrir un nuevo canal por hilo de ejecución, compartiendo entre ellos la misma conexión física.

Hosts virtuales

Es posible hacer que un solo broker maneje múltiples entornos aislados, donde se tienen grupos de usuarios, exchanges y colas por cada entorno. AMQP 0-9-1 incluye el concepto de hosts virtuales - vhosts -, similares a los hosts virtuales utilizados por algunos servidores web. Proporcionan entornos completamente separados donde viven las entidades AMQP. Los clientes deben especificar a qué vhosts se quieren conectar durante la negociación de la conexión.

Atributos de mensajes

Los mensajes en el modelo AMQP 0-9-1 tienen diferentes atributos. Algunos son definidos por la especificación AMQP 0-9-1 - liberando a los desarrolladores de pasarlos explícitamente - y otros son declarados por las aplicaciones. En esta imagen podemos ver algunos de ellos.

Los mensajes también tienen una carga útil - o payload - que los broker de AMQP tratan como datos opacos; es decir que el broker no inspecciona ni modifica el payload. Es común utilizar formatos de serialización como JSON, XML o MessagePack aunque también es posible que los mensajes solo contengan atributos y no contengan carga útil.

Los mensajes pueden publicarse como persistentes garantizando que los mensajes no se pierdan si el broker se reinicia. La publicación de mensajes persistentes afecta el rendimiento ya que el broker debe hacer operaciones de I/O para almacenar los mensajes. Así mismo, estas garantías dependen de las configuraciones específicas del broker para la persistencia, por lo que se debe hacer un análisis adecuado de los mecanismos para asegurar que los mensajes no se pierdan.

Consumidores

Almacenar mensajes en colas carecería de sentido si no hay aplicaciones puedan consumirlos. En AMQP 0-9-1, hay dos formas de que las aplicaciones hagan esto; una es mediante la suscripción para recibir ciertos mensajes - conocida como API push -, y la otra es mediante un mecanismo de polling donde los consumidores consultan periódicamente para chequear si hay nuevos datos. La primera de las opciones es la recomendada, ya que el polling es muy ineficiente y debe evitarse en la mayoría de los casos.

Mediante API push, las aplicaciones indican que quieren consumir mensajes de una cola en particular. Cuando lo hacen, decimos que registran un consumidor o, simplemente, se suscriben a una cola. Es posible tener más de un consumidor por cola o registrar un consumidor exclusivo que excluye a todos los demás consumidores de la cola mientras está consumiendo. En esta imagen podemos ver la secuencia desde que un publicador envía un mensaje con un nuevo resultado que es recibido por varios clientes a modo push.

Cada consumidor suscrito a una cola es identificado mediante un tag que el broker usa para tener un registro de los consumidores suscritos a las colas. Veamos ahora algunos mecanismos y tareas que realizan los consumidores.

Reconocimientos de mensajes (ACK)

Las aplicaciones consumidoras que reciben y procesan mensajes pueden fallar al procesarlos, ya sea porque ocurrió un error en el código y el programa termina inesperadamente, así como también por problemas de red. Bajo estos escenarios, la especificación AMQP 0-9-1 establece un mecanismo que brinda a los consumidores dos formas de reconocimiento de mensajes antes de ser eliminados de una cola. Por un lado, un consumidor le puede enviar al broker un ACK justo después de haber recibido un mensaje, o bien en otro momento que la aplicación decida.

La primera opción se denomina reconocimiento automático, mientras que la última se conoce como reconocimiento explícito. Con el modelo explícito, la aplicación elige cuándo es el momento de enviar un ACK. Puede ser justo después de recibir un mensaje, después de guardarlo en una base de datos o después de procesarlo por completo.

Si un consumidor termina su ejecución sin enviar un ACK, el broker lo volverá a entregar a otro consumidor o, si no hay ninguno disponible en ese momento, esperará hasta que al menos un consumidor esté registrado en la misma cola antes de intentar entregarlo nuevamente.

Rechazar mensajes (NACK)

De modo contrario al ACK, una aplicación puede indicarle al broker que el procesamiento ha fallado o que no se puede realizar en ese momento, rechazando explícitamente un mensaje. Al momento del rechazo de un mensaje, la aplicación puede indicarle al broker que lo descarte o lo vuelva a encolar.

Es importante que tengas en cuenta que si hay un solo consumidor de la cola, y el mensaje vuelve a ser re-encolado en vez de descartado, se puede formar un lazo infinito indeseado que impactará negativamente en el rendimiento y funcionamiento de la aplicación, ya que el mensaje será recibido por el mismo consumidor una y otra vez.

Ecosistema AMQP 0-9-1

El protocolo AMQP 0-9-1 tiene varios puntos de extensión, como por ejemplo los siguientes:

Estas características hacen que el modelo AMQP 0-9-1 sea aún más flexible y aplicable a una amplia gama de problemas. Así mismo, hay muchos clientes AMQP 0-9-1 para varios lenguajes y plataformas. Algunos se apegan más a la implementaciones de métodos AMQP y otros tienen características, métodos y abstracciones particulares. Algunos clientes son asincrónicos, algunos sincrónicos y otros admiten ambos modelos.

Conclusiones

A lo largo de este documento hicimos un recorrido general y bastante extenso sobre las características que identifican a RabbitMQ. Como habrás visto, es un protocolo realmente potente, que lleva a otro nivel las comunicaciones entre aplicaciones, ya sea de manera interna a una empresa como así también para interactuar con aplicaciones de terceros.

Este tipo de modelo, desplaza en el buen sentido las comunicaciones ad-hoc donde es necesario un know-how específico para llevar a cabo una implementación. Ante un panorama actual tan cambiante, se hace mandatorio conocer este tipo de tecnología que pueden ayudarte a llevar a un nivel de interoperabilidad garantizado el desarrollo de una aplicación.

Por supuesto que es la punta del iceberg, pero más allá de eso pudimos ver las partes más relevantes del protocolo. Para hacer un resumen, en este artículo vimos los siguiente temas:

Bibliografía

Licencia

Este material es distribuido bajo licencia Creative Commons BY-SA 4.0. Podés encontrar detalles sobre el uso del material en este link.