Grundlagen

MQTT-Topics und Wildcards: Design-Guide und Best Practices

MQTT-Topics und Wildcards erklärt: Topic-Ebenen, die Wildcards + und #, $SYS-Topics, Shared Subscriptions und Best Practices für deine Topic-Hierarchie.

Aktualisiert · 7 Min. Lesezeit

In MQTT ist das Topic das Einzige, was Publisher und Subscriber miteinander verbindet. Es gibt keine Queues oder Exchanges zu konfigurieren – der Broker gleicht einfach das Topic jeder Nachricht mit allen Subscription-Filtern ab. Damit wird das Topic-Design zum Rückgrat deines Systems: Es bestimmt, was Clients abonnieren können, wie Berechtigungen formuliert werden und wie leicht du später neue Consumer hinzufügen kannst.

Topic-Grundlagen

Ein Topic-Name ist ein UTF-8-String aus Ebenen (Levels), die durch einen Schrägstrich getrennt sind:

text
building-a/floor-3/room-12/temperature
vehicles/truck-0042/gps
acme/devices/sensor-17/status
  • Topics unterscheiden Groß- und Kleinschreibung. Home/Kitchen und home/kitchen sind verschiedene Topics.
  • Keine Registrierung nötig. Ein Topic existiert in dem Moment, in dem jemand daran publisht.
  • Jedes Zeichen zählt. Ein führender Schrägstrich erzeugt eine leere erste Ebene, daher ist /home/kitchen nicht dasselbe wie home/kitchen. Leerzeichen sind erlaubt, sorgen aber für endlose Verwirrung.
  • Größe. Topic-Namen müssen mindestens ein Zeichen und höchstens 65.535 Bytes lang sein und dürfen kein Null-Zeichen enthalten.

Wildcards: + und #

Subscriber verwenden Topic-Filter, die zwei Wildcards enthalten dürfen. Publisher nutzen immer konkrete Topic-Namen – Wildcards in einem PUBLISH sind ein Protokollverstoß.

Single-Level-Wildcard: +

+ passt auf genau eine Ebene. Es muss eine ganze Ebene belegen (sensor+ ist ungültig), darf aber überall und mehrfach vorkommen.

FilterPasst aufPasst nicht auf
home/+/temperaturehome/kitchen/temperaturehome/kitchen/oven/temperature
+/+/statusacme/sensor-17/statusacme/status
home/+home/kitchenhome, home/kitchen/light

Multi-Level-Wildcard: #

# passt auf beliebig viele Ebenen – auch auf null – und muss das letzte Zeichen des Filters sein, auf einer eigenen Ebene. home/# passt auf home/kitchen, home/kitchen/light/state und auch auf die übergeordnete Ebene home selbst. Filter wie home/#/light oder home# sind ungültig.

Ein einzelnes # abonniert jedes Topic auf dem Broker (außer $-Topics). Zum Debuggen deines eigenen privaten Brokers ist das nützlich, auf einem öffentlichen Broker wirst du damit aber vom Traffic anderer Leute überflutet.

$SYS und andere $-Topics

Topics, die mit $ beginnen, sind für den Broker reserviert. Am bekanntesten ist $SYS/, wo viele Broker (Mosquitto, EMQX und andere) Statistiken wie verbundene Clients, Nachrichtenraten und Uptime veröffentlichen. Die genauen Topics sind Broker-spezifisch – die Spezifikation reserviert nur das Präfix.

Wildcards auf der ersten Ebene passen nicht auf $-Topics: # und +/monitor liefern niemals $SYS/...-Nachrichten. Abonniere sie explizit:

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

MQTT 5 bringt ein weiteres reserviertes Präfix mit: Shared Subscriptions verwenden $share/<group>/<filter>. Alle Subscriber derselben Gruppe teilen sich den Stream, wobei jede Nachricht nur an ein Mitglied geht – ein einfacher Weg, Consumer per Load Balancing zu verteilen. Weitere MQTT-5-Neuerungen wie Topic Aliases findest du unter MQTT 5 vs. 3.1.1.

Eine Topic-Hierarchie entwerfen

Eine gute Hierarchie geht vom Allgemeinen zum Speziellen, sodass Wildcards am Ende sinnvolle Gruppen auswählen. Ein verbreitetes Muster ist:

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

Mit diesem Aufbau gilt:

  • acme/berlin/# liefert einem Standort-Dashboard alles aus Berlin.
  • acme/+/thermostat/+/temperature liefert einem Analytics-Service jeden Thermostat-Messwert.
  • acme/berlin/thermostat/th-0193/# liefert den kompletten Stream eines einzelnen Geräts.

Best Practices

  1. Beginne nicht mit einem Schrägstrich. Er fügt eine sinnlose leere Ebene hinzu.
  2. Nutze Kleinbuchstaben, ASCII und Bindestriche. Vermeide Leerzeichen und gemischte Groß-/Kleinschreibung; sie führen zu subtilen Fehlzuordnungen.
  3. Nimm die Geräte- oder Client-ID ins Topic auf. Das macht Zugriffskontrolle pro Client einfach (viele Broker können die Client-ID oder den Benutzernamen in ACL-Patterns einsetzen).
  4. Trenne Daten, Befehle und Zustand. Zum Beispiel .../temperature für Telemetrie, .../setpoint/set für Befehle und .../status für Retained-Zustand oder Präsenz per Last Will.
  5. Halte Topics kurz, aber aussagekräftig. Das Topic wird mit jeder Nachricht übertragen (außer du nutzt Topic Aliases aus MQTT 5), daher kosten sehr lange Namen auf schmalbandigen Verbindungen Bandbreite.
  6. Kodiere keine Daten im Topic. Werte gehören in die Payload; ein Topic pro Wert erzeugt unbegrenzt viele Topics und massenhaft Retained Messages.
  7. Versioniere dein Schema, falls es sich ändern kann. Ein Präfix wie v1/ lässt alte und neue Geräte während einer Migration nebeneinander existieren.
  8. Vermeide breite Subscriptions in Produktion. Wenn ein Backend-Service # abonniert, wird jede Nachricht auf dem Broker zu seinem Problem.

Häufige Fehler bei Topics

  • Erwarten, dass a/+ auf a passt. Tut es nicht – + verlangt, dass eine Ebene vorhanden ist. Nutze a/#, wenn du auch die übergeordnete Ebene willst.
  • Abschließende Schrägstriche. home/kitchen/ hat eine leere letzte Ebene und ist ein anderes Topic als home/kitchen.
  • Überlappende Subscriptions. Abonniert ein Client sowohl home/# als auch home/+/temperature, erlaubt die Spezifikation dem Broker, entweder eine einzige Kopie oder eine Kopie pro passender Subscription zuzustellen – manche Broker liefern dir also Duplikate. Halte Subscriptions nach Möglichkeit überschneidungsfrei.
  • Gemischte ID-Formate. Wenn manche Geräte als Sensor_17 und andere als sensor-17 publishen, muss jeder Filter und jede ACL-Regel beides abdecken. Normalisiere IDs schon beim Provisioning.
  • Topics als Datenbank behandeln. Broker sind fürs Routing optimiert, nicht für Abfragen. Wenn du Historie brauchst, lass einen Consumer abonnieren und die Nachrichten in eine Datenbank schreiben.

Topics und Zugriffskontrolle

Broker autorisieren Publish- und Subscribe-Operationen pro Topic, deine Hierarchie ist also gleichzeitig dein Berechtigungsmodell. Schreibt jedes Gerät nur unterhalb von acme/<site>/<type>/<device-id>/, kannst du ihm genau diesen Teilbaum freigeben und sonst nichts. Sind die Topics flach oder inkonsistent, lassen sich keine engen Regeln mehr schreiben, und Teams geben am Ende jedem # frei.

Probier es selbst aus

Öffne den Online-MQTT-Client, abonniere testmqtt/demo/+/temperature und publishe an testmqtt/demo/kitchen/temperature und testmqtt/demo/kitchen/oven/temperature. Nur die erste Nachricht kommt an – genau wie es die +-Regel vorhersagt.

Im Online-MQTT-Client ausprobieren

Häufig gestellte Fragen

Was ist der Unterschied zwischen + und # in MQTT?

Die Wildcard + passt auf genau eine Topic-Ebene und darf überall im Filter stehen. Die Wildcard # passt auf beliebig viele Ebenen, auch auf null, und muss das letzte Zeichen des Filters sein.

Kann ich an ein Topic publishen, das eine Wildcard enthält?

Nein. Wildcards sind nur in Subscription-Filtern erlaubt. Ein PUBLISH mit + oder # im Topic-Namen ist ein Protokollfehler, und der Broker schließt die Verbindung.

Enthält eine Subscription auf # auch $SYS-Topics?

Nein. Filter, die mit einer Wildcard beginnen, passen nicht auf Topics, die mit $ anfangen. Um Broker-Statistiken zu erhalten, musst du explizit abonnieren, zum Beispiel $SYS/#.