Fundamentos

Tópicos e curingas MQTT: guia de design e boas práticas

Tópicos e curingas MQTT explicados: níveis de tópico, os curingas + e #, tópicos $SYS, assinaturas compartilhadas e boas práticas para projetar uma hierarquia.

Atualizado · 7 min de leitura

No MQTT, o tópico é a única coisa que liga publicadores e assinantes. Não há filas nem exchanges para configurar — o broker simplesmente compara o tópico de cada mensagem com todos os filtros de assinatura. Isso faz do design de tópicos a espinha dorsal do seu sistema: ele determina o que os clientes podem assinar, como as permissões são expressas e quão fácil será adicionar novos consumidores depois.

Fundamentos dos tópicos

Um nome de tópico é uma string UTF-8 formada por níveis separados por uma barra:

text
building-a/floor-3/room-12/temperature
vehicles/truck-0042/gps
acme/devices/sensor-17/status
  • Tópicos diferenciam maiúsculas de minúsculas. Home/Kitchen e home/kitchen são tópicos diferentes.
  • Não é preciso registrar nada. Um tópico passa a existir no momento em que alguém publica nele.
  • Cada caractere conta. Uma barra inicial cria um primeiro nível vazio, então /home/kitchen não é o mesmo que home/kitchen. Espaços são permitidos, mas causam confusão sem fim.
  • Tamanho. Nomes de tópicos devem ter no mínimo um caractere e no máximo 65.535 bytes, e não podem conter o caractere nulo.

Curingas: + e #

Os assinantes usam filtros de tópico, que podem conter dois curingas. Os publicadores sempre usam nomes de tópico concretos — curingas em um PUBLISH são uma violação do protocolo.

Curinga de nível único: +

+ corresponde a exatamente um nível. Ele precisa ocupar um nível inteiro (sensor+ é inválido), mas pode aparecer em qualquer posição e mais de uma vez.

FiltroCorresponde aNão corresponde a
home/+/temperaturehome/kitchen/temperaturehome/kitchen/oven/temperature
+/+/statusacme/sensor-17/statusacme/status
home/+home/kitchenhome, home/kitchen/light

Curinga multinível: #

# corresponde a qualquer número de níveis — inclusive zero — e precisa ser o último caractere do filtro, sozinho em seu nível. home/# corresponde a home/kitchen, home/kitchen/light/state e também ao próprio nível pai home. Filtros como home/#/light ou home# são inválidos.

Um # sozinho assina todos os tópicos do broker (exceto os tópicos $). Isso é útil para depurar o seu próprio broker privado, mas em um broker público você será inundado pelo tráfego de outras pessoas.

$SYS e outros tópicos $

Tópicos que começam com $ são reservados ao broker. O mais comum é $SYS/, onde muitos brokers (Mosquitto, EMQX e outros) publicam estatísticas como clientes conectados, taxas de mensagens e uptime. Os tópicos exatos dependem do broker — a especificação só reserva o prefixo.

Curingas no primeiro nível não correspondem a tópicos $: # e +/monitor nunca vão entregar mensagens de $SYS/.... Assine explicitamente:

bash
mosquitto_sub -h test.mosquitto.org -t '$SYS/broker/clients/connected' -v

O MQTT 5 adiciona outro prefixo reservado: as assinaturas compartilhadas usam $share/<group>/<filter>. Todos os assinantes do mesmo grupo dividem o fluxo, e cada mensagem vai para apenas um membro — uma forma simples de balancear a carga entre consumidores. Veja MQTT 5 vs 3.1.1 para outras novidades do MQTT 5, como os topic aliases.

Projetando uma hierarquia de tópicos

Uma boa hierarquia vai do geral para o específico, de modo que curingas no final selecionem grupos com significado. Um padrão comum é:

text
<org-or-app>/<site>/<device-type>/<device-id>/<measurement-or-channel>

acme/berlin/thermostat/th-0193/temperature
acme/berlin/thermostat/th-0193/setpoint/set
acme/berlin/thermostat/th-0193/status

Com esse layout:

  • acme/berlin/# entrega a um dashboard do site tudo o que há em Berlim.
  • acme/+/thermostat/+/temperature entrega a um serviço de analytics todas as leituras de termostatos.
  • acme/berlin/thermostat/th-0193/# entrega o fluxo completo de um único dispositivo.

Boas práticas

  1. Não comece com uma barra. Ela adiciona um nível vazio sem utilidade.
  2. Use minúsculas, ASCII e hífens. Evite espaços e letras maiúsculas misturadas; eles produzem divergências sutis.
  3. Coloque o ID do dispositivo ou do cliente no tópico. Isso facilita o controle de acesso por cliente (muitos brokers conseguem substituir o client ID ou o nome de usuário em padrões de ACL).
  4. Separe dados, comandos e estado. Por exemplo, .../temperature para telemetria, .../setpoint/set para comandos e .../status para estado retido (retained) ou presença via Last Will.
  5. Mantenha os tópicos curtos, mas descritivos. O tópico é enviado com cada mensagem (a menos que você use topic aliases do MQTT 5), então nomes muito longos custam banda em links limitados.
  6. Não codifique dados no tópico. Valores pertencem ao payload; um tópico por valor gera uma quantidade ilimitada de tópicos e muitas mensagens retidas.
  7. Versione seu esquema se ele puder mudar. Um prefixo como v1/ permite que dispositivos antigos e novos coexistam durante uma migração.
  8. Evite assinaturas amplas em produção. Assinar # em um serviço de backend faz de toda mensagem do broker um problema dele.

Erros comuns com tópicos

  • Esperar que a/+ corresponda a a. Não corresponde — + exige que um nível esteja presente. Use a/# se quiser incluir também o nível pai.
  • Barras no final. home/kitchen/ tem um último nível vazio e é um tópico diferente de home/kitchen.
  • Assinaturas sobrepostas. Se um cliente assina tanto home/# quanto home/+/temperature, a especificação permite que o broker entregue uma única cópia ou uma cópia por assinatura correspondente, então alguns brokers vão lhe entregar duplicatas. Mantenha as assinaturas disjuntas sempre que possível.
  • Misturar formatos de ID. Se alguns dispositivos publicam como Sensor_17 e outros como sensor-17, todo filtro e toda regra de ACL precisam tratar os dois casos. Normalize os IDs no provisionamento.
  • Tratar tópicos como banco de dados. Brokers são otimizados para roteamento, não para consultas. Se você precisa de histórico, faça um consumidor assinar os tópicos e gravar as mensagens em um banco de dados.

Tópicos e controle de acesso

Os brokers autorizam operações de publicação e assinatura por tópico, então sua hierarquia funciona também como seu modelo de permissões. Se cada dispositivo escreve apenas sob acme/<site>/<type>/<device-id>/, você pode conceder exatamente essa subárvore e nada mais. Se os tópicos forem planos ou inconsistentes, escrever regras restritas se torna impossível e as equipes acabam liberando # para todo mundo.

Experimente você mesmo

Abra o cliente MQTT online, assine testmqtt/demo/+/temperature e publique em testmqtt/demo/kitchen/temperature e testmqtt/demo/kitchen/oven/temperature. Só a primeira mensagem chega — exatamente como a regra do + prevê.

Teste no cliente MQTT online

Perguntas frequentes

Qual a diferença entre + e # no MQTT?

O curinga + corresponde a exatamente um nível de tópico e pode aparecer em qualquer posição do filtro. O curinga # corresponde a qualquer número de níveis, inclusive zero, e precisa ser o último caractere do filtro.

Posso publicar em um tópico que contém um curinga?

Não. Curingas só são válidos em filtros de assinatura. Um PUBLISH com + ou # no nome do tópico é um erro de protocolo, e o broker vai encerrar a conexão.

Assinar # inclui os tópicos $SYS?

Não. Filtros que começam com um curinga não correspondem a tópicos iniciados por $. Para receber as estatísticas do broker, você precisa assiná-las explicitamente, por exemplo com $SYS/#.