Die MQTT-Nachrichtenreihenfolge ist garantiert – aber nur innerhalb bestimmter Grenzen, die viele Entwickler nie nachlesen. Nachrichten eines Publishers auf einem Topic mit einer QoS-Stufe kommen in Reihenfolge an; fast alles darüber hinaus ist nicht zugesagt. Dieser Artikel erklärt genau, was die Spezifikation garantiert, in welchen gängigen Situationen Nachrichten scheinbar ungeordnet ankommen und wie Sie Sequenznummern ergänzen, damit Ihre Konsumenten das erkennen und behandeln können.
Was die MQTT-Spezifikation garantiert
Sowohl MQTT 3.1.1 als auch MQTT 5 definieren ein „Ordered Topic“. Standardmäßig muss ein Broker jedes Topic als geordnet behandeln, woraus diese Garantie folgt:
Nachrichten, die ein Client auf einem Topic mit derselben QoS-Stufe publiziert, werden jedem Subscriber in der Reihenfolge zugestellt, in der sie publiziert wurden.
Die Spezifikation verlangt außerdem, dass ein Client unbestätigte QoS-1- und QoS-2-Nachrichten nach einem Reconnect in der ursprünglichen Reihenfolge erneut sendet und dass Bestätigungen in der Reihenfolge verschickt werden, in der die Pakete empfangen wurden.
Liest man diese Garantie genau, werden die Lücken offensichtlich. Sie sagt nichts über:
- Nachrichten verschiedener Publisher auf demselben Topic.
- Nachrichten eines Publishers auf verschiedenen Topics.
- Nachrichten eines Publishers auf demselben Topic, aber mit unterschiedlichen QoS-Stufen.
- Was Ihre Anwendung tut, nachdem die Client-Bibliothek die Nachricht übergeben hat.
Wo die MQTT-Nachrichtenreihenfolge nicht mehr gilt
| Situation | Reihenfolge garantiert? | Warum |
|---|---|---|
| Ein Publisher, ein Topic, gleiche QoS | Ja | Die Ordered-Topic-Regel |
| Ein Publisher, zwei Topics | Nein | Jedes Topic ist unabhängig geordnet |
| Zwei Publisher, ein Topic | Nein | Keine globale Uhr; die Ankunftsreihenfolge beim Broker entscheidet |
| Gleiches Topic, gemischt QoS 0 und QoS 1 | Nein | Unterschiedliche Zustellpfade und Wiederholungen |
| QoS 0 über eine abgebrochene Verbindung | Reihenfolge bleibt, aber mit Lücken | Verlorene Nachrichten werden nicht erneut gesendet |
| QoS 1 mit erneuter Zustellung | Duplikate möglich | Eine erneut gesendete Nachricht kann auf neuere folgen, vor denen sie schon einmal kam |
| Shared Subscription | Keine globale Reihenfolge | Nachrichten werden auf Konsumenten verteilt |
| Multithreaded-Konsument | Nein | Ihr Worker-Pool sortiert die Verarbeitung um |
Zwei davon verdienen einen genaueren Blick.
Shared Subscriptions. Mit $share/group/topic verteilt der Broker Nachrichten zur Lastverteilung auf mehrere Konsumenten. Jeder Konsument sieht eine Teilmenge, und sie verarbeiten parallel, also gibt es keine sinnvolle globale Reihenfolge. Zählt die Reihenfolge pro Gerät, gestalten Sie das System so, dass die Nachrichten eines Geräts immer denselben Konsumenten erreichen, oder sortieren Sie nachgelagert neu. Unser Leitfaden zu MQTT Shared Subscriptions behandelt diesen Kompromiss.
Erneute Zustellung und Duplikate. QoS 1 bedeutet At-least-once. Geht eine Bestätigung verloren, sendet der Absender mit gesetztem DUP-Flag erneut. Ein Subscriber sieht dann womöglich Nachricht 5 noch einmal, nachdem er bereits 6 und 7 verarbeitet hat. Das ist eher ein Duplikat als eine echte Umsortierung, doch für naiven Code sieht es so aus, als liefe die Zeit rückwärts. Die Lösung beschreibt Umgang mit doppelten MQTT-Nachrichten.
Reihenfolge auf dem öffentlichen MQTT-Broker vorführen
Das schnellste Experiment nutzt den öffentlichen MQTT-Broker unter broker.connectmq.com, der für Tests und Prototypen gedacht ist, nicht für Produktionsdaten. Beachten Sie dessen Limit von 10 Nachrichten pro Sekunde und Client; der folgende Publisher drosselt sich daher selbst.
Subscriber:
mosquitto_sub -h broker.connectmq.com -p 1883 -i ord-sub-h5d3 -q 1 \
-t 'connectmq/demo/h5d3/#' -v
Publisher, ein Topic, QoS 1, nummerierte Nachrichten:
for i in $(seq 1 20); do
mosquitto_pub -h broker.connectmq.com -p 1883 -i ord-pub-h5d3 -q 1 \
-t connectmq/demo/h5d3/seq -m "$i"
sleep 0.2
done
Sie sehen 1 bis 20 in Reihenfolge. Beachten Sie, dass jeder mosquitto_pub-Aufruf eine eigene Verbindung ist; hier sorgt also die sequenzielle Schleife für die Reihenfolge. Starten Sie nun zwei Schleifen parallel auf zwei verschiedenen Topics, seq-a und seq-b. Jedes Topic bleibt in Reihenfolge, aber die Zeilen der beiden Topics verschränken sich unvorhersehbar. Das ist kein Fehler: Topic-übergreifende Reihenfolge ist schlicht nicht Teil der Vereinbarung.
Sequenznummern ergänzen
Wenn die Reihenfolge für Ihre Anwendung zählt, transportieren Sie sie im Payload. Eine monoton steigende Sequenznummer pro Publisher plus Zeitstempel ermöglicht Konsumenten, Lücken, Duplikate und veraltete Nachrichten zu erkennen. Hier ein vollständiges Paar aus Publisher und Konsument mit paho-mqtt 2.x:
import json
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/h5d3/pump-07/telemetry"
last_seq = {} # Gerät -> höchste gesehene Sequenznummer
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
dev, seq = data["device"], data["seq"]
prev = last_seq.get(dev)
if prev is not None and seq <= prev:
print(f"stale or duplicate seq={seq} (last={prev}), dropped")
return
if prev is not None and seq > prev + 1:
print(f"gap: missing {prev + 1}..{seq - 1}")
last_seq[dev] = seq
print(f"apply seq={seq} value={data['value']}")
sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"ord-c-{uuid.uuid4().hex[:6]}")
sub.on_connect = on_connect
sub.on_message = on_message
sub.connect(BROKER, 1883, keepalive=30)
sub.loop_start()
time.sleep(1)
pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"ord-p-{uuid.uuid4().hex[:6]}")
pub.connect(BROKER, 1883, keepalive=30)
pub.loop_start()
for seq in [1, 2, 3, 5, 4, 6]: # 4 absichtlich verspätet gesendet
body = {"device": "pump-07", "seq": seq, "ts": time.time(), "value": 20 + seq}
pub.publish(TOPIC, json.dumps(body), qos=1).wait_for_publish()
time.sleep(0.2)
time.sleep(2)
for c in (pub, sub):
c.loop_stop()
c.disconnect()
Der Konsument wendet 1, 2, 3 an, meldet vor 5 eine Lücke, verwirft die verspätete 4 als veraltet und wendet dann 6 an. Ob Sie verspätete Nachrichten verwerfen, puffern oder akzeptieren, hängt von den Daten ab – womit wir bei der Strategie wären.
Eine Strategie für die Reihenfolge wählen
- Der letzte Wert gewinnt (Zustand). Bei Messwerten wie der Temperatur verwerfen Sie alles, was älter ist als das, was Sie haben. Der obige Code tut genau das.
- Umsortierpuffer (Ereignisströme). Halten Sie Nachrichten kurz zurück und geben Sie sie in Sequenzreihenfolge frei; geben Sie Lücken nach einem Timeout auf. Nützlich, wenn jedes Ereignis zählt, etwa bei Stückzählern für die OEE-Berechnung.
- Idempotente Upserts (Speicherung). Schreiben Sie jede Nachricht mit Gerät und Sequenz oder Zeitstempel als Schlüssel. Die Datenbank ist am Ende unabhängig von der Ankunftsreihenfolge korrekt.
- Ein Topic pro geordnetem Strom. Müssen Befehle in Reihenfolge angewendet werden, publizieren Sie sie auf einem einzigen Topic von einem einzigen Client mit einer einzigen QoS.
- Kein Mischen von QoS in einem Strom. Wählen Sie eine QoS pro Topic.
Das betrifft besonders Industrie-4.0-Architekturen mit einem Unified Namespace (UNS). Dort liegen die Werte einer Maschine nach der ISA-95-Hierarchie auf getrennten Topics, etwa acme/werk-stuttgart/montage/linie-1/zelle-3/status und .../zelle-3/stueckzahl. Weil es keine Topic-übergreifende Reihenfolge gibt, kann ein MES den Zählerstand vor dem zugehörigen Statuswechsel sehen. Müssen Werte zusammen ausgewertet werden, bündeln Sie sie in einer Nachricht mit gemeinsamem Zeitstempel oder führen eine Sequenznummer pro Maschine.
Reihenfolge über Reconnects und Offline-Phasen hinweg
Bei Reconnects werden Annahmen zur Reihenfolge am häufigsten auf die Probe gestellt. Kehrt ein Subscriber mit persistenter Session zurück, sendet der Broker zunächst alle Nachrichten erneut, die unterwegs und unbestätigt waren, liefert dann die während der Offline-Zeit gepufferten Nachrichten aus und fährt anschließend mit dem Live-Verkehr fort. Innerhalb eines Topics von einem Publisher gilt die Reihenfolge weiterhin, doch der Konsument erhält plötzlich einen Schwall älterer Nachrichten, gefolgt von aktuellen.
Zwei Dinge helfen hier. Erstens: Halten Sie die Verarbeitung pro Gerät sequenziell, damit ein Schwall in Reihenfolge angewendet wird, statt von parallelen Workern durcheinandergebracht zu werden. Zweitens: Nutzen Sie Zeitstempel im Payload, um zu entscheiden, wie nachgeholte Daten dargestellt werden: Ein Dashboard springt vielleicht direkt zum neuesten Wert, während ein Historian jeden Messwert mit seiner ursprünglichen Zeit speichern sollte. Sind gepufferte Nachrichten nur begrenzt nützlich, hält ein Message Expiry aus MQTT 5 sehr alte Nachrichten ganz aus dem Schwall heraus.
Auf der Publisher-Seite sollten Geräte, die während eines Netzwerkausfalls Messwerte lokal puffern, den Puffer in Sequenzreihenfolge senden, bevor sie wieder live publizieren – und zwar auf demselben Topic und mit derselben QoS wie im normalen Verkehr.
Fallstricke und Fehlersuche bei der Reihenfolge
Sequenz wird nach einem Neustart zurückgesetzt. Ein Gerät, das nach dem Neustart bei Sequenz 1 beginnt, wirkt so, als sende es veraltete Daten. Fügen Sie der Sequenz eine Boot-ID oder einen Epochenzähler hinzu oder persistieren Sie den Zähler im Flash.
Uhrbasierte Reihenfolge versagt. Geräteuhren driften und springen manchmal auf 1970 zurück. Nutzen Sie bevorzugt Sequenznummern für die Reihenfolge und Zeitstempel für die Anzeige.
Reihenfolge geht in Ihrer App verloren. Asynchrone Handler, Thread-Pools und Queues im Konsumenten sortieren munter um. Verarbeiten Sie den Strom jedes Geräts auf einem einzigen Worker oder partitionieren Sie nach Geräte-ID.
Effekte des In-flight-Fensters. Mit Receive Maximum aus MQTT 5 können mehrere QoS-1-Nachrichten gleichzeitig unterwegs sein. Die Reihenfolge auf der Leitung bleibt erhalten, doch Wiederholungen bei Paketverlust machen Duplikate wahrscheinlicher. Zur Abstimmung siehe den Artikel zu MQTT Receive Maximum.
Reihenfolge auf einem verwalteten MQTT-Broker
ConnectMQ befolgt die MQTT-Reihenfolgeregeln pro Topic für MQTT-3.1.1- und 5.0-Clients. Shared Subscriptions im kostenpflichtigen Serverless-Plan ermöglichen horizontale Skalierung für Konsumenten, mit dem oben beschriebenen üblichen Kompromiss bei der Reihenfolge. Ein verwalteter MQTT-Broker ändert nichts an den Protokollgarantien, nimmt Ihnen aber den Betriebsaufwand ab, sodass Sie sich auf das Konsumentendesign konzentrieren können. Für den breiteren Protokollkontext siehe das MQTT-Tutorial.
Starten Sie mit ConnectMQ
Erstellen Sie unter app.connectmq.com einen Broker im kostenlosen Serverless-Plan, ganz ohne Kreditkarte. Führen Sie das Sequenznummern-Beispiel mit Ihren eigenen ConnectMQ-Zugangsdaten aus, beobachten Sie den Strom im Live-Topic-Explorer des Portals und bringen Sie es dann auf einem Cloud-MQTT-Broker, der mit Ihrer Flotte skaliert, in die Produktion.
Häufig gestellte Fragen
Garantiert MQTT die Nachrichtenreihenfolge?
Ja, für Nachrichten eines Clients auf einem Topic mit derselben QoS. Über Topics, Publisher oder gemischte QoS-Stufen hinweg gibt es keine Garantie.
Garantiert QoS 2 die Reihenfolge?
QoS 2 garantiert Exactly-once-Zustellung pro Hop. Die Reihenfolge folgt weiterhin derselben Regel pro Topic und Publisher.
Warum sehe ich eine ältere Nachricht nach einer neueren?
Meist wegen einer erneuten QoS-1-Zustellung, eines zweiten Publishers oder paralleler Verarbeitung in Ihrem Konsumenten. Mit Sequenznummern lässt sich leicht erkennen, was davon zutrifft.
Sind Shared Subscriptions geordnet?
Nicht global. Jede Nachricht geht an ein Mitglied der Gruppe, und die Mitglieder verarbeiten parallel.



