Colas de mensajes en la práctica
RabbitMQ, Redis Streams o Kafka — los patrones de mensajería detrás de sistemas desacoplados y resilientes, y cuándo conviene usar cada herramienta.

Las colas de mensajes son la columna vertebral de los sistemas distribuidos. Desacoplan a los productores de los consumidores, absorben picos de tráfico y permiten una comunicación confiable entre servicios que no necesitan estar en línea al mismo tiempo. Pero elegir entre RabbitMQ, Kafka, Redis Streams o SQS sin entender los patrones subyacentes lleva a soluciones sobrediseñadas.
Punto a punto vs. Pub/Sub
Todo patrón de mensajería entra en una de dos categorías.
// Point-to-point: one message, one consumer
// Use case: task distribution (send email, process payment)
// Each message is processed by exactly ONE worker
// Pub/Sub: one message, many consumers
// Use case: event broadcasting (order placed → notify analytics, inventory, email)
// Each message is delivered to ALL subscribers| Patrón | Entrega | Consumidores | Ejemplo |
|---|---|---|---|
| Punto a punto | Un consumidor por mensaje | Workers en competencia | Colas de trabajo, distribución de tareas |
| Pub/Sub | Todos los suscriptores | Consumidores independientes | Difusión de eventos, notificaciones |
| Fan-out | Todos los consumidores, cada uno recibe una copia | Procesamiento independiente | Sincronización entre múltiples sistemas |
Cuándo necesitas una cola de mensajes
No toda interacción entre servicios necesita una cola. Las llamadas HTTP directas funcionan bien cuando:
- La respuesta se necesita de inmediato
- Ambos servicios deben estar disponibles
- La falla debe ser visible para quien hace la llamada
Las colas aportan valor cuando:
- El trabajo puede posponerse
- Los servicios tienen requisitos de disponibilidad distintos
- El tráfico es irregular y necesita amortiguación
// ❌ Synchronous chain — one failure breaks everything
app.post("/api/orders", async (req, res) => {
const order = await createOrder(req.body);
await inventoryService.reserve(order.items); // If this fails...
await paymentService.charge(order.total); // ...none of this runs
await emailService.sendConfirmation(order); // ...the user sees an error
await analyticsService.trackPurchase(order);
res.json(order);
});
// ✅ Async event — order is placed, downstream services react independently
app.post("/api/orders", async (req, res) => {
const order = await createOrder(req.body);
await messageQueue.publish("order.placed", {
orderId: order.id,
items: order.items,
total: order.total,
customerId: order.customerId,
});
res.json(order);
});
// Each service consumes the event independently
// inventory-service listens to "order.placed"
// payment-service listens to "order.placed"
// email-service listens to "order.placed"
// analytics-service listens to "order.placed"Garantías de entrega
Los sistemas de mensajería ofrecen distintos niveles de confiabilidad en la entrega.
// At-most-once: fire and forget
// Message may be lost, but never duplicated
// Use for: metrics, logging, non-critical notifications
// At-least-once: guaranteed delivery, possible duplicates
// Consumer MUST be idempotent
// Use for: most business events (payments, emails, orders)
// Exactly-once: no loss, no duplicates (very expensive)
// Requires distributed transactions or deduplication
// Use for: financial transactions (or use at-least-once + idempotency)En la práctica, at-least-once con consumidores idempotentes es la opción correcta por defecto. La entrega exactly-once es una garantía teórica extremadamente costosa de implementar correctamente.
RabbitMQ: broker de mensajería tradicional
RabbitMQ se destaca en enrutamiento, confirmación de mensajes y colas punto a punto.
import amqp from "amqplib";
// Producer
async function publishOrderEvent(order: Order) {
const connection = await amqp.connect(process.env.RABBITMQ_URL!);
const channel = await connection.createChannel();
await channel.assertExchange("orders", "topic", { durable: true });
channel.publish(
"orders",
"order.placed",
Buffer.from(JSON.stringify(order)),
{ persistent: true }, // Survive broker restarts
);
}
// Consumer
async function startOrderConsumer() {
const connection = await amqp.connect(process.env.RABBITMQ_URL!);
const channel = await connection.createChannel();
await channel.assertExchange("orders", "topic", { durable: true });
const queue = await channel.assertQueue("inventory-service", {
durable: true,
});
await channel.bindQueue(queue.queue, "orders", "order.placed");
channel.prefetch(10); // Process 10 messages at a time
channel.consume(queue.queue, async (msg) => {
if (!msg) return;
try {
const order = JSON.parse(msg.content.toString());
await reserveInventory(order);
channel.ack(msg); // Acknowledge successful processing
} catch (error) {
channel.nack(msg, false, true); // Requeue on failure
}
});
}Kafka: streaming de eventos
Kafka no es una cola tradicional, sino un registro de confirmaciones distribuido. Los mensajes se persisten y pueden reproducirse.
import { Kafka } from "kafkajs";
const kafka = new Kafka({
clientId: "order-service",
brokers: [process.env.KAFKA_BROKER!],
});
// Producer
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: "orders",
messages: [
{
key: order.id, // Partition by order ID for ordering guarantees
value: JSON.stringify({ event: "order.placed", data: order }),
},
],
});
// Consumer group — messages distributed among group members
const consumer = kafka.consumer({ groupId: "inventory-service" });
await consumer.connect();
await consumer.subscribe({ topic: "orders", fromBeginning: false });
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
const event = JSON.parse(message.value!.toString());
if (event.event === "order.placed") {
await reserveInventory(event.data);
}
},
});Cómo elegir la herramienta adecuada
| Característica | RabbitMQ | Kafka | Redis Streams | SQS |
|---|---|---|---|---|
| Enrutamiento | Avanzado (exchanges, bindings) | Topics + particiones | Simple | Básico |
| Reproducción de mensajes | No (consumido = perdido) | Sí (log persistente) | Limitada | No |
| Throughput | ~50K msg/s | ~1M msg/s | ~100K msg/s | ~3K msg/s |
| Orden | Por cola | Por partición | Por stream | Solo colas FIFO |
| Complejidad operativa | Media | Alta | Baja (si ya usas Redis) | Ninguna (administrado) |
Usa RabbitMQ para enrutamiento complejo, colas con prioridad y distribución tradicional de tareas. Usa Kafka para streaming de eventos de alto throughput con capacidad de reproducción. Usa Redis Streams para colas livianas cuando ya tienes Redis. Usa SQS cuando quieras cero sobrecarga operativa en AWS.
Puntos clave
- No toda llamada entre servicios necesita una cola — úsalas para trabajos diferibles, independientes o con tráfico irregular
- At-least-once + consumidores idempotentes es el estándar práctico por defecto para una mensajería confiable
- RabbitMQ para enrutamiento, Kafka para streaming de eventos de alto throughput, Redis Streams para colas livianas
- Confirma siempre los mensajes de forma explícita — la confirmación automática arriesga la pérdida de mensajes ante fallas del consumidor
- Particiona y usa como clave el ID de la entidad para garantizar el orden dentro de los eventos de una misma entidad


