Día 23 de 30Fase 6: Mensajería Asíncrona con Kafka, RabbitMQ, EDA y CQRS
RabbitMQ, Patrones de Mensajería y Comparativa Estratégica con Kafka
Exchanges, Queues, Bindings, Routing Keys, AMQP 0-9-1, Spring AMQP y Cuándo Elegir Cada Uno
Visión de conjunto. Sales entendiendo cómo funciona y cuándo usarlo; dominarlo pide más práctica en un proyecto real.
3.5h de estudio
5 conceptos
3 preguntas
1 katas
RabbitMQAMQPExchangesQueuesSpring AMQPDead Letter Queue
Al terminar este día sabrás 4
Enrutar mensajes con exchanges, bindings y routing keys en RabbitMQ.
Controlar la entrega con acknowledgements y prefetch.
Desviar mensajes fallidos a un Dead Letter Exchange y reintentar con retardo.
Elegir con argumentos entre RabbitMQ y Kafka para un caso concreto.
1
Modelo AMQP: Exchange, Queue, Binding y Routing Key
En RabbitMQ, los productores NUNCA envían mensajes directamente a una cola. Publican en un Exchange (buzón de enrutamiento). El Exchange usa las Bindings (reglas de enrutamiento) y la Routing Key del mensaje para decidir a qué Queue(s) entregar el mensaje. Tipos de Exchange: direct (routing key exacta), fanout (broadcast a todas las colas vinculadas), topic (patrón con comodines * y #), headers (por atributos del header).
En una línearabbitTemplate.convertAndSend("ordenes.exchange", "orden.creada", evento);
En AMQP los productores nunca publican directamente en una cola. Publican en un exchange con una routing key, y el exchange decide a qué colas copiar el mensaje según sus bindings (reglas que unen exchange y cola). Los consumidores leen de las colas.
Hay cuatro tipos de exchange. direct entrega a las colas cuyo binding coincide exactamente con la routing key. topic admite patrones con palabras separadas por puntos: * sustituye una palabra y # cero o más (tareas.*.completada, tareas.#). fanout ignora la clave y copia a todas las colas enlazadas. headers decide por cabeceras del mensaje.
Esta capa de enrutamiento en el broker es la gran fortaleza de RabbitMQ: puedes añadir un nuevo consumidor interesado solo en ciertos eventos creando una cola y un binding, sin tocar al productor.
Permite patrones de mensajería sofisticados (pub/sub, routing selectivo, broadcast) sin modificar el código del productor.
Publicar directamente a una cola usando el Exchange por defecto ("") y acoplar el productor al nombre exacto de la cola del consumidor.
Independiente de Java.
Se conecta conPatrón Pub/SubRoutingDesacoplamiento
2
Acknowledgements y Prefetch: Control de Flujo del Consumidor
Cuando un consumidor recibe un mensaje, debe confirmar (acknowledge) que lo ha procesado correctamente. En modo MANUAL, el consumidor llama a channel.basicAck() explícitamente tras procesar el mensaje con éxito. Si algo falla, puede rechazarlo con basicNack() para que RabbitMQ lo reencole o lo envíe a una Dead Letter Queue. El prefetch controla cuántos mensajes no confirmados puede tener el consumidor simultáneamente.
En una líneaspring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.prefetch=10
Prefetch y confirmación manualjava
// application.yml// spring:// rabbitmq:// listener:// simple:// acknowledge-mode: manual// prefetch: 10 # como máximo 10 mensajes sin confirmar por consumidor// concurrency: 2// max-concurrency: 8@RabbitListener(queues = "tareas.notificaciones")voidalRecibir(TareaEvento evento, Channel canal,
@Header(AmqpHeaders.DELIVERY_TAG)long tag)throws IOException {
try {
notificador.avisar(evento);
canal.basicAck(tag, false); // procesado: RabbitMQ lo borra
} catch (ServicioCaidoException e) {
canal.basicNack(tag, false, true); // transitorio: vuelve a la cola
} catch (Exception e) {
canal.basicNack(tag, false, false); // permanente: al DLX si está configurado
}
}
Cómo funciona por dentro
Un acknowledgement (ack) es la confirmación del consumidor de que procesó un mensaje; solo entonces RabbitMQ lo borra de la cola. Si el consumidor rechaza el mensaje (nack o reject), puede pedir que se reencole o que se descarte (o se desvíe a un DLX). Si la conexión se cae con mensajes sin confirmar, RabbitMQ los reencola automáticamente para otro consumidor.
El prefetch (QoS) limita cuántos mensajes sin confirmar puede tener un consumidor a la vez. Con prefetch 1, RabbitMQ no envía el siguiente hasta recibir el ack del anterior: reparto justo entre consumidores lentos y rápidos, pero poco rendimiento. Con valores altos, más rendimiento, pero un consumidor puede acaparar mensajes que otro procesaría antes. Spring usa 250 por defecto.
En Spring AMQP, el modo AUTO confirma cuando tu método termina sin excepción y hace nack si lanza una. El modo MANUAL te da el Channel para confirmar tú mismo, útil cuando el procesamiento continúa de forma asíncrona.
Previene la pérdida de mensajes si el consumidor falla a mitad de procesamiento y controla la presión sobre consumidores lentos.
Usar modo de confirmación automática (AUTO) en producción, donde el mensaje se elimina de la cola inmediatamente al ser entregado sin verificar que el procesamiento fue exitoso.
Idéntico en 17 y 21.
Se conecta conBackpressureFiabilidadControl de Concurrencia
3
Dead Letter Exchanges (DLX) en RabbitMQ
Cuando un mensaje es rechazado (nack sin requeue), ha expirado por TTL, o la cola destino ha alcanzado su límite de longitud, RabbitMQ redirige ese mensaje a un Dead Letter Exchange (DLX) vinculado a una cola de errores para inspección, alerta o reprocesamiento automático.
En una línea@Queue(value = "ordenes", arguments = @Argument(name = "x-dead-letter-exchange", value = "ordenes.dlx"))
Cola principal, cola de espera y aparcamientojava
@BeanQueueprincipal() {
return QueueBuilder.durable("pagos.procesar")
.deadLetterExchange("pagos.dlx")
.deadLetterRoutingKey("espera")
.build();
}
@BeanQueueespera() { // sin consumidores: solo esperareturn QueueBuilder.durable("pagos.espera")
.ttl(30_000) // 30 s
.deadLetterExchange("pagos") // al caducar, vuelve al exchange principal
.deadLetterRoutingKey("procesar")
.build();
}
@BeanQueueaparcados() {
return QueueBuilder.durable("pagos.aparcados").build();
}
@RabbitListener(queues = "pagos.procesar")voidprocesar(Message mensaje, Pago pago) {
longintentos= Optional.ofNullable(mensaje.getMessageProperties().getXDeathHeader())
.map(d -> d.stream().mapToLong(x -> (Long) x.get("count")).sum()).orElse(0L);
if (intentos >= 3) {
rabbit.convertAndSend("", "pagos.aparcados", pago); // revisión manualreturn;
}
pasarela.cobrar(pago); // si lanza excepción -> DLX -> espera 30 s
}
Cómo funciona por dentro
Un Dead Letter Exchange es un exchange normal al que RabbitMQ reenvía los mensajes «muertos» de una cola. Se configura en la cola con el argumento x-dead-letter-exchange (y opcionalmente x-dead-letter-routing-key). Un mensaje muere cuando se rechaza sin reencolar, cuando caduca su TTL o cuando la cola supera su longitud máxima.
RabbitMQ añade la cabecera x-death con el motivo, la cola de origen y cuántas veces ha muerto, lo que permite analizar fallos o decidir si reintentar.
Un patrón clásico es el reintento con retardo: la cola principal desvía los fallos a una cola de espera sin consumidores con un TTL (por ejemplo, 30 s), cuyo DLX devuelve el mensaje a la cola principal. Tras varios intentos (contando en x-death), se envía a una cola de aparcamiento para revisión manual.
Proporciona un mecanismo robusto de manejo de fallos sin perder mensajes ni bloquear colas principales.
No configurar un DLX y que los mensajes fallidos se reencoleen en bucle infinito, saturando la cola y consumiendo CPU.
Idéntico en 17 y 21.
Se conecta conResilienciaMonitorizaciónRetry Patterns
4
Cuándo Elegir RabbitMQ y Cuándo Elegir Kafka
RabbitMQ excelle en: colas de trabajo punto a punto, routing complejo con exchanges y bindings, mensajes con TTL y prioridades, confirmación individual de cada mensaje. Kafka excelle en: streaming de eventos de alto throughput, retención de histórico, reproducción de eventos, integración de múltiples consumidores independientes con Consumer Groups, y sistemas de Event Sourcing.
En una líneaKafka para flujo de eventos (pedidos, pagos, inventario). RabbitMQ para tareas puntuales (enviar email, generar PDF, notificación push).
Comparativa rápidatext
RabbitMQ Kafka
Modelo cola con enrutamiento log particionado
Tras consumir el mensaje se borra (ack) se conserva según retención
Releer el pasado no sí (mover offset)
Enrutamiento exchanges direct/topic/fanout por topic y clave de partición
Orden por cola (con 1 consumidor) por partición
Reintento por mensaje nack, DLX, TTL error handler, topics de reintento, DLT
Rendimiento típico decenas de miles msg/s cientos de miles a millones msg/s
Encaja con colas de trabajo, RPC, tareas event streaming, event sourcing, CDC
Cómo funciona por dentro
RabbitMQ es un broker inteligente con consumidores sencillos: enruta mensajes con reglas flexibles, entrega cada mensaje a un consumidor, lleva la cuenta de confirmaciones mensaje a mensaje y lo borra al confirmarse. Destaca en colas de trabajo, enrutamiento complejo, prioridades, TTL por mensaje y patrones petición-respuesta.
Kafka es un log con consumidores inteligentes: guarda todo durante un tiempo, cada consumidor lleva su posición y puede releer. Destaca en volumen muy alto, en que muchos sistemas consuman los mismos eventos de forma independiente, en reprocesar el histórico y en procesamiento de flujos (Kafka Streams, Flink). Ordena por partición.
Las preguntas que deciden: ¿necesito releer o que lleguen nuevos consumidores al histórico? (Kafka). ¿Necesito enrutamiento flexible, colas de tareas con reintentos por mensaje o prioridades? (RabbitMQ). ¿Cientos de miles de mensajes por segundo de forma sostenida? (Kafka). Muchos sistemas usan ambos: Kafka para eventos de dominio y RabbitMQ para colas de trabajo.
No existe una herramienta universalmente superior; la elección depende del patrón de comunicación dominante en tu arquitectura.
Usar Kafka para una simple cola de tareas en background (e.g. enviar emails) donde RabbitMQ sería mucho más sencillo y adecuado.
Independiente de Java.
Se conecta conArquitectura de MicroserviciosEvent SourcingColas de Trabajo
5
Spring AMQP: @RabbitListener y RabbitTemplate
Spring AMQP proporciona RabbitTemplate como productor de alto nivel y @RabbitListener como consumidor declarativo. Soporta conversión automática de mensajes con Jackson (Jackson2JsonMessageConverter), reintentos con RetryTemplate y configuración declarativa de exchanges, queues y bindings.
En una línea@RabbitListener(queues = "ordenes.creadas") public void procesarOrden(OrdenCreadaEvent evento) { ... }
Spring AMQP ofrece RabbitTemplate para publicar y @RabbitListener para consumir, con Spring Boot configurando conexión, listeners y conversión de mensajes. Por defecto los mensajes se serializan con Java, lo que acopla productor y consumidor a las mismas clases; lo recomendable es registrar un Jackson2JsonMessageConverter para enviar JSON.
convertAndSend(exchange, routingKey, objeto) convierte y publica. Para no perder mensajes por el camino, activa las confirmaciones del publicador (publisher-confirm-type: correlated): el broker confirma que recibió y enrutó el mensaje, y puedes reaccionar si no lo hizo.
Para reintentos simples en el consumidor, Spring Boot ofrece reintento con backoff en memoria (listener.simple.retry). Al agotar los intentos, con default-requeue-rejected: false, el mensaje se rechaza sin reencolar y acaba en el DLX de la cola.
Simplifica enormemente la integración con RabbitMQ eliminando la configuración manual del canal AMQP.
No registrar un MessageConverter de Jackson y recibir los mensajes como bytes crudos en vez de objetos Java deserializados.
Idéntico en 17 y 21.
Se conecta conSpring Boot Auto-ConfigurationSerializaciónRetry Pattern
Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.
Bloque 1: Modelo AMQP y Tipos de Exchange (0 - 25 min)
Qué estudiar. Exchanges direct, fanout, topic y headers. Bindings y Routing Keys.
Pregunta a responder. ¿Cómo lograr que un mismo evento llegue a 3 servicios diferentes usando un solo exchange?
Resultado esperado. Diagrama de arquitectura con exchange fanout y 3 colas vinculadas.
Bloque 2: Productor y Consumidor con Spring AMQP (25 - 50 min)
Pregunta a responder. ¿Cómo garantizar que un mensaje no se pierde si el consumidor falla?
Resultado esperado. Productor y consumidor funcionales con acknowledge manual.
Bloque 3: Dead Letter Queues y Comparativa Kafka vs RabbitMQ (50 - 80 min)
Qué estudiar. Configuración de DLX, criterios de selección entre Kafka y RabbitMQ.
Pregunta a responder. ¿En qué 3 escenarios es mejor Kafka y en qué 3 es mejor RabbitMQ?
Resultado esperado. Tabla de decisión Kafka vs RabbitMQ según caso de uso.
Cierre y Consolidación (80 - 90 min)
Qué estudiar. Repaso de las reglas de oro de mensajería asíncrona.
Pregunta a responder. ¿Por qué la idempotencia es obligatoria en consumidores de mensajes?
Resultado esperado. Notas consolidadas en la app.
Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.
Explícalo desde cero
Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.
El Exchange como director de orquesta: el productor solo sabe a quién enviar, no quién escucha
Lo que debes entender
El productor publica mensajes a un Exchange con una routing key. No sabe ni le importa cuántas colas están vinculadas ni cuántos consumidores existen. Esto permite añadir o quitar consumidores sin modificar el productor. Es desacoplamiento puro.
Analogía para contarlo
El Exchange es como la centralita de un hotel: tú llamas a "recepción" (routing key) y la centralita redirige la llamada a la habitación correcta. Si cambian la habitación, el sistema de enrutamiento se actualiza sin que tú tengas que saberlo.
Lo que suele explicarse mal
Mostrar ejemplos donde el productor envía directamente a una cola por nombre, acoplando productor y consumidor.
Señal de que aún no lo dominas
Si configuras el productor con el nombre exacto de la cola destino en lugar de con el nombre del exchange y la routing key.
Preguntas trampa
Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.
Nivel 1 (Básico)¿Qué ocurre si un consumidor de RabbitMQ con acknowledge manual falla al procesar un mensaje y la conexión se cierra antes de enviar el ack o nack?Ver respuesta
Respuesta razonada
Si la conexión o el canal se cierran con mensajes entregados pero sin confirmar, RabbitMQ los considera no procesados y los vuelve a poner en la cola, en su posición original si es posible. Se entregarán a otro consumidor (o al mismo cuando se reconecte) con la marca redelivered=true.
Es la garantía de entrega «al menos una vez»: no se pierde, pero puede procesarse dos veces si el consumidor llegó a hacer su trabajo (por ejemplo, enviar un correo) y cayó justo antes del ack. Por eso los consumidores deben ser idempotentes, por ejemplo registrando el id del mensaje procesado y descartando repetidos.
Qué está evaluando la pregunta
RabbitMQ detecta la desconexión, marca el mensaje como no confirmado y lo reencola para su entrega a otro consumidor disponible (o al mismo cuando se reconecte). Este es el mecanismo de fiabilidad fundamental de AMQP.
Nivel 2 (Intermedio)¿Por qué un exchange de tipo fanout ignora la routing key del mensaje y un exchange de tipo direct la requiere exacta?Ver respuesta
Respuesta razonada
Porque resuelven problemas distintos. Un fanout es una difusión: su propósito es copiar cada mensaje a todas las colas enlazadas, sin filtrar, así que la routing key no aporta nada y se ignora. Encaja con «todos los interesados deben enterarse», como invalidar cachés en todas las instancias.
Un direct enruta de forma selectiva: compara la routing key del mensaje con la clave de cada binding y solo entrega si coinciden exactamente. Encaja con distribuir trabajo por tipo (pdf, email). Si necesitas algo intermedio, con patrones, usas un exchange topic.
Qué está evaluando la pregunta
Un fanout distribuye el mensaje a TODAS las colas vinculadas al exchange sin importar la routing key. Un direct solo entrega el mensaje a las colas cuyo binding tenga una routing key que coincida EXACTAMENTE con la del mensaje.
Nivel 3 (Entrevista Senior)En un sistema de alta disponibilidad con RabbitMQ en cluster, ¿qué diferencia hay entre una cola clásica, una cola en espejo (mirrored queue) y una cola tipo Quorum Queue?Ver respuesta
Respuesta razonada
Una cola clásica vive en un único nodo: si ese nodo cae, la cola no está disponible y, si se pierde el disco, sus mensajes también. Las colas clásicas en espejo (mirrored) replicaban la cola en varios nodos, pero su algoritmo de sincronización tenía problemas conocidos de pérdida de datos y de rendimiento; quedaron obsoletas y se eliminaron en RabbitMQ 4.0.
Las Quorum Queues son la opción recomendada para datos importantes: se replican en varios nodos con el algoritmo de consenso Raft, un mensaje se confirma cuando lo tiene la mayoría de réplicas, y si cae el líder se elige otro sin perder mensajes confirmados. Son siempre durables y tienen límites de entrega integrados para mensajes envenenados. Su coste es algo más de latencia y de uso de disco.
Qué está evaluando la pregunta
Las colas clásicas viven en un solo nodo; si cae, los mensajes se pierden. Las mirrored queues replican la cola en múltiples nodos pero con un algoritmo de consenso frágil (deprecated). Las Quorum Queues (recomendadas desde RabbitMQ 3.8+) usan el algoritmo Raft para consenso distribuido robusto, garantizando que los mensajes sobrevivan a la caída de nodos sin inconsistencias.
Reto del tutor
Tu equipo discute si usar Kafka o RabbitMQ para un nuevo sistema de procesamiento de pagos. Los requisitos son: (1) Cada pago debe procesarse EXACTAMENTE una vez; (2) Si el servicio de pagos falla, el mensaje debe reenviarse automáticamente; (3) No se necesita historial de pagos; (4) Se procesan ~500 pagos por segundo en hora punta. ¿Qué herramienta recomendarías y por qué?
Lo que una respuesta suele tener bien
Reconoce que RabbitMQ encaja mejor para este caso de uso.
Lo que suele quedarse en la superficie
Decir "Kafka porque es más moderno".
El concepto de fondo que suele faltar
No evaluar que 500 msg/s es tráfico moderado, que no se necesita historial, y que el modelo de cola con ack/nack de RabbitMQ ofrece semántica más simple para "procesamiento exactamente una vez" en un patrón de cola de trabajo punto a punto.
Cómo lo respondería alguien senior en una entrevista
RabbitMQ es la opción más adecuada para este escenario: (1) El patrón es una cola de trabajo punto a punto, no un flujo de eventos multi-consumidor; (2) RabbitMQ ofrece acknowledge manual con requeue/DLX nativo perfecto para reintentos fiables; (3) 500 msg/s está dentro del rango cómodo de RabbitMQ sin necesitar la complejidad de Kafka; (4) "Exactamente una vez" se logra combinando acks manuales con procesamiento idempotente en la base de datos del servicio; (5) No necesitan retención de historial ni reproducción de eventos, que son las fortalezas de Kafka. Kafka añadiría complejidad operativa innecesaria para este patrón.
Proyecto «Pedidos» · paso 23 de 30
Cola de facturas con RabbitMQ
Repartir trabajo pesado entre varios consumidores sin perder tareas.
De qué va el proyecto
El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.
Qué hacer
Al pagar un pedido, encola una tarea GenerarFactura en RabbitMQ.
Crea un consumidor con acknowledgement manual que simula generar la factura.
Configura un Dead Letter Exchange para las facturas que fallan tres veces.
Está listo cuando
Con dos consumidores arrancados, las facturas se reparten entre ellos.
Si un consumidor se cae a mitad de una factura, otro la termina.
Katas: ejercicios cortos
Reglas de oro
Publica siempre a un Exchange con routing key, nunca directamente a una cola por nombre.
Usa acknowledge manual en producción para no perder mensajes ante fallos del consumidor.
Configura siempre un Dead Letter Exchange (DLX) para capturar mensajes fallidos.
Para tareas puntuales (emails, PDFs), RabbitMQ es más sencillo y adecuado que Kafka.
Para streaming de eventos con múltiples consumidores independientes, elige Kafka.