Una cola de mensajes es un sistema que permite una comunicación asíncrona entre diferentes servicios en el cual un mediador (llamado broker) almacena mensajes y se encarga de entregarlos a su destinatario. Esto permite desacoplar servicios y procesos pesados.

Es posible que la definición de cola de mensaje anterior no te diga mucho, pero si la estudiamos en detalle, verás la potencia real que se esconde en este tipo de comunicación. Las aplicaciones modernas se construyen a partir de pequeños sistemas independientes para facilitar su desarrollo, puesta en marcha y mantenimiento. Este tipo de desarrollo o arquitectura se denomina arquitectura de microservicios. El problema es que estos pequeños sistemas tienen que comunicarse entre ellos para sincronizarse o intercambiar mensajes.

Las colas de mensajes nos permiten una comunicación entre diferentes sistemas de forma asíncrona y nos aporta muchas ventajas como desacoplamiento, escalabilidad e independencia.

Qué es una cola de mensajes

Si alguna vez has trabajado en el mundo de los microservicios seguramente habrás escuchado el concepto de cola de mensajes (o, su traducción al inglés, messaging queues) o sus implementaciones como Apache Kafka o RabbitMQ.

Las colas de mensajes son sistemas intermediarios que se emplean para comunicar servicios de forma indirecta. Es decir, en vez de conectar los servicios entre sí, delegan en un broker que se encarga de recibir y enviar los mensajes a los destinatarios correctos. El paradigma se basa en que los productores de mensajes envían los mensajes a una cola o sistema intermedio, mientras que los consumidores (los sistemas que tienen que estar atentos a que lleguen mensajes) se quedan escuchando y reaccionan en cuanto llegue un mensaje. Las colas de mensajes se basan en un paradigma asíncrono porque ninguno de los componentes espera a que el otro haya procesado el mensaje.

Pero te voy a proponer un símil que te permitirá ver con más claridad qué es y cómo funciona una cola de mensajes. En vez de comunicar dos microservicios, vamos a plantear el ejemplo de que tú quieres comunicarte con un amigo para contarle algo importante. Tienes dos opciones: comunicarte de forma directa o indirecta.

La forma directa, que dentro del símil es la comunicación vía API, es ir a su casa y darle el mensaje en persona. Esta forma es la más rápida y fiable, pero tiene desventajas:

  • tu amigo tiene que estar en su casa en el momento en el que tú llegues para que pueda atenderte. Símil de que la API tiene que estar accesible y disponible.
  • Además, si hay otra persona hablando con tu amigo, tendrás que esperar a que acaben ellos para poder hablar tú con él. Dentro del símil, pico de tráfico.
  • Tu amigo y tú tenéis que hablar un lenguaje común para poder entenderos. Dentro del símil, la API tiene unos campos y una forma concreta de interactuar.

La forma indirecta es ir a tu oficina de Correos y darles el mensaje a ellos para que se encarguen de darle la información a tu amigo. No es un método que valga cuando la comunicación es muy importante, pero tiene más ventajas siempre que podamos prescindir de la máxima velocidad:

  • Da igual si está o no en casa, Correos la dejará en el buzón para cuando llegue a casa. Símil de que la comunicación existe aunque haya un nodo caído, tan pronto se levante, leerá el mensaje.
  • Si tu amigo está ocupado, ya leerá la carta cuando esté libre. Símil de que el mensaje no se pierde si el proceso está ocupado.
  • Es independiente el lenguaje que se emplee mientras el mensaje tenga toda la información necesaria.

Con estos ejemplos, creo que ya entiendes la diferencia entre una comunicación directa vía API y una comunicación indirecta con una cola de mensajes. Sin embargo, vamos a analizar los dos escenarios posibles para ver sus pros y sus contras concretos.

Conectar servicios mediante una API

Cuando necesitamos que dos sistemas independientes entre sí se comuniquen de forma inmediata, siempre es posible conectarlos mediante una API (o similar, como protobuf). Supón que el microservicio de productos necesita conectarse con el servicio de usuarios para conocer el rol del usuario que está haciendo la petición y, según sea su rol, ofrecer unos productos u otros. En este caso, necesitamos una conexión síncrona, pues el microservicio de productos no puede dar una respuesta hasta que el servicio de usuarios responda.

En este escenario, la API es una opción bien válida, pues internamente ambos servicios se comunican entre sí de forma rápida. Sin embargo, analicemos los contras que tiene este tipo de conexión:

  • Si el servicio de usuarios está caído, y, por tanto, su API no es accesible, el servicio de productos dará una respuesta no válida. Esto significa que utilizando API necesitamos que todos los servicios estén levantados y funcionando, pues de otra forma, la API no está disponible.
  • Si, de forma puntual, aumenta el tráfico que recibe la API, necesitarás escalar las instancias del microservicio afectado. Esto significa que si de repente el tráfico al microservicio de usuarios tiene un pico, necesitarás instalar más máquinas para poder atender estas peticiones. Si usas una aplicación monolítica, vete preparando la billetera.
  • Cuando toque actualizar la API, deberás tener en cuenta que la migración deberá ser paulatina para que todos los microservicios que llaman a nuestra API puedan ser actualizados (usando el formato o los campos nuevos).

Como ves, la conexión entre servicios usando una API es posible, pero tiene problemas asociados. A veces, para tener una respuesta síncrona y rápida, tenemos que estar dispuestos a asumir estos problemas. Sin embargo, si no requerimos una conexión síncrona, tenemos otra forma de conectarlos: la cola de mensajes.

Conectar servicios mediante una Cola de Mensajes

Las ventajas de usar una cola de mensajes como modelo de comunicación son varias, siempre que podamos prescindir del tiempo real o sincronicidad. La primera ventaja es que proporciona mayor tolerancia al fallo: es decir, si uno de los sistemas están caídos y no funcionan, los mensajes no se pierden. Mientras que con el modelo API no hay comunicación posible y el mensaje se pierde, empleando el modelo de la cola de mensajes el mensaje se queda esperando en el broker a que alguien los consuma. Cuando los sistemas que estaban caídos comiencen a funcionar podrán recuperar esos mensajes y procesarlos.

La siguiente ventaja es que los sistemas conectados con cola de mensajes están muy desacoplados. De hecho, el productor y consumidor no tienen por qué tener constancia de que existe el otro sistema. Cuando usamos una API, el productor tiene que ponerse en contacto con el consumidor e intercambiar mensajes usando el formato propio de la API. Sin embargo, cuando conectamos dos sistemas mediante una cola de mensajes, el productor envía el mensaje al broker y se olvida para siempre de dicho mensaje. Por otro lado, el consumidor se conectará al broker y se quedará esperando a que lleguen los mensajes. Pero fíjate que ambos sistemas jamás llegan a conectarse entre sí directamente. Esto permite que cada sistema funcione por separado.

Otra ventaja es que el único punto de conexión común que tienen es la cola, por lo que es posible diseñar los consumidores para que no acepten peticiones entrantes de Internet. Los sistemas consumidores se conectarán a la cola de mensajes y se quedarán esperando a que llegue un mensaje. Esto significa que no recibirán peticiones API desde el exterior, por lo que se pueden configurar para que ninguna conexión DESDE el exterior pueda entrar al sistema. Este hecho aumenta la seguridad del sistema y reduce la superficie de ataque.

Por último, una cola de mensajes ofrece una mayor escalabilidad. Mientras que el uso de una API fuerza a aumentar el número de instancias de los consumidores para poder hacer frente al tráfico y atender la petición, una cola de mensajes no fuerza a ello. Simplemente, si aumenta el tráfico, los consumidores irán sacando el trabajo a medida que puedan, pero el sistema no se vendrá abajo por el pico de tráfico. Es posible aumentar el número de consumidores ante un pico de trabajo, pero no requiere hacerlo deprisa y corriendo; es más flexible.

Adicionalmente, ya que puede que este punto sea o no una ventaja, por cómo está hecho un sistema de cola de mensajes, el primer mensaje en entrar siempre será el primer mensaje en ser procesado. Esto garantiza que los mensajes serán atendidos en el orden en el que han sido generados. Esta ventaja es muy útil cuando es importante el orden de los mensajes, como por ejemplo, la venta de entradas. El primero que llega es el primero que compra.

Casos de usos de una cola de mensajes: enviar emails y recortar imágenes

A las colas de mensajes se las puede ver como un sistema que permite “dejar para luego” un trabajo en concreto, es decir, en un primer momento realizamos un proceso crítico síncrono y dejamos los procesos pesados para “luego”. De esta forma, podemos separar el trabajo ligero y el trabajo pesado en dos partes: así que en definitiva, podemos pensar en una cola de mensajes como un “procesado pesado por lotes”.

Un ejemplo básico para ir entendiendo las colas de mensajes es el envío de un correo electrónico cuando un usuario se registra en nuestra web. Muchos desarrolladores no se dan cuenta, pero enviar un email es un proceso realmente lento en comparación con crear un usuario en la base de datos: primero hay que abrir una conexión TCP con el servidor de correo electrónico saliente. Posteriormente, tenemos que indicarle a este servidor los datos del mensaje (destinatario, remitente, contenido del texto, etc). Y finalmente, esperar a que el servidor de correo nos dé luz verde al envío. También tenemos que tener en cuenta que no es un proceso que requiera tiempo real o procesamiento inmediato (ya que podemos asumir un retraso de unos segundos). Por ello, cuando un usuario se registra en nuestra web, podemos ver el caso de uso como un proceso en dos partes: la primera es generar el usuario en la base de datos y devolver una respuesta rápidamente y, por otro lado, enviar el correo electrónico.

De esta forma, creamos el usuario en la base de datos igual que hacemos generalmente, pero, en vez de enviar un email desde el hilo principal, enviaremos un mensaje a la cola de mensajes indicando que debemos enviar un email de bienvenida al usuario. Una vez que el broker de la cola de mensajes nos confirme que ha recibido el mensaje, podemos responder con una web de bienvenida al usuario y olvidarnos de la petición.

De forma asíncrona, nuestro worker o consumidor de mensajes, recibirá una comunicación de la cola de mensajes indicando que debemos enviar un mensaje de bienvenida. Da igual cuánto tarde la tarea, el usuario no está esperando que finalice la petición para ver cambios en su navegador: tan pronto como un consumidor quede libre, asumirá el trabajo.

La ventaja de emplear una cola de mensajes para comunicar este tipo de mensajes es que es tolerante a fallos, pues si el sistema de envío está caído no bloquearemos el registro del usuario y, tan pronto se solucione, los mensajes serán enviados. De igual forma, ante un pico de tráfico, podemos añadir más componentes para enviar emails, pero el sistema no colapsará.

Otro de los ejemplos típicos de una cola de mensajes es el procesamiento de imágenes. Cuando un usuario sube una imagen de perfil o añade una imagen al artículo de un blog, seguramente necesitemos recortarla en diferentes formatos: pequeña, mediana, grande, en formato circular, etc. Sin el uso de colas de mensajes, tendríamos que hacer estos recortes durante la petición HTTP y, como es un proceso pesado, el usuario puede pensar que se ha roto y hacer un F5 repetidas veces o abandonar el proceso. Así mismo, si el microservicio que hace estos recortes está caído, la petición fallará y dañará la experiencia de usuario.

Utilizando una cola de mensajes, podemos “dejar para luego” esos recortes, pues la imagen que el usuario ha subido ya está en nuestro almacén. Tan pronto como un consumidor quede libre, podrá recortar y formatear esas imágenes, reemplazar las actuales y esperar otro trabajo. Además, si este microservicio está caído, tan pronto como esté disponible recuperará las órdenes de trabajo y no se perderá ningún mensaje.

Conclusión

Siempre que podamos prescindir de la inmediatez de respuesta, podemos usar una cola de mensajes, ya que nos ofrece muchas ventajas frente a una comunicación vía API. Existen en el mercado múltiples tipos de colas de mensajes según las necesidades de nuestra aplicación: RabbitMQ, Kafka, etc. Te invito a que investigues más sobre los diferentes brokers de mensajes y elijas el que mejor satisface tus requerimientos.

Espero que este artículo te haya enseñado qué son las colas de mensajes y por qué deberías usarlas en tu próximo proyecto o migrar el actual.

Además, te recuerdo que si usas PHP, tienes a tu disposición el componente Symfony Messenger, que te permite implementar colas de mensajes en tu aplicación de una forma sencilla y rápida.

En el siguiente artículo, veremos qué son y cuándo usar los protocolos de intercambio de mensajes AMQP, MQTT y STOMP.

¡Qué tengas un feliz coding!