Fundamentos de MQTT

Recursos do MQTT 5 Que Fazem Diferença na Prática: Reason Codes, Shared Subscriptions, Expiração e Mais

O MQTT 5 é mais do que uma nova versão. Conheça os recursos que mudam a forma de construir sistemas IoT, de reason codes e expiração a shared subscriptions e request/response, com exemplos práticos.

Diagrama de recursos do MQTT 5, incluindo shared subscriptions, reason codes e message expiry, passando por um broker MQTT na nuvem
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

O MQTT 5 é a versão atual do padrão MQTT e resolve muitos dos problemas que as equipes enfrentam ao rodar MQTT 3.1.1 em produção. Neste guia você vai descobrir quais recursos do MQTT 5 realmente mudam o trabalho de engenharia do dia a dia, compará-los lado a lado com o 3.1.1 e testá-los você mesmo no servidor MQTT público broker.connectmq.com com as ferramentas de CLI do Mosquitto e Python.

Se você é novo no protocolo, comece por o que é um broker MQTT e volte aqui depois.

Por Que o MQTT 5 Existe

O MQTT 3.1.1 é pequeno, estável e está em todo lugar. Mas ele deixa perguntas importantes sem resposta. Por que minha conexão foi recusada? Por que minha assinatura falhou? Como distribuo as mensagens de um topic entre vários workers? Por quanto tempo o broker deve manter minha sessão ou um comando antigo?

No 3.1.1, as respostas geralmente eram "o broker fechou o socket" ou "escreva isso você mesmo no payload". O MQTT 5, padronizado pela OASIS em 2019, adiciona ao protocolo retorno explícito, metadados e limites, mantendo o mesmo modelo publish/subscribe. Seus topics, níveis de QoS e retained messages funcionam exatamente como antes. A especificação completa está disponível na OASIS.

MQTT 3.1.1 vs MQTT 5 em Resumo

Recurso MQTT 3.1.1 MQTT 5
Retorno de erros Alguns return codes no CONNACK, flag de falha no SUBACK Reason codes em quase todas as confirmações, mais reason strings opcionais
Duração da sessão Clean session liga ou desliga Session Expiry Interval em segundos
Duração da mensagem Ilimitada até ser entregue Message Expiry Interval por mensagem
Balanceamento de carga Fora do padrão Shared subscriptions $share/{group}/{filter}
Economia de banda Topic completo em cada PUBLISH Topic aliases (um inteiro no lugar da string do topic)
Metadados personalizados Só dentro do payload User properties (pares chave/valor)
Request/response Convenções improvisadas Response Topic e Correlation Data
Controle de fluxo Específico de cada implementação Receive Maximum negociado em CONNECT/CONNACK
Limites do servidor O cliente precisa adivinhar Anunciados no CONNACK (Maximum QoS, Retain Available e outros)

Reason Codes: Saber Por Que Algo Falhou

A mudança mais útil do MQTT 5 para a operação são os reason codes. CONNACK, PUBACK, PUBREC, SUBACK, UNSUBACK e DISCONNECT carregam um código de um byte, e o servidor pode enviar junto uma reason string legível por humanos.

Código Nome Significado típico
0x00 Success Operação aceita
0x10 No matching subscribers Publicação QoS 1/2 aceita, mas ninguém está assinando
0x86 Bad user name or password Credenciais incorretas
0x87 Not authorized O cliente não tem permissão para a ação
0x89 Server busy O broker está se protegendo, tente mais tarde
0x95 Packet too large A mensagem excede o tamanho máximo de pacote
0x97 Quota exceeded Um limite de taxa ou de uso foi atingido

Códigos abaixo de 0x80 indicam sucesso; 0x80 e acima são falhas. Para dispositivos em campo, isso faz muita diferença: o firmware pode reagir de um jeito a "senha errada" (parar e alertar) e de outro a "servidor ocupado" (recuar e tentar de novo). O ConnectMQ retorna reason codes como 0x97 Quota exceeded e 0x89 Server busy, para que seus clientes consigam distinguir um problema de cota de uma sobrecarga temporária.

Session Expiry e Message Expiry

Session Expiry Interval

No 3.1.1, uma sessão persistente (clean session = false) dura até o cliente voltar, possivelmente para sempre. O MQTT 5 substitui isso por duas configurações: Clean Start (começar do zero ou retomar) e Session Expiry Interval (por quantos segundos o broker mantém a sessão após a desconexão). Um sensor que dorme por dez minutos pode pedir uma sessão de 15 minutos e ainda receber suas mensagens QoS 1 enfileiradas ao acordar, enquanto sessões abandonadas desaparecem sozinhas.

No ConnectMQ, o session expiry tem um teto por plano; no servidor MQTT público, o máximo é 30 minutos.

Message Expiry Interval

O publisher pode anexar a cada mensagem uma validade em segundos. Se a mensagem ainda estiver esperando em uma fila ou armazenada como retida quando o intervalo acabar, o broker a descarta. É exatamente o que você quer para comandos como "abrir a válvula", que são perigosos se executados uma hora depois.

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

Shared Subscriptions: Balanceamento de Carga Nativo

Com uma assinatura normal, cada subscriber recebe todas as mensagens. Isso vira um problema quando uma única instância de backend não dá conta de milhares de dispositivos. As shared subscriptions do MQTT 5 permitem que vários clientes entrem em um grupo; o broker entrega cada mensagem a apenas um membro do grupo.

A sintaxe é $share/{group}/{filter}. Os publishers não mudam nada: continuam publicando no topic simples.

Teste: Dois Workers Compartilhando um Topic

Abra dois terminais e inicie dois consumidores no mesmo grupo workers. Troque a1b2 pelo seu próprio ID aleatório, porque a área pública pode ser lida por todos.

# 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

Agora publique seis jobs a partir de um terceiro 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

Quando rodamos isso no broker.connectmq.com, o worker 1 recebeu job-1, job-3, job-5 e o worker 2 recebeu job-2, job-4, job-6. Cada job foi processado uma única vez, e adicionar um terceiro worker distribui ainda mais a carga sem mexer nos publishers. As aspas simples em volta do topic são importantes no bash, para que o shell não expanda $share.

Usos típicos são serviços de ingestão escalados horizontalmente, processadores de comandos e qualquer consumidor que você roda como várias réplicas de contêiner.

Topics industriais podem ser longos, por exemplo acme/izmir/machining/line1/cnc1/telemetry. Enviar essa string em cada PUBLISH desperdiça banda em links celulares ou de satélite. Com um topic alias, o cliente envia o topic completo uma vez junto com um alias inteiro e, a partir daí, envia só o número do alias. Cada lado anuncia quantos aliases aceita (Topic Alias Maximum) no CONNECT e no CONNACK. A maioria das bibliotecas cliente cuida dos aliases para você depois que eles são ativados.

User Properties: Metadados Sem Mexer no Payload

User properties são pares chave/valor UTF-8 arbitrários anexados a um pacote. Use-as para indicar o tipo de conteúdo, versões de schema, trace IDs ou a versão de firmware do remetente, enquanto o payload continua sendo uma leitura de sensor limpa.

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

Um subscriber pode imprimi-las com mosquitto_sub -V mqttv5 -F "%t %p %P".

Request/Response com Response Topic e Correlation Data

Muitos fluxos de IoT são, na verdade, chamadas RPC: "leia sua configuração", "reinicie", "me passe os últimos 10 valores". O MQTT 5 padroniza isso:

  1. O solicitante assina seu próprio topic de resposta.
  2. Ele publica a requisição com uma propriedade Response Topic apontando para esse topic de resposta e Correlation Data que identifica a requisição.
  3. O respondente responde no response topic e devolve a correlation data.

Como o response topic é repassado aos subscribers, o respondente não precisa de nenhum conhecimento de roteamento fixo no código. Você pode ver as propriedades chegando com:

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"

Controle de Fluxo e Limites Anunciados pelo Servidor

Receive Maximum

O Receive Maximum limita quantas mensagens QoS 1 e QoS 2 podem estar em trânsito (enviadas, mas ainda não confirmadas) ao mesmo tempo. O cliente informa seu limite ao servidor no CONNECT, e o servidor informa o dele ao cliente no CONNACK. Um microcontrolador pequeno pode definir um valor baixo para nunca ser inundado, e o broker pode se proteger de um cliente que publica mais rápido do que consegue confirmar. Para saber mais sobre confirmações, veja Níveis de QoS MQTT Explicados.

Limites no CONNACK

No 3.1.1, o cliente descobria os limites falhando. No MQTT 5, o servidor os anuncia nas propriedades do CONNACK, incluindo Maximum QoS, Retain Available, Wildcard Subscription Available, Shared Subscription Available, Receive Maximum e Topic Alias Maximum. Um cliente bem escrito lê esses valores uma vez e se adapta, por exemplo, caindo para QoS 1 se o QoS 2 não for oferecido no plano atual.

MQTT 5 em Python com Paho 2.x

O script a seguir se conecta com MQTT 5, imprime os limites anunciados no CONNACK e executa um ciclo completo de request/response usando message expiry, user properties, response topic e 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"          # use seu próprio ID aleatório
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 do respondente: responde no topic solicitado e devolve a 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   # mantém a sessão por 10 minutos
connect_props.ReceiveMaximum = 10           # no máximo 10 mensagens recebidas sem confirmação

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()

Em um sistema real, o solicitante e o respondente são clientes diferentes, mas o código continua o mesmo. Em produção, conecte-se ao seu próprio broker com YOUR_USERNAME, YOUR_PASSWORD e YOUR_CLIENT_ID do portal, de preferência via TLS na porta 8883 (veja boas práticas de segurança MQTT).

Suporte ao MQTT 5 no ConnectMQ

Os brokers ConnectMQ falam MQTT 3.1.1 e MQTT 5.0 sobre TCP, TLS e WebSocket, então dispositivos antigos e novos podem compartilhar o mesmo broker. Os recursos do MQTT 5 implementados atualmente são:

Recurso Suporte no ConnectMQ
Reason codes Sim, por exemplo 0x97 Quota exceeded e 0x89 Server busy
Session expiry Sim, com teto por plano (30 minutos no servidor MQTT público)
Message expiry interval Sim
Shared subscriptions Sim, $share/{group}/{filter}; no servidor MQTT público e no plano Serverless (Paid) ou superior
Topic alias Sim
Response topic (request/response) Sim, repassado aos subscribers
Limites no CONNACK Maximum QoS, Retain Available, Wildcard e Shared Subscription Available, Receive Maximum
Retained messages Sim

A extensão do ConnectMQ para VS Code mostra diretamente os reason codes de CONNACK, SUBACK e PUBACK, o que ajuda muito quando você está investigando por que uma assinatura foi recusada.

Comece com o ConnectMQ

Você pode testar todos os exemplos deste artigo no servidor MQTT público broker.connectmq.com sem conta, usando apenas dados de teste. Quando estiver pronto para ter seu próprio broker isolado com credenciais, cadastre-se em app.connectmq.com: o plano Serverless é gratuito, não exige cartão de crédito e entrega um broker em minutos. Compare as opções em o que é um broker MQTT gerenciado.

Perguntas frequentes

O MQTT 5 é retrocompatível com o MQTT 3.1.1?

O formato na rede é diferente, então o cliente escolhe uma versão ao se conectar. Um broker que suporta as duas, como o ConnectMQ, permite que clientes MQTT 3.1.1 e MQTT 5 troquem mensagens nos mesmos topics.

Preciso do MQTT 5 para usar shared subscriptions?

As shared subscriptions são definidas no padrão MQTT 5. Conecte-se com MQTT 5 (por exemplo, -V mqttv5 nas ferramentas do Mosquitto) e assine $share/{group}/{filter} para obter entrega com balanceamento de carga.

Qual a diferença entre session expiry e message expiry?

O session expiry controla por quanto tempo o broker mantém a sessão e as mensagens enfileiradas de um cliente desconectado. O message expiry controla por quanto tempo uma mensagem individual continua válida antes que o broker a descarte.

Qual recurso do MQTT 5 devo adotar primeiro?

Comece pelos reason codes, porque eles tornam as falhas visíveis sem custo nenhum. Depois, adicione session expiry para dispositivos que dormem e shared subscriptions para serviços de backend que precisam escalar.

Artigos relacionados

Por que Seu Cliente MQTT Fica Desconectando (e Como Corrigir)

Loops de reconexão estão entre os problemas mais comuns do MQTT. Conheça as cinco causas habituais, de client IDs duplicados a timeouts de keep alive e pacotes grandes demais, e siga um fluxo de diagnóstico que identifica a sua.

9 min de leitura

Quem Inventou o MQTT? A História dos Oleodutos à IoT

O MQTT foi inventado em 1999 por Andy Stanford-Clark, da IBM, e Arlen Nipper, da Arcom, para monitorar oleodutos via satélite. Acompanhe sua trajetória até o open source, a OASIS, a ISO e os brokers MQTT gerenciados na nuvem de hoje.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.