Modos de Exchange en RabbitMQ: Fanout, Direct y Topic

En el protocolo AMQP, la separación entre productor y consumidor es un principio fundamental. Los productores nunca envían mensajes directamente a una cola; en su lugar, publican en un exchange (intercambiador). Este componente recibe los mensajes y los redirige a las colas apropiadas según reglas predefinidas. Los consumidores, por su parte, leen exclusivamente desde las colas, sin conocer el origen del intercambiador.

RabbitMQ implementa cuatro tipos de exchange: fanout, direct, topic y header. Debido a que el modo header tiene un uso marginal en entornos productivos, este artículo se concentra en los tres primeros.

Exchange tipo Fanout

Cuando un exchange opera en modo fanout, cada mensaje recibido se replica hacia todas las colas que tengan un enlace (binding) activo con dicho exchange. No se evalúa ninguna clave de enrutamiento (routing key): la difusión es indiscriminada, similar a cómo funciona un broadcast en una red local.

Esta simplicidad hace que fanout sea el modo de reenvío más rápido disponible en RabbitMQ.

// Publicación con exchange Fanout
private static async Task EmitirNotificacion(OrdenCompra orden)
{
    using var conexion = CrearConexionRabbit();
    using var canal = conexion.CreateModel();

    canal.ExchangeDeclare("ordenes_fanout", ExchangeType.Fanout, durable: true);

    var cuerpo = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(orden));
    var propiedades = canal.CreateBasicProperties();
    propiedades.Persistent = true;

    canal.BasicPublish(
        exchange: "ordenes_fanout",
        routingKey: string.Empty,
        basicProperties: propiedades,
        body: cuerpo);
}

// Consumo con exchange Fanout
private static void EscucharNotificaciones()
{
    var conexion = CrearConexionRabbit();
    var canal = conexion.CreateModel();

    canal.ExchangeDeclare("ordenes_fanout", ExchangeType.Fanout, durable: true);

    var nombreCola = canal.QueueDeclare().QueueName;
    canal.QueueBind(nombreCola, "ordenes_fanout", routingKey: string.Empty);

    var consumidor = new EventingBasicConsumer(canal);
    consumidor.Received += (sender, args) =>
    {
        var mensaje = Encoding.UTF8.GetString(args.Body.Span);
        Console.WriteLine($"[Fanout] Recibido: {mensaje}");
        canal.BasicAck(args.DeliveryTag, multiple: false);
    };

    canal.BasicConsume(queue: nombreCola, autoAck: false, consumer: consumidor);
}

Exchange tipo Direct

En modo direct, el exchange enruta cada mensaje únicamente a las colas cuya clave de enrutamiento coincida exactamente con la especificada por el productor. Si ninguna cola coincide, el mensaje se descarta.

RabbitMQ incluye un exchange por defecto (sin nombre) que opera como direct. Cada cola creada se enlaza automáticamente a este exchange utilizando su propio nombre como clave de enrutamiento, lo que permite publicar directamente a una cola sin declarar un exchange explícito.

// Publicación con exchange Direct
private static void EnviarOrden(string claveRuta, DetalleOrden detalle)
{
    var factory = new ConnectionFactory { HostName = "localhost" };
    using var conexion = factory.CreateConnection();
    using var canal = conexion.CreateModel();

    canal.ExchangeDeclare("ordenes_direct", ExchangeType.Direct, durable: true);

    var datos = JsonSerializer.SerializeToUtf8Bytes(detalle);

    canal.BasicPublish(
        exchange: "ordenes_direct",
        routingKey: claveRuta,
        body: datos);

    Console.WriteLine($"Orden enviada con clave: {claveRuta}");
}

// Consumo con exchange Direct
private static void ProcesarOrdenes(string claveRuta)
{
    var factory = new ConnectionFactory { HostName = "localhost" };
    var conexion = factory.CreateConnection();
    var canal = conexion.CreateModel();

    canal.ExchangeDeclare("ordenes_direct", ExchangeType.Direct, durable: true);

    var cola = canal.QueueDeclare(queue: claveRuta, durable: true,
                                  exclusive: false, autoDelete: false);

    canal.QueueBind(cola.QueueName, "ordenes_direct", routingKey: claveRuta);

    var handler = new EventingBasicConsumer(canal);
    handler.Received += (_, ea) =>
    {
        var texto = Encoding.UTF8.GetString(ea.Body.ToArray());
        Console.WriteLine($"[Direct] Clave={claveRuta}: {texto}");
        canal.BasicAck(ea.DeliveryTag, false);
    };

    canal.BasicConsume(queue: cola.QueueName, autoAck: false, consumer: handler);
}

Exchange tipo Topic

El modo topic permite un enrutamiento flexible mediante coincidencia de patrones. Las colas se vinculan al exchange con una expresión de clave de enrutamiento que puede contener comodines:

  • * — coincide con exactamente una palabra entre puntos.
  • # — coincide con cero o más palbaras.

Por ejemplo, la expresión inventario.# empareja con inventario.productos.nuevos, mientras que inventario.* solo empareja con inventario.alertas.

// Publicación con exchange Topic
private static void PublicarEvento(string topico, string payload)
{
    using var conexion = EstablecerConexion();
    using var canal = conexion.CreateModel();

    canal.ExchangeDeclare("eventos_topic", ExchangeType.Topic, durable: true);

    var bytes = Encoding.UTF8.GetBytes(payload);

    canal.BasicPublish(
        exchange: "eventos_topic",
        routingKey: topico,
        body: bytes);

    Console.WriteLine($"Evento publicado → {topico}");
}

// Consumo con exchange Topic
private static void SuscribirEventos(string patron)
{
    var conexion = EstablecerConexion();
    var canal = conexion.CreateModel();

    canal.ExchangeDeclare("eventos_topic", ExchangeType.Topic, durable: true);

    var colaTemporal = canal.QueueDeclare().QueueName;
    canal.QueueBind(colaTemporal, "eventos_topic", routingKey: patron);

    var receptor = new AsyncEventingBasicConsumer(canal);
    receptor.Received += async (_, evento) =>
    {
        var contenido = Encoding.UTF8.GetString(evento.Body.Span);
        Console.WriteLine($"[Topic] Patrón='{patron}': {contenido}");
        canal.BasicAck(evento.DeliveryTag, false);
        await Task.CompletedTask;
    };

    canal.BasicConsume(queue: colaTemporal, autoAck: false, consumer: receptor);
    Console.WriteLine($"Escuchando con patrón: {patron}");
}

La selección entre fanout, direct y topic depende del caso de uso: fenout cuando se requiere difusión masiva, direct para enrutamiento exacto y topic para filtrado jerárquico con comodines.

Etiquetas: RabbitMQ AMQP exchange fanout direct

Publicado el 7-17 23:11