MQTT, das „Message Queue Telemetry Protocol“, ist ein Messaging-Protokoll, das in den letzten Jahren zunehmend beliebter geworden ist und sich zu einem wichtigen Bestandteil vieler Internet-of-Things (IoT)-Lösungen etabliert hat. Dabei ist das Protokoll gar nicht mehr so neu: Ursprünglich wurde MQTT bereits im Jahr 1999 entwickelt. Die beiden Firmen IBM und Arcom Control entwarfen das Protokoll im Rahmen eines Projekts zur Überwachung von Öl-Pipelines.

Die Anforderungen an das Protokoll waren unter anderem, dass es zum einen (aufgrund des physischen Standortes der Öl-Pipelines) auch in unzuverlässigen Netzwerken funktioniert, sprich in solchen, in denen es zu Verbindungsunterbrüchen kommen kann, und zum anderen in Netzwerken, die nur über eine niedrige Bandbreite verfügen und zusätzlich mit hoher Latenz „zu kämpfen“ haben.

Eigenschaften von MQTT

MQTT wurde daher von vorneherein als leichtgewichtiges Messaging-Protokoll entworfen. Eine Eigenschaft, die mitverantwortlich für den Erfolg im IoT-Bereich sein dürfte. So funktioniert MQTT insbesondere auch auf Endgeräten, die nur über vergleichsweise sehr wenig Systemressourcen verfügen, beispielsweise auf Mikrocontrollern wie dem Arduino oder auf Mini-Computern wie dem Raspberry Pi. Durch diese Eigenschaft hebt sich MQTT von anderen eher „schwergewichtigen“ Messaging-Protokollen wie dem von Apache Kafka ab: MQTT-Clients benötigen deutlich weniger Ressourcen als Kafka-Clients.

Darüber hinaus unterstützt MQTT verschiedene sogenannte Servicequalitätslevel („Quality of Service Level“), deren Einsatz abhängig von der Stabilität des Netzwerks sinnvoll sein kann. So lässt sich hierüber steuern, ob Nachrichten höchstens einmal (QoS = 0), mindestens einmal (QoS = 1) oder exakt einmal (QoS = 2) übertragen werden. Die Übertragung der Daten ist zudem extrem effizient: So verwendet MQTT ein Binärformat, welches eine sehr kompakte Übertragung ermöglicht.

Um mit Verbindungsabbrüchen zwischen Clients und Server (dem „MQTT-Broker“) umgehen zu können, verfügt MQTT zudem über Sessions: Nachrichten, die aufgrund eines Verbindungsabbruchs nicht zugestellt werden konnten, können mit Hilfe von Sessions zwischengespeichert und zu einem späteren Zeitpunkt gesendet werden, wenn die entsprechende Verbindung wiederhergestellt wurde.

Große Auswahl an günstigen Domain-Endungen – schon ab 0,08 € /Monat
Jetzt Domain-Check starten

Publish / Subscribe

MQTT verwendet das sogenannte Publish/Subscribe Messaging Pattern, welches auch kurz als Pub/Sub bezeichnet wird: Die als Publisher bezeichneten MQTT-Clients können Nachrichten unter sogenannten Topics an den MQTT-Broker schicken. Topics sind dabei einfache Zeichenketten, die ähnlich wie URLs hierarchisch aufgebaut sein können, wobei einzelne Hierarchieebenen durch einen Backslash getrennt werden (z.B. factory1/hall2/sensorA oder this/is/an/example/topic).

Diejenigen MQTT-Clients, die sich für ein Topic (oder mehrere Topics) registrieren und hierunter auf Nachrichten „lauschen“, werden dagegen Subscriber genannt. Der Broker fungiert dann als „Mediator“ zwischen Publishern und Subscribern: Er empfängt Nachrichten (von den Publishern) unter den jeweiligen Topics und leitet sie entsprechend an die Subscriber weiter, die sich für das jeweilige Topic registriert haben. Alle MQTT-Clients (sowohl Publisher als auch Subscriber) sind dabei mit dem Broker über eine bestehende TCP-Verbindung verbunden, über die der Broker Nachrichten an die Clients senden kann („Push-Verfahren“).

Subscriber haben die Möglichkeit, bei der Registrierung für Topics sogenannte Wildcards zu verwenden. Auf diese Weise können sich Subscriber relativ einfach direkt für mehrere Topics „subscriben“. Zur Verfügung stehen dabei zwei Wildcard-Operatoren, wobei der Unterschied der beiden darin liegt, wie viele Topic-Ebenen „abgedeckt“ werden: so deckt der Operator # direkt mehrere Topic-Ebenen ab, der Operator + dagegen nur eine Ebene.

Dazu ein Beispiel: Registriert sich ein Subscriber für das Topic factory/*/temperature-sensor, würde er sowohl die Nachrichten erhalten, die unter dem Topic factory/hall1/temperature-sensor eingehen, als auch die Nachrichten, die unter dem Topic factory/hall1/machine1/temperature-sensor eingehen. Würde er sich stattdessen für das Topic factory/+/temperature-sensor registrieren, würde er alle Nachrichten erhalten, die unter factory/hall1/temperature-sensor, factory/hall2/temperature-sensor, etc. eingehen, nicht aber Nachrichten, die unter factory/hall1/machine1/temperature-sensor eingehen.

Jedes Mal, wenn eine neue Nachricht von einem Publisher unter einem Topic eingeht, leitet der Broker diese Nachricht also an die Subscriber weiter. Auf diese Weise werden der Sender einer Nachricht und der Empfänger einer Nachricht voneinander entkoppelt, weil sie nicht direkt miteinander kommunizieren. Der Vorteil davon ist die „lose Kopplung“ der verschiedenen Komponenten. Einzelne Komponenten sind nicht direkt voneinander abhängig bzw. müssen sich nicht kennen. Dies erlaubt es, relativ einfach neue Komponenten in ein System hinzuzufügen.

MQTT-Broker

Mittlerweile gibt es verschiedene MQTT-Broker auf dem Markt. Einer der bekanntesten und am weitesten verbreiteten ist Eclipse Mosquitto. Dieser Broker wird unter einer Open-Source-Lizenz unterhalb der Eclipse Foundation bereitgestellt und maßgeblich von der Cedalo AG weiterentwickelt. Mosquitto steht für alle größeren Betriebssysteme zur Verfügung: so finden sich auf der Homepage Installationsdateien für Linux, Windows und macOS.

Die einfachste Möglichkeit, Mosquitto zu installieren führt jedoch über Docker. Von Docker Hub steht dazu ein offizielles Docker Image zur Verfügung. Wer den Broker direkt mit anderen nützlichen Open-Source-Tools installieren möchte (etwa der No-Code-Plattform Eclipse Streamsheets oder einer graphischen Weboberfläche für Mosquitto), greift auf den offiziellen (ebenfalls Docker-basierten) Installer zurück. Eine Installationsanleitung für die verschiedenen Betriebssysteme (Windows, Linux, macOS, Raspberry Pi) findet sich hier.

Alternativ zur Verwendung des Installers kann Eclipse Mosquitto natürlich auch direkt über Docker aufgesetzt werden. Folgendes Listing beispielsweise zeigt ein einfaches Setup für Docker Compose, welcher sich über den Befehl docker-compose -f docker-compose.yml up starten lässt (ein installiertes Docker und Docker Compose vorausgesetzt). Anschließend lädt Docker – soweit noch nicht auf dem jeweiligen System vorhanden – das Docker Image von Docker Hub herunter, erzeugt einen Docker Container und startet diesen.

Alternativ lässt sich Eclipse Mosquitto auch ohne Docker Compose mit einem einzelnen Docker-Befehl starten. Genauere Informationen dazu finden sich auf der offiziellen Webseite bei Docker-Hub.

docker run -it -p 1883:1883 -p 9001:9001 eclipse-mosquitto

Anschließend kann der Broker unter Port 1883 über MQTT und unter Port 9001 über „MQTT over WebSockets“ erreicht werden. Letzteres ist beispielsweise dann notwendig, wenn eine Verbindung zum Broker direkt aus einem Browser hergestellt werden soll. In diesem Fall kann die Verbindung nicht direkt über MQTT erfolgen. Stattdessen muss man WebSockets als zugrundeliegendes Protokoll verwenden.

MQTT-Clients

MQTT-Clients gibt es mittlerweile nahezu für jede Programmiersprache. Für Node.js ist hier insbesondere das Package MQTT.js interessant. Das Package lässt sich wie gewohnt entweder über den offiziellen Package Manager von Node.js (npm) installieren (npm install mqtt) oder über einen alternativen Package Manager wie Yarn (yarn add mqtt).

Einen Publisher implementieren

Listing 1 zeigt, wie sich MQTT.js verwenden lässt, um eine Verbindung zu einem MQTT-Broker herzustellen und anschließend unter einem Topic eine Nachricht zu veröffentlichen. Zunächst wird das Package über den import-Befehl importiert. Hierbei handelt es sich bereits um die neue ES-Modulsyntax, die für Node.js nur dann funktioniert, wenn in der package.json-Datei der Eintrag type auf den Wert module gesetzt wird. Alternativ kann man das Package natürlich auch wie gewohnt über die globale require()-Funktion laden (die allerdings nicht zur Verfügung steht, wenn man oben genannte Änderung an der package.json-Datei vorgenommen hat).

Hostname, Port, die optionale Client-ID sowie Nutzername und Passwort werden im Beispiel über Konstanten definiert, sollten im Produktivsystem aber entweder aus Konfigurationsdateien, Umgebungsvariablen oder Programmparametern ausgelesen werden (vergleiche auch die entsprechenden Best Practices der sogenannten „Twelve Factor Apps“). Unter Verwendung der Konstanten wird im Anschluss die Methode connect() aufgerufen, die ein client-Objekt zurückgibt, welches den MQTT-Client repräsentiert.

An diesem Client lässt sich nun über die Methode on() ein Event-Listener für das connect-Event registrieren. Dieses Event wird ausgelöst, wenn die Verbindung zum Broker erfolgreich hergestellt wurde. Innerhalb des Event-Listeners wird zu Demonstrationszwecken über setInterval() jede Sekunde ein zufälliger Wert zwischen 0 und 20 generiert und dieser in Form eines Objekts über die Client-Methode publish() an den Broker gesendet. Als ersten Parameter übergibt man dabei das Topic, unter der die Nachricht „gepublisht“ werden soll, als zweiten Parameter die Nachricht als solche. Im Beispiel muss das JavaScript- bzw. JSON-Objekt, welches die Nachricht repräsentiert, zunächst über JSON.stringify() in eine Zeichenkette umgewandelt werden, da MQTT JSON als Datenformat nicht kennt.

Listing 1: MQTT-Publisher in Node.js

Einen Subscriber implementieren

Listing 2 zeigt den Code für die Subscriber-Seite: Wie zuvor wird zunächst das Package importiert, die Verbindungsdaten über Konstanten definiert und die Verbindung zum Broker über den Aufruf der Methode connect() hergestellt. Im Event-Listener für das connect-Event wird jetzt allerdings nicht die Methode publish() aufgerufen. Stattdessen wird der Client über den Aufruf der Methode subscribe() am Broker als Subscriber für das entsprechende Topic registriert. Als Parameter ist hierbei das Topic zu übergeben, auf welches „gehört“ werden soll.

Was genau passieren soll, wenn eine Nachricht unter diesem Topic eingeht, kann über entsprechende Event-Listener für das message-Event gesteuert werden. Event-Listener für dieses Event werden jeweils mit dem Topic und der eingehenden Nachricht aufgerufen (die Angabe des Topics ist hier beispielsweise hilfreich, um das konkrete Topic herauszufinden, für den Fall, dass der Subscriber sich zuvor für ein Wildcard-Topic registriert hat). Da die eingehende Nachricht als Zeichenkette ankommt, wird diese im Beispiel über JSON.parse() in ein JSON-Objekt umgewandelt, um auf die einzelnen Eigenschaften zugreifen zu können.

Listing 2: MQTT-Subscriber in Node.js

Weitere Features

Neben den eingangs genannten Features stellt MQTT noch eine Reihe weiterer zur Verfügung. So können Clients, wenn sie eine Verbindung zum Broker herstellen, über das Last Will and Testament-Feature eine sogenannte „Last Will Message“ festlegen. Diese Nachricht wird vom Broker gespeichert und nur dann an die anderen Clients geschickt, falls die Verbindung zum Client nicht ordnungsgemäß getrennt wurde, beispielsweise durch einen unerwarteten Verbindungsabbruch.

Sogenannte Retained Messages ermöglichen es, Nachrichten unter einem Topic zu speichern. Der Broker speichert dabei jeweils die zuletzt unter dem Topic eingegangene Nachricht. Wenn sich nun ein Client für das Topic registriert, wird die gespeicherte Nachricht direkt an ihn geschickt. Dies kann immer dann sinnvoll sein, wenn unter einem Topic sehr selten neue Nachrichten eingehen (beispielsweise bei Sensoren, die nur einmal pro Stunde einen Wert senden), neue Subscriber aber trotzdem sofort die aktuellste Nachricht erhalten sollen.

Neu in MQTT 5

Im Jahr 2019 wurde mit MQTT 5 die lange erwartete Nachfolgerversion von MQTT 3.1.1 veröffentlicht, welche das Protokoll um zahlreiche weitere Features erweitert. Shared Subscriptions ermöglichen es beispielsweise, dass sich die Subscriber eines Topics das Topic „teilen“. Während bei normalen Subscriptions alle Subscriber eines Topics auch alle Nachrichten erhalten, die unter dem Topic eingehen, lassen sich mit Hilfe von Shared Subscriptions die Nachrichten auf mehrere Subscriber verteilen. Dies kann immer dann sinnvoll sein, wenn so viele Nachrichten unter einem Topic eingehen, dass ein einzelner Subscriber mit der Verarbeitung der Nachrichten überlastet wäre.

Die eingangs erwähnte Sessionfähigkeit ist eigentlich eine der Stärken von MQTT: Wird die Verbindung zwischen einem Client und dem MQTT-Broker unterbrochen, speichert der Broker Sessioninformationen zu dem Client wie etwa die aktiven Subscriptions und nicht übermittelte Nachrichten. Für den Fall jedoch, dass eine unterbrochene Verbindung nicht wiederhergestellt wird, sieht MQTT 3.1.1 keine Möglichkeit vor, einen Ablaufzeitpunkt für Sessions zu definieren. Mit dem in MQTT 5 eingeführten Feature Session Expiry lässt sich nun angeben, nach welchem Zeitintervall die Sessioninformationen ablaufen und damit vom Broker gelöscht werden.

Ebenfalls neu in MQTT 5 ist die Möglichkeit, auch für Nachrichten ein Zeitintervall zu definieren, nach dem die jeweilige Nachricht nicht mehr gültig ist (Message Expiry). Darüber hinaus bietet MQTT 5 unter anderem erweiterte Mechanismen zur Authentifizierung, Request/Response-Funktionalität sowie die client- und serverseitige Angabe der maximalen Größe von Nachrichten.

Fazit

Das MQTT-Protokoll überzeugt durch seine Leichtgewichtigkeit und ist insbesondere deswegen im IoT-Bereich sehr beliebt. Eclipse Mosquitto ist einer der am meisten verbreitetsten MQTT-Broker, unterstützt sowohl MQTT 3.1.1 als auch MQTT 5 und kann sowohl in der Cloud als auch auf Edge-Devices verwendet werden. Für Node.js steht mit MQTT.js ein stabiles Package zur Verfügung, über das sich Publisher und Subscriber implementieren lassen. Mit diesen Komponenten können Messaging-Szenarien mit vergleichsweise wenig Programmieraufwand umgesetzt werden. Wer nicht selbst programmieren möchte oder kann, hat zudem die Möglichkeit, MQTT über eine No-Code-Plattform wie die Eclipse Streamsheets zu integrieren.

Philip Ackermann

Große Auswahl an günstigen Domain-Endungen – schon ab 0,08 € /Monat
Jetzt Domain-Check starten