Cómo usar RabbitMQ sin liarla: colas, exchanges y reintentos en sistemas reales

Hay un momento en la vida de todo backend en el que piensas:

“Vale, ya he procesado la request… pero esto (enviar un mail, generar un informe, llamar a un sevicio externo) no lo quiero hacer ahora, ni aquí, ni bloquear a nadie.”

Y ahí, amigui, suele aparecer RabbitMQ. Es cierto que la buena gente de Codely dicen que se puede hacer un sistema de colas en Postgres y tienen razón. Solo que hay veces que queremos un pasito más y es cuando RabbitMQ nos viene como anillo al dedo.

Añadir RabbitMQ, no es una bala de plata que va a solucionar todo nuestro legacy y todo va a ir rapidísimo si lo enviamos a la cola. Sino tenemos que ver RabbitMQ como una herramienta muy bien afinada para resolver problemas muy concretos: desacoplar, absorber picos, reintentar sin liarla, y dormir un poco mejor.

Este post no va de teoría académica ni de dibujitos con flechas infinitas. Va de algunos ejemplos que pueden servir de inspiración sobre cómo sacarle partido a RabbitMQ en la vida real, entendiendo bien cuatro conceptos clave y usándolos con cabeza. Por cierto si quieres probar tu arquitectura de colas échale un ojo a https://tryrabbitmq.com/ porque sirve para poder probar infinidad de casos


Primero lo importante: qué piezas tiene RabbitMQ

Si entendemos esto, el resto encaja solo.

Exchanges: el cerebro del tinglao

Un exchange no guarda mensajes es el punto de entrada, decide qué hacer con ellos. Cuando publicas un mensaje, siempre va a un exchange. Nunca directamente a una cola.
Según el tipo de exchange, RabbitMQ decide a qué cola (o colas) manda el mensaje:

  • Fanout → a todas las colas conectadas
  • Direct → a una cola concreta según una clave
  • Topic → a varias colas según patrones

Piensa en el exchange como la pieza que reparte los mensajes/eventos.


Queues: donde esperan los mensajes

La queue es fácil de entender: es donde el mensaje se queda esperando a que alguien lo consuma.

Cosas importantes de las colas:

  • Los mensajes se procesan en orden
  • Pueden tener uno o varios consumidores
  • Pueden ser durables (o no)

Y aquí ya empezamos a tomar decisiones que importan. Dependiendo de si vamos a tener mensajes durables, si la cola necesita recibir un ACK para indicar que el mensaje se ha procesado correctamente, cuanto tiempo espera la cola el ACK ,… todo esto hace que las posiblidades sean muchísimas pero que tengamos que pensar bien que es que necesitamos.


Bindings: el pegamento

Un binding conecta una cola con un exchange. Y aquí es donde defines la regla:

“Este tipo de mensaje va a esta cola”.

En exchanges direct y topic, el binding incluye una routing key o un pattern.


ACK: la diferencia entre fiabilidad y caos

Ya lo comentaba antes, cuando un consumidor recibe un mensaje, RabbitMQ espera una respuesta:

  • ACK → todo bien, bórralo
  • NACK / reject → algo ha ido mal

Hasta que no hay ACK, el mensaje no se elimina.

Esto es clave para:

  • Reintentos
  • Evitar pérdida de mensajes
  • Poder reiniciar consumidores sin miedo

Si te saltas los ACK puedes volver a procesar un evento otra vez. Imagina que el evento es enviar un email a un usuario, si el consumidor no envia el ACK a rabbit ese mensaje se va a quedar dando vueltas y enviando emails durante mucho tiempo.


Durability: sobrevivir a reinicios

Si no marcas bien esto, RabbitMQ funciona… hasta que se cae.

Para que los mensajes sobrevivan a un restart necesitas:

  • Exchange durable
  • Queue durable
  • Mensajes marcados como persistent

Si falla una de las tres: sorpresa.


Dead Letter Queues: el cementerio útil

Una DLQ (Dead Letter Queue) es donde acaban los mensajes que:

  • Han fallado demasiadas veces
  • Han expirado (TTL)
  • Han sido rechazados

No es un basurero. Es una herramienta de observabilidad y control.


Hagamos un resumen:

Producer

  • La aplicación que publica mensajes
  • Nunca habla directamente con una cola
  • Solo conoce exchanges, routing keys y el payload que va a enviar

Exchange

  • El cerebro del sistema
  • Decide a qué cola(s) va cada mensaje
  • Tipos principales:
    • Direct: routing exacto, un mensaje → una cola
    • Fanout: broadcast, un mensaje → todas las colas
    • Topic: routing por patrones (*, #)

Queue

  • Donde los mensajes esperan a ser procesados
  • FIFO (en general)
  • Puede tener uno o varios consumidores
  • Puede ser durable o no

Binding

  • La regla que conecta un exchange con una cola
  • Define cuándo un mensaje debe ir a esa cola
  • Usa routing keys o patterns (según el exchange)

Routing Key

  • Metadato que acompaña al mensaje
  • El exchange la usa para decidir el routing
  • Es semántica, no técnica (order.created, email.send)

Consumer

  • El proceso que lee y procesa mensajes de una cola
  • Puede haber varios por cola (competición)
  • Su velocidad define el throughput real

ACK / NACK

  • Confirmación explícita del consumidor
  • ACK: mensaje procesado correctamente
  • NACK / reject: fallo (con o sin requeue)
  • Sin ACK, el mensaje no desaparece

Durability

  • Define si exchanges, colas y mensajes sobreviven a reinicios
  • Para no perder mensajes necesitas:
    • Exchange durable
    • Queue durable
    • Mensajes persistentes

Prefetch Count

  • Cuántos mensajes puede recibir un consumer sin hacer ACK
  • Controla la presión y el reparto del trabajo
  • Clave para evitar consumidores saturados

Dead Letter Queue (DLQ)

  • Cola donde acaban los mensajes problemáticos
  • Herramienta de debugging y observabilidad
  • Nunca debería crecer en silencio

TTL (Time To Live)

  • Tiempo máximo de vida de un mensaje o cola
  • Muy usado para retries y parking de mensajes

Retry / Parking Queues

  • Colas intermedias con TTL
  • Permiten reintentos retrasados sin lógica en el código
  • Se apoyan en DLX + TTL

Ahora sí: casos de uso reales

Aquí es donde RabbitMQ empieza a brillar.


Caso 1: “esto lo quiero hacer en segundo plano”

Ejemplo clásico: enviar emails, generar PDFs, redimensionar imágenes, Llamar a APIs lentas o inestables. Si hacemos algo de esto en una request hacemos al usuario esperar, google se enfada, tenemos que tener cuidado con los timeouts… y además, mezclamos responsabilidades: servir tráfico y hacer trabajo pesado.

Solución con RabbitMQ

  • Exchange: Direct
  • Cola: email.send
    • Durable
  • Producer publica el evento
  • Worker consume y ejecuta el trabajo

Aquí el producer no sabe quién lo va a procesar ni cuándo. Y eso es una bendición.

Ventajas son claras: La request del usuario vuela, Si el worker cae, el mensaje sigue ah, Puedes escalar consumidores sin tocar el código


Caso 2: un evento, muchos interesados

Ejemplo clasico “Un usuario se registra y varias cosas deben reaccionar.” imaginemos que son: Enviar email de bienvenida, crear métricas, notificar al CRM, emitir eventos internos

Si hacemos estas llamadas directas entre servicios: Creamos acoplamiento, cada nuevo caso implica tocar código existente un fallo en uno de estos servicios puede romper la petición

Solución con RabbitMQ

  • Exchange: Fanout
  • Una cola por consumidor

Cada servicio puede consumir a su ritmo, si algo falla se trata ese fallo de forma aislada, puede reiniciarse un consumido sin que haya problemas en el resto

Esto escala organizativamente, no solo técnicamente.


Caso 3: routing más fino con topics

No todos los consumidores quieren todo. producimos eventos cuando pasan cosas, pero un solo consumidor puede consumir varios eventos distintos. Imagina que el sistema de pedidos quiere reaccionar cuando para algo a un pedido, y tenemos esto eventos:

  • order.created
  • order.cancelled
  • order.shipped
  • order.refunded

Solución con RabbitMQ

  • Exchange: Topic
  • Bindings flexibles:
    • order.*
    • order.created
    • order.#

Esto te permite:

  • Añadir nuevos consumidores sin tocar producers
  • Evolucionar el sistema sin miedo
  • Tener eventos semánticos y expresivos

Caso 4: reintentos bien hechos (sin bucles infinitos)

Aquí entra en juego el parking de mensajes. Imagina que tenemos que hacer peticiones http a un sistema inestable, que no siempre está disponible porque puede que: un proveedor externo está caído, hay un timeout puntual, el sistema dependiente está desplegando

Reintentar inmediatamente suele empeorar las cosas.

La solución: colas con TTL + DLQ

Estrategia típica:

  1. El mensaje falla
  2. Se rechaza (NACK)
  3. Se envía a una cola de retry con TTL (ej: 5 minutos)
  4. Al expirar, RabbitMQ lo mueve automáticamente a la cola original
  5. Se reintenta

Podemos tener varias colas de retries: retry-30s, retry-5m, retry-30m, dead-letter-final,

Es como tener un cronjob pero que solo funciona cuando hay mensajes, no necesitamos sleeps para que esperar activamente y la lógica en el código es más simple porque hemos delegado los reintentos a RabbitMQ. El broker hace el trabajo sucio por nosotros.


Monitoring: si no lo miras, no existe

RabbitMQ sin monitoring es una bomba de relojería. Cosas mínimas que tenemos que vigilar son

  • Número de mensajes en cola
  • Tasa de consumo vs publicación
  • Mensajes en DLQ
  • Consumers conectados

Red flags claras:

  • Colas creciendo sin parar
  • DLQs con mensajes antiguos
  • Consumers que desaparecen

La UI de RabbitMQ ya da mucha información. Si no la miras, vas tarde.


Para qué sí y para qué no

Lo principal es definir bien quién publica y quién consume, a mi me sirve mucho tener diagramas de event-storming, aunque con tener un Miró o similar con tarjetas de evento que sale, consumidor puede ser suficiente. Observa las colas, no las ignores

  • Trabajo asíncrono
  • Integraciones
  • Desacoplar sistemas
  • Reintentos controlados

No

  • Streaming masivo
  • Replay infinito de eventos
  • Sustituir una base de datos

Anti‑patrones: cómo pegarte tiros en el pie con RabbitMQ

Porque sí, RabbitMQ funciona… hasta que lo usamos mal y nos volvemos todos

1. La cola única para gobernarlos a todos

“Creamos una cola y metemos ahí todo.”

Error clásico. Al principio pensamos que con un tipo de consumidor podemos procesar todo, en parte es cierto, el problema viene cuando cada tipo de mensaje tarda un tiempo distinto en procesarse y los consumidores se quedan consumiendo los más lentos. Todavía podemos ir a peor cuando a la dead letter van mensajes y queremos reprocesar solo algunos, no todos,…

Resumen:

  • Mensajes críticos bloqueados por trabajos pesados
  • Imposible priorizar
  • Debugging infernal
  • Reprocesar solo algunos mensajes se vuelve un horror

Solución: colas por responsabilidad, no por comodidad.


2. Reintentos infinitos sin DLQ

Si un mensaje falla 1.000 veces, no es mala suerte: es un bug.

Sin DLQ:

  • Saturas el sistema
  • Ocultas errores reales
  • Te enteras tarde

Solución: retries limitados + DLQ observable.


3. Consumers que hacen demasiadas cosas

Un consumidor que:

  • Llama a 3 APIs
  • Escribe en BD
  • Hace lógica de negocio compleja

…es una bomba de relojería.

Solución: consumidores tontos y pequeños. Orquestamos con eventos, no con lógica spaghetti.


Conclusión: menos bloqueo, más fluidez

RabbitMQ no elimina el trabajo pesado, pero lo organiza para que tu arquitectura sea resiliente. Desacoplar procesos nos permite:

  • Fallar sin romperlo todo
  • Escalar por partes
  • Evolucionar sin miedo

Mi consejo es: no intentes migrar todo mañana. Usa https://tryrabbitmq.com/ para hacer distintos diagramas y probar distintas configuraciones. Empieza con lo más simple posible, ya podremos crear cientos de colas.

Empieza por sacar el envío de emails, los PDFs o las integraciones externas fuera del flujo principal. Verás cómo la latencia baja

Y ahora mójate: ¿qué parte de tu monolito te está pidiendo a gritos, entre errores 500, que la conviertas en asíncrona?

Comenta la entrada

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.

Jesús López

Soy un Ingeniero en Informática y apasionado de la programación. Me gusta disfrutar de mi familia, viajar y perdernos paseando.  Me mola programar, hacer tests y refactorizar código . Practico Test Driven Development (TDD) y me lo paso bien con el legacy codeLeer más

Sígueme en: