Colas punto a punto vs. topics de pub/sub: ¿cuál es la diferencia real en la semántica de entrega?
Mismo broker, dos contratos opuestos: una cola reparte trabajo entre consumidores que compiten, mientras un topic le da a cada suscriptor su propia copia completa del flujo.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Colas punto a punto vs. topics de pub/sub: ¿cuál es la diferencia real en la semántica de entrega?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Mismo broker, dos contratos de entrega completamente distintos
"Cola" y "topic" se usan con bastante libertad, pero codifican una diferencia estructural real: una cola entrega cada mensaje a exactamente un consumidor dentro de un grupo que compite por el trabajo (punto a punto), mientras que un topic entrega una copia de cada mensaje a cada suscriptor independiente (publicación/suscripción). Ambos patrones se pueden construir sobre el mismo broker —RabbitMQ, por ejemplo— porque el patrón vive en cómo conectas exchanges con colas, no en la identidad del broker.
#Punto a punto: una cola de trabajo con consumidores que compiten
En el modelo punto a punto, una sola cola lógica guarda los mensajes, y cualquier número de procesos consumidores puede leer de ella. El broker entrega cada mensaje a exactamente uno de ellos —esto se llama competing consumers— que es justo lo que quieres para repartir trabajo: cinco workers de procesamiento de pedidos leen todos de orders.process, y cada pedido se maneja una sola vez, por el worker que esté libre en ese momento.
# producer.py — punto a punto: un mensaje, un worker lo recibiráimport pika, jsonconnection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))channel = connection.channel()channel.queue_declare(queue="orders.process", durable=True)order = {"order_id": "A-1042", "sku": "WIDGET-9", "qty": 3}channel.basic_publish(exchange="", # el exchange por defecto enruta por nombre de colarouting_key="orders.process",body=json.dumps(order),properties=pika.BasicProperties(delivery_mode=2), # persiste en disco)connection.close()
# worker.py — corre N copias de esto; RabbitMQ reparte las entregas entre ellasimport pika, jsonconnection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))channel = connection.channel()channel.queue_declare(queue="orders.process", durable=True)channel.basic_qos(prefetch_count=1) # no le des un mensaje nuevo a un worker hasta que confirme el anteriordef handle_order(ch, method, properties, body):order = json.loads(body)print(f"procesando {order['order_id']}")ch.basic_ack(delivery_tag=method.delivery_tag)channel.basic_consume(queue="orders.process", on_message_callback=handle_order)channel.start_consuming()
Ejecuta tres copias de worker.py y cada pedido sigue procesándose exactamente una vez, repartido entre los workers que estén libres. Ese es el rasgo definitorio: la cola es capacidad compartida, no un canal de difusión.
#Publicación/suscripción: un topic con suscriptores independientes
Pub/sub invierte eso. Cada suscriptor recibe su propia copia completa del flujo, y añadir un segundo suscriptor no reduce lo que ve el primero. En RabbitMQ esto se modela con un exchange fanout conectado a una cola por cada suscriptor:
# publisher.py — pub/sub: cada cola conectada recibe su propia copiaimport pika, jsonconnection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))channel = connection.channel()channel.exchange_declare(exchange="order.events", exchange_type="fanout")event = {"type": "OrderPlaced", "order_id": "A-1042", "total": 79.99}channel.basic_publish(exchange="order.events", routing_key="", body=json.dumps(event))connection.close()
# email_subscriber.py — su propia cola, su propia copia completa de cada eventoimport pikaconnection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))channel = connection.channel()channel.exchange_declare(exchange="order.events", exchange_type="fanout")result = channel.queue_declare(queue="", exclusive=True) # cola anónima para este suscriptorchannel.queue_bind(exchange="order.events", queue=result.method.queue)def send_confirmation_email(ch, method, properties, body):print(f"[email-service] enviando confirmación de {body}")channel.basic_consume(queue=result.method.queue, on_message_callback=send_confirmation_email, auto_ack=True)channel.start_consuming()
# analytics_subscriber.py — un suscriptor totalmente independiente, los mismos eventosimport pikaconnection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))channel = connection.channel()channel.exchange_declare(exchange="order.events", exchange_type="fanout")result = channel.queue_declare(queue="", exclusive=True)channel.queue_bind(exchange="order.events", queue=result.method.queue)def record_metric(ch, method, properties, body):print(f"[analytics-service] registrando evento de venta {body}")channel.basic_consume(queue=result.method.queue, on_message_callback=record_metric, auto_ack=True)channel.start_consuming()
Tanto email_subscriber.py como analytics_subscriber.py reciben todos los eventos OrderPlaced, de forma independiente entre sí. Si matas uno y lo reinicias después —salvo que su cola se haya declarado durable y se hayan perdido mensajes mientras estaba caído— simplemente retoma la recepción de eventos nuevos; nunca "le roba" un mensaje al otro.
#Por qué esta distinción importa más que la herramienta
| Punto a punto (cola) | Pub/sub (topic) | |
|---|---|---|
| Quién recibe cada mensaje | Exactamente un consumidor del grupo | Cada suscriptor, de forma independiente |
| Añadir un consumidor | Reparte el mismo trabajo total entre más manos | Añade una copia nueva y completa del flujo |
| Caso de uso | Repartir trabajo (jobs, tareas) | Difundir hechos (eventos) |
| Analogía en Kafka | Consumidores en el mismo consumer group | Consumidores en distintos consumer groups |
Vale la pena detenerse en esa analogía con Kafka: Kafka no tiene primitivas separadas de "cola" y "topic" como RabbitMQ. Todo topic de Kafka es en realidad un log de pub/sub, pero los consumer groups permiten obtener semántica punto a punto por encima de ese log — cada grupo recibe una copia completa e independiente del topic (pub/sub entre grupos), mientras que dentro de un mismo grupo, cada partición la consume solo un miembro (punto a punto entre los miembros del grupo). El mismo log de fondo, pero ambos contratos de entrega, según cómo agrupes a los consumidores.
🧠 Check your understanding
0/2 · 0/2 answered1. Tres procesos consumidores están leyendo de la misma cola de RabbitMQ. Un productor publica 30 mensajes. ¿Aproximadamente cuántos mensajes procesa cada consumidor?
2. En Kafka, ¿cómo se obtiene entrega estilo 'punto a punto' sobre un topic que es fundamentalmente pub/sub?