Fundamentos

Suscripciones compartidas en MQTT: balanceo de carga con $share

Aprende las suscripciones compartidas de MQTT: la sintaxis $share/grupo/filtro, cómo se reparten los mensajes, soporte en brokers, casos de uso y limitaciones.

Actualizado · 7 min de lectura

En MQTT normal, cada suscriptor cuyo filtro coincide con un topic recibe su propia copia de cada mensaje. Eso es perfecto para el fan-out, pero resulta incómodo cuando quieres que varios workers de backend se repartan un flujo de mensajes. Las suscripciones compartidas, estandarizadas en MQTT 5, lo resuelven: los suscriptores se unen a un grupo con nombre y el broker entrega cada mensaje a un solo miembro de ese grupo.

La sintaxis $share

Una suscripción compartida es una petición de suscripción normal con un filtro de topic especial:

Formato
$share/<ShareName>/<TopicFilter>
  • $share: un prefijo literal que marca la suscripción como compartida.
  • ShareName: el nombre del grupo. Debe tener al menos un carácter y no puede contener /, + ni #.
  • TopicFilter: un filtro normal, comodines incluidos. Consulta topics y comodines en MQTT.

Por ejemplo, $share/ingest/factory/+/telemetry mete al suscriptor en el grupo ingest para todos los topics factory/<line>/telemetry. Los publicadores no hacen nada especial: siguen publicando en factory/line-3/telemetry y nunca ven el prefijo $share. Para comprobar con qué topics coincide un filtro, pega la parte que va después del nombre del grupo en el probador de topics MQTT.

Cómo se reparten los mensajes

Las reglas de entrega se ven mejor con un ejemplo. Supón que existen estas suscripciones:

ClienteSuscripción¿Recibe un mensaje publicado en orders/new?
worker-a$share/billing/orders/#Uno de los dos (lo elige el broker)
worker-b$share/billing/orders/#
mailer-1$share/email/orders/newSí: es el único miembro del grupo email
auditorders/#Sí: las suscripciones normales no se ven afectadas

Así, cada grupo recibe una copia y, dentro de un grupo, la procesa un solo miembro. La especificación no define cómo se elige ese miembro. Los brokers ofrecen estrategias como round robin, aleatoria, sticky (seguir enviando al mismo miembro hasta que desaparezca) o hash sobre el client ID o el topic; EMQX, por ejemplo, permite configurarlo. Cada miembro conserva el QoS de su suscripción y los mensajes se confirman por miembro, como siempre.

Soporte en los brokers

Las suscripciones compartidas forman parte del estándar MQTT 5, y un broker indica que las soporta mediante la propiedad Shared Subscription Available del CONNACK. Si un broker no las soporta, rechaza la suscripción con el reason code 0x9E (Shared Subscriptions not supported); consulta la referencia de reason codes de MQTT.

  • Mosquitto soporta $share desde la versión 1.6, para clientes MQTT 5 y 3.1.1.
  • EMQX soporta $share y también un atajo más antiguo, $queue/<filter>, que se comporta como una suscripción compartida en un único grupo por defecto.
  • HiveMQ soporta $share en ambas versiones del protocolo.

Los brokers gestionados y cloud varían, así que revisa su documentación antes de depender de ello. Nuestra comparativa de brokers MQTT cubre las opciones principales.

Ejemplo: dos workers compartiendo un flujo

Abre tres terminales. Los dos primeros se unen al mismo grupo; el tercero publica diez mensajes:

Terminales 1 y 2: workers
mosquitto_sub -V mqttv5 -h broker.hivemq.com -q 1 \
  -t '$share/workers/testmqtt/share-demo/jobs' -v
Terminal 3: publicador
for i in $(seq 1 10); do
  mosquitto_pub -V mqttv5 -h broker.hivemq.com -q 1 \
    -t 'testmqtt/share-demo/jobs' -m "job $i"
done

Cada trabajo aparece exactamente en un terminal de worker. Fíjate en las comillas simples alrededor del filtro: en la mayoría de los shells, $share entre comillas dobles se expandiría como una variable. En código funciona igual:

Worker con MQTT.js
import mqtt from 'mqtt';

const client = mqtt.connect('wss://broker.hivemq.com:8884/mqtt', { protocolVersion: 5 });

client.on('connect', () => {
  client.subscribe('$share/workers/testmqtt/share-demo/jobs', { qos: 1 });
});

client.on('message', (topic, payload) => {
  // topic is the real topic, without the $share prefix
  console.log(topic, payload.toString());
});

Puedes hacer de publicador desde el navegador: abre el cliente de abajo y publica en testmqtt/share-demo/jobs mientras tus workers están en marcha.

Publica trabajos de prueba en el cliente online

Casos de uso

  • Escalado horizontal de consumidores de backend. Ejecuta N instancias de un servicio de ingesta y deja que el broker reparta la telemetría entre ellas, sin una cola de mensajes aparte.
  • Alta disponibilidad. Si un consumidor falla, los demás miembros siguen recibiendo mensajes.
  • Puentes hacia bases de datos o procesadores de streams. Varios escritores pueden repartirse la carga de insertar mensajes en una base de datos de series temporales o de reenviarlos a Kafka.
  • Colas de trabajo. Comandos como «redimensiona esta imagen» los puede procesar el worker que los reciba.

Diseñar una configuración de suscripciones compartidas

Elige nombres de grupo por tipo de consumidor

Nombra los grupos según la tarea, no según la instancia: $share/ingest/..., $share/alerts/.... Todas las instancias del servicio de ingesta se unen a ingest; todas las del servicio de alertas, a alerts. Así, cada servicio en su conjunto ve cada mensaje exactamente una vez, mientras sus instancias se reparten el trabajo. Si por error das a cada instancia un nombre de grupo único, vuelves al fan-out simple y todas las instancias procesan todos los mensajes.

Sesiones y QoS para los workers

Los workers suelen suscribirse con QoS 1 para que un mensaje se vuelva a entregar si un worker falla antes de confirmarlo. Que los mensajes se encolen para un grupo mientras todos sus miembros están offline depende de las sesiones de los miembros: con una sesión persistente el broker puede seguir encolando; con sesiones limpias, los mensajes publicados mientras no hay nadie del grupo conectado simplemente no se entregan a ese grupo.

Suscripciones compartidas vs una cola de mensajes

Las suscripciones compartidas te dan consumidores competidores sencillos dentro de MQTT. No te dan funciones de los sistemas de colas dedicados, como reproducir desde un offset, dead-letter queues o garantías de orden por partición. Para muchos backends IoT ese compromiso es aceptable; si necesitas replay, reenvía el flujo desde una suscripción compartida a un log como Kafka y procésalo allí.

Errores habituales y limitaciones

  • Sin orden global. MQTT garantiza el orden por topic para un único suscriptor, pero en cuanto los mensajes se reparten entre varios workers se procesan en paralelo. Si el orden importa por dispositivo, usa una estrategia del broker que haga hash sobre el topic, o particiona tú mismo por topic.
  • Sin mensajes retenidos. Según la especificación de MQTT 5, los mensajes retenidos no se envían al establecer una suscripción compartida. Usa una suscripción normal para estados como los topics de estado retenidos.
  • No Local no está permitido. Activar la opción No Local en una suscripción compartida es un error de protocolo en MQTT 5.
  • Desuscríbete con el filtro completo. Para salir del grupo, cancela la suscripción con la cadena exacta $share/group/filter, no solo con el filtro simple.
  • ACL. Los brokers difieren en si las reglas de acceso se aplican a la cadena $share/... o al filtro subyacente. Prueba tus permisos con la sintaxis real.
  • Los duplicados siguen siendo posibles. QoS 1 significa al menos una vez, y la reentrega a otro miembro tras una desconexión puede producir duplicados. Diseña handlers idempotentes; consulta los niveles de QoS de MQTT.

Preguntas frecuentes

¿Qué es una suscripción compartida en MQTT?

Una suscripción compartida permite que varios clientes se suscriban como grupo usando un filtro de topic con la forma $share/grupo/filtro. Cada mensaje que coincide se entrega a un solo miembro del grupo en lugar de a todos los suscriptores, lo que reparte la carga entre varios consumidores.

¿Las suscripciones compartidas funcionan con MQTT 3.1.1?

Las suscripciones compartidas están estandarizadas en MQTT 5. Varios brokers, como Mosquitto, EMQX y HiveMQ, también aceptan la sintaxis $share de clientes MQTT 3.1.1 como extensión, pero el comportamiento depende del broker, así que revisa su documentación.

¿Por qué los suscriptores compartidos no reciben mensajes retenidos?

La especificación de MQTT 5 indica que los mensajes retenidos no se envían a una sesión cuando establece una nueva suscripción compartida. Como un mensaje retenido representa el estado actual y no trabajo que repartir, entregarlo a un miembro cualquiera del grupo no tendría sentido. Usa una suscripción normal si necesitas el valor retenido.