Fundamentos de MQTT

Las características de MQTT 5 que importan en la práctica: reason codes, suscripciones compartidas, expiración y más

MQTT 5 es mucho más que un cambio de versión. Conoce las funciones que cambian la forma de construir sistemas IoT, desde los reason codes y la expiración hasta las suscripciones compartidas y la petición/respuesta, con ejemplos prácticos.

Diagrama de las características de MQTT 5, como suscripciones compartidas, reason codes y expiración de mensajes, a través de un broker MQTT en la nube
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

MQTT 5 es la versión actual del estándar MQTT y resuelve muchos de los problemas con los que se topan los equipos al operar MQTT 3.1.1 en producción. En esta guía verás qué características de MQTT 5 cambian de verdad el trabajo de ingeniería del día a día, las compararás con 3.1.1 y las probarás tú mismo contra el servidor MQTT público gratuito broker.connectmq.com (pensado para pruebas y prototipos, nunca para datos de producción) con las herramientas CLI de Mosquitto y con Python.

Si el protocolo es nuevo para ti, empieza por qué es un broker MQTT y vuelve aquí después.

Por qué existe MQTT 5

MQTT 3.1.1 es pequeño, estable y está en todas partes. Pero deja sin respuesta preguntas importantes. ¿Por qué se rechazó mi conexión? ¿Por qué falló mi suscripción? ¿Cómo reparto los mensajes de un topic entre varios workers? ¿Cuánto tiempo debería guardar el broker mi sesión o un comando antiguo?

En 3.1.1 las respuestas solían ser «el broker cerró el socket» o «escríbelo tú mismo en el payload». MQTT 5, estandarizado por OASIS en 2019, añade al protocolo información explícita, metadatos y límites, manteniendo el mismo modelo de publicación/suscripción. Tus topics, niveles de QoS y retained messages funcionan exactamente igual que antes. La especificación completa está disponible en OASIS.

MQTT 3.1.1 frente a MQTT 5 de un vistazo

Capacidad MQTT 3.1.1 MQTT 5
Información de errores Unos pocos códigos de retorno en CONNACK, flag de fallo en SUBACK Reason codes en casi todas las confirmaciones, más reason strings opcionales
Duración de la sesión Clean session activada o desactivada Session Expiry Interval en segundos
Duración del mensaje Ilimitada hasta su entrega Message Expiry Interval por mensaje
Balanceo de carga No está en el estándar Suscripciones compartidas $share/{group}/{filter}
Ahorro de ancho de banda Topic completo en cada PUBLISH Topic aliases (un entero en lugar de la cadena del topic)
Metadatos personalizados Solo dentro del payload User properties (pares clave/valor)
Petición/respuesta Convenciones ad hoc Response Topic y Correlation Data
Control de flujo Depende de la implementación Receive Maximum negociado en CONNECT/CONNACK
Límites del servidor El cliente tiene que adivinarlos Anunciados en el CONNACK (Maximum QoS, Retain Available y más)

Reason codes: saber por qué algo ha fallado

El cambio más útil de MQTT 5 para la operación son los reason codes. CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK y DISCONNECT llevan un código de un byte, y el servidor puede enviar junto a él una reason string legible.

Código Nombre Significado habitual
0x00 Success Operación aceptada
0x10 No matching subscribers Publicación QoS 1/2 aceptada, pero no hay nadie suscrito
0x86 Bad user name or password Credenciales incorrectas
0x87 Not authorized El cliente no tiene permiso para realizar la acción
0x89 Server busy El broker se está protegiendo; reintenta más tarde
0x95 Packet too large El mensaje supera el tamaño máximo de paquete
0x97 Quota exceeded Se ha alcanzado un límite de tasa o de uso

Los códigos por debajo de 0x80 indican éxito; los de 0x80 en adelante son fallos. Para los dispositivos desplegados esto es muy importante: el firmware puede reaccionar de forma distinta ante «contraseña incorrecta» (detenerse y alertar) que ante «servidor ocupado» (esperar y reintentar). ConnectMQ devuelve reason codes como 0x97 Quota exceeded y 0x89 Server busy, de modo que tus clientes pueden distinguir un problema de cuota de una sobrecarga temporal.

Expiración de sesión y expiración de mensaje

Session Expiry Interval

En 3.1.1, una sesión persistente (clean session = false) dura hasta que el cliente vuelve, posiblemente para siempre. MQTT 5 lo sustituye por dos ajustes: Clean Start (empezar de cero o reanudar) y Session Expiry Interval (cuántos segundos conserva el broker la sesión tras la desconexión). Un sensor que duerme diez minutos puede pedir una sesión de 15 minutos y seguir recibiendo sus mensajes QoS 1 encolados al despertar, mientras que las sesiones abandonadas desaparecen por sí solas.

En ConnectMQ, la expiración de sesión tiene un tope por plan; en el servidor MQTT público gratuito, el máximo es de 30 minutos.

Message Expiry Interval

Un publicador puede asignar a cada mensaje una vida útil en segundos. Si el mensaje sigue esperando en una cola o almacenado como retenido cuando se agota ese intervalo, el broker lo descarta. Es exactamente lo que quieres para comandos como «abrir la válvula», que resultan peligrosos si se ejecutan con una hora de retraso.

mosquitto_pub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-pub \
  -t "connectmq/demo/a1b2/commands/valve1" -m "open" -q 1 \
  -D publish message-expiry-interval 60

Suscripciones compartidas: balanceo de carga integrado

Con una suscripción normal, cada suscriptor recibe todos los mensajes. Eso es un problema cuando una sola instancia del backend no da abasto con miles de dispositivos. Las suscripciones compartidas de MQTT 5 permiten que varios clientes se unan a un grupo; el broker entrega cada mensaje a un solo miembro del grupo.

La sintaxis es $share/{group}/{filter}. Los publicadores no cambian nada: siguen publicando en el topic normal.

Pruébalo: dos workers compartiendo un topic

Abre dos terminales e inicia dos consumidores en el mismo grupo workers. Sustituye a1b2 por tu propio ID aleatorio, porque cualquiera puede leer el área pública.

# Terminal 1
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-w1 \
  -t '$share/workers/connectmq/demo/a1b2/jobs' -v

# Terminal 2
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-w2 \
  -t '$share/workers/connectmq/demo/a1b2/jobs' -v

Ahora publica seis trabajos desde una tercera terminal:

for i in 1 2 3 4 5 6; do
  mosquitto_pub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-pub \
    -t "connectmq/demo/a1b2/jobs" -m "job-$i" -q 1
done

Cuando lo ejecutamos contra broker.connectmq.com, el worker 1 recibió job-1, job-3 y job-5, y el worker 2 recibió job-2, job-4 y job-6. Cada trabajo se procesó una sola vez, y añadir un tercer worker reparte aún más la carga sin tocar los publicadores. En bash, las comillas simples alrededor del topic son importantes para que la shell no expanda $share.

Los usos típicos son servicios de ingesta escalados horizontalmente, procesadores de comandos y cualquier consumidor que ejecutes como varias réplicas de contenedor.

Topic aliases: paquetes más pequeños en enlaces limitados

Los topics industriales pueden ser largos, por ejemplo acme/izmir/machining/line1/cnc1/telemetry. Enviar esa cadena en cada PUBLISH desperdicia ancho de banda en enlaces móviles o por satélite, como los de una mina remota en los Andes o una plataforma de oil & gas. Con un topic alias, el cliente envía una vez el topic completo junto con un alias numérico y, a partir de ahí, solo envía el número del alias. Cada parte anuncia cuántos alias acepta (Topic Alias Maximum) en CONNECT y CONNACK. La mayoría de las librerías cliente gestionan los alias por ti una vez que los activas.

User properties: metadatos sin tocar el payload

Las user properties son pares clave/valor UTF-8 arbitrarios asociados a un paquete. Úsalas para indicar el tipo de contenido, versiones de esquema, IDs de trazas o la versión de firmware del emisor, mientras el payload sigue siendo una lectura de sensor limpia.

mosquitto_pub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-pub \
  -t "connectmq/demo/a1b2/sensors/t1" -m '{"temperature":22.4}' \
  -D publish user-property schema v2 -D publish user-property fw 1.4.0

Un suscriptor puede mostrarlas con mosquitto_sub -V mqttv5 -F "%t %p %P".

Petición/respuesta con Response Topic y Correlation Data

Muchos flujos IoT son en realidad llamadas RPC: «lee tu configuración», «reinicia», «dame los 10 últimos valores». MQTT 5 lo estandariza:

  1. El solicitante se suscribe a su propio topic de respuesta.
  2. Publica la petición con una propiedad Response Topic que apunta a ese topic de respuesta y una Correlation Data que identifica la petición.
  3. El que responde contesta en el response topic y devuelve una copia de la correlation data.

Como el response topic se transmite a los suscriptores, quien responde no necesita ningún conocimiento de enrutamiento fijado en el código. Puedes ver llegar las propiedades con:

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i demo-a1b2-s \
  -t "connectmq/demo/a1b2/#" -F "%t %p | response=%R props=%P"

Control de flujo y límites anunciados por el servidor

Receive Maximum

Receive Maximum limita cuántos mensajes QoS 1 y QoS 2 pueden estar en tránsito (enviados pero aún sin confirmar) a la vez. El cliente comunica su límite al servidor en el CONNECT y el servidor comunica el suyo al cliente en el CONNACK. Un microcontrolador pequeño puede fijar un valor bajo para no verse nunca desbordado, y el broker puede protegerse de un cliente que publica más rápido de lo que es capaz de confirmar. Para saber más sobre las confirmaciones, consulta Niveles de QoS en MQTT explicados.

Límites en el CONNACK

En 3.1.1, un cliente descubría los límites fallando. En MQTT 5, el servidor los anuncia en las propiedades del CONNACK, entre ellas Maximum QoS, Retain Available, Wildcard Subscription Available, Shared Subscription Available, Receive Maximum y Topic Alias Maximum. Un cliente bien escrito los lee una vez y se adapta, por ejemplo bajando a QoS 1 si el plan actual no ofrece QoS 2.

MQTT 5 en Python con Paho 2.x

El siguiente script se conecta con MQTT 5, muestra los límites anunciados en el CONNACK y ejecuta un ciclo completo de petición/respuesta usando expiración de mensaje, user properties, response topic y correlation data.

import time
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties

BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/a1b2"          # usa tu propio ID aleatorio
REQ_TOPIC = f"{PREFIX}/rpc/request"
RESP_TOPIC = f"{PREFIX}/rpc/response"


def on_connect(client, userdata, flags, reason_code, properties):
    print("CONNACK:", reason_code)
    for name in ("MaximumQoS", "RetainAvailable", "ReceiveMaximum",
                 "TopicAliasMaximum", "SharedSubscriptionAvailable"):
        if hasattr(properties, name):
            print(f"  {name} = {getattr(properties, name)}")
    client.subscribe(REQ_TOPIC, qos=1)
    client.subscribe(RESP_TOPIC, qos=1)


def on_message(client, userdata, msg):
    props = msg.properties
    if msg.topic == REQ_TOPIC:
        # Lado que responde: contestar en el topic solicitado y devolver la correlation data
        reply = Properties(PacketTypes.PUBLISH)
        reply.CorrelationData = props.CorrelationData
        client.publish(props.ResponseTopic, b'{"uptime": 3600}', qos=1,
                       properties=reply)
    elif msg.topic == RESP_TOPIC:
        print("Response:", msg.payload.decode(),
              "correlation:", props.CorrelationData.decode())


connect_props = Properties(PacketTypes.CONNECT)
connect_props.SessionExpiryInterval = 600   # conservar la sesión 10 minutos
connect_props.ReceiveMaximum = 10           # máx. 10 mensajes entrantes sin confirmar

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="demo-a1b2-py", protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, clean_start=True, properties=connect_props)
client.loop_start()
time.sleep(2)

request = Properties(PacketTypes.PUBLISH)
request.ResponseTopic = RESP_TOPIC
request.CorrelationData = uuid.uuid4().hex.encode()
request.MessageExpiryInterval = 30
request.UserProperty = [("schema", "v1"), ("app", "blog-demo")]
client.publish(REQ_TOPIC, b'{"cmd": "status"}', qos=1, properties=request)

time.sleep(3)
client.loop_stop()
client.disconnect()

En un sistema real, el solicitante y el que responde son clientes distintos, pero el código es el mismo. En producción, conéctate a tu propio broker con YOUR_USERNAME, YOUR_PASSWORD y YOUR_CLIENT_ID del portal, idealmente por TLS en el puerto 8883 (consulta las buenas prácticas de seguridad en MQTT).

Soporte de MQTT 5 en ConnectMQ

Los brokers de ConnectMQ hablan tanto MQTT 3.1.1 como MQTT 5.0 sobre TCP, TLS y WebSocket, así que dispositivos antiguos y nuevos pueden compartir el mismo broker. Las funciones de MQTT 5 implementadas actualmente son:

Función Soporte en ConnectMQ
Reason codes Sí, por ejemplo 0x97 Quota exceeded y 0x89 Server busy
Expiración de sesión Sí, con tope por plan (30 minutos en el servidor MQTT público gratuito)
Message expiry interval Sí
Suscripciones compartidas Sí, $share/{group}/{filter}; en el servidor público y en el plan Serverless (Paid) y superiores
Topic alias Sí
Response topic (petición/respuesta) Sí, se transmite a los suscriptores
Límites en el CONNACK Maximum QoS, Retain Available, Wildcard y Shared Subscription Available, Receive Maximum
Retained messages Sí

La extensión de ConnectMQ para VS Code muestra directamente los reason codes de CONNACK, SUBACK y PUBACK, algo muy útil cuando estás depurando por qué se ha rechazado una suscripción.

Empieza con ConnectMQ

Puedes probar todos los ejemplos de este artículo en broker.connectmq.com sin cuenta. Cuando estés listo para tener tu propio broker aislado con credenciales, regístrate en app.connectmq.com: el plan Serverless es gratuito, no requiere tarjeta de crédito y te da un broker en minutos. Compara las opciones en nuestra guía sobre qué es un broker MQTT gestionado.

Preguntas frecuentes

¿Es MQTT 5 compatible con MQTT 3.1.1?

El formato de los paquetes es distinto, así que un cliente elige una versión al conectarse. Un broker que admite ambas, como ConnectMQ, permite que clientes MQTT 3.1.1 y MQTT 5 intercambien mensajes en los mismos topics.

¿Necesito MQTT 5 para usar suscripciones compartidas?

Las suscripciones compartidas están definidas en el estándar MQTT 5. Conéctate con MQTT 5 (por ejemplo, -V mqttv5 en las herramientas de Mosquitto) y suscríbete a $share/{group}/{filter} para obtener una entrega con balanceo de carga.

¿Qué diferencia hay entre la expiración de sesión y la expiración de mensaje?

La expiración de sesión controla cuánto tiempo conserva el broker la sesión y los mensajes encolados de un cliente desconectado. La expiración de mensaje controla cuánto tiempo sigue siendo válido un mensaje concreto antes de que el broker lo descarte.

¿Qué característica de MQTT 5 debería adoptar primero?

Empieza por los reason codes, porque hacen visibles los fallos sin coste alguno. Después añade la expiración de sesión para los dispositivos que duermen y las suscripciones compartidas para los servicios backend que necesitan escalar.

Artículos relacionados

¿Quién inventó MQTT? La historia, de los oleoductos al IoT

MQTT lo inventaron en 1999 Andy Stanford-Clark, de IBM, y Arlen Nipper, de Arcom, para monitorizar oleoductos vía satélite. Sigue su camino hacia el open source, OASIS, ISO y los brokers MQTT gestionados en la nube de hoy.

8 min de lectura

Por qué tu cliente MQTT se desconecta constantemente (y cómo solucionarlo)

Los bucles de reconexión son uno de los problemas más habituales de MQTT. Conoce las cinco causas típicas, desde los client ID duplicados hasta los timeouts de keep alive y los paquetes demasiado grandes, y sigue un flujo de diagnóstico que localiza la tuya.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.