Doppelte MQTT-Nachrichten sind kein Fehler Ihres Brokers, sondern eine dokumentierte Folge von QoS 1, das die Zustellung „mindestens einmal“ verspricht. Verschluckt eine Netzwerkstörung eine Bestätigung, sendet der Absender erneut, und Ihr Konsument sieht dieselbe Nachricht zweimal. In der Fertigung ist das kein theoretisches Problem: Ein doppelt gezählter Stückzähler verfälscht die OEE, ein doppelt gebuchter Energiewert die Abrechnung pro Kostenstelle. In diesem Leitfaden zur Fehlersuche erfahren Sie genau, wann Duplikate entstehen, was das DUP-Flag Ihnen sagt und was nicht, und wie Sie einen idempotenten Python-Konsumenten schreiben, der korrekt damit umgeht.
Warum QoS 1 Duplikate erzeugt
QoS 1 nutzt auf jedem Hop einen zweistufigen Handshake: Der Absender überträgt PUBLISH, der Empfänger antwortet mit PUBACK. Bis das PUBACK eintrifft, behält der Absender die Nachricht und sendet sie gegebenenfalls erneut. Der Artikel MQTT-QoS-Stufen erklärt behandelt alle drei Stufen; hier konzentrieren wir uns darauf, was bei Stufe 1 schiefgeht.
Ein Duplikat entsteht immer dann, wenn der Empfänger die Nachricht erhalten hat, der Absender aber nicht die Bestätigung:
- Das Gerät publiziert Messwert #42 an den Broker.
- Der Broker empfängt ihn, leitet ihn an Subscriber weiter und sendet PUBACK.
- Die Verbindung bricht ab, bevor das PUBACK das Gerät erreicht.
- Das Gerät verbindet sich mit seiner Session neu und sendet #42 mit gesetztem DUP-Flag erneut.
- Der Broker behandelt sie als neue Nachricht und leitet sie erneut weiter.
Dasselbe passiert auf dem Hop vom Broker zum Subscriber. Hat der Subscriber eine Nachricht verarbeitet, sein PUBACK ging aber verloren, sendet der Broker sie erneut, sobald die Session des Subscribers fortgesetzt wird.
Beachten Sie, dass auf jedem Hop unabhängig ein Duplikat entstehen kann, sodass eine publizierte Nachricht einen Konsumenten gelegentlich auch mehr als zweimal erreicht.
Was das DUP-Flag Ihnen sagt
Jedes PUBLISH-Paket hat ein DUP-Bit. Ein Absender setzt es, wenn er ein QoS-1- oder QoS-2-Paket erneut überträgt. Es ist verlockend, jede Nachricht mit gesetztem DUP zu verwerfen, aber das ist aus mehreren Gründen falsch:
| Annahme | Realität |
|---|---|
| DUP heißt, ich habe diese Nachricht schon | Nicht unbedingt. Die erste Kopie kann verloren gegangen sein, sodass die Wiederholung die einzige Kopie ist, die Sie erhalten |
| Kein DUP heißt, sie ist neu | Nein. Der Broker leitet die Wiederholung eines Publishers als frische Zustellung weiter, ohne DUP auf dem ausgehenden Hop |
| Packet Identifiers erkennen Duplikate | Nur innerhalb einer Session und eines In-flight-Fensters. IDs werden ständig wiederverwendet |
| QoS 2 löst alles | QoS 2 verhindert Duplikate pro Hop, Wiederholungen auf Anwendungsebene können aber trotzdem welche erzeugen |
Das DUP-Flag ist nützlich für Logging und Diagnose. Ein Deduplizierungsschlüssel ist es nicht. Echte Deduplizierung braucht eine Kennung, die Sie selbst in die Nachricht schreiben.
Duplikate reproduzieren
Echte erneute Zustellung hängt von verlorenen Paketen ab und lässt sich kaum gezielt auslösen. Der zuverlässige Weg, Ihren Konsumenten zu testen, ist, Duplikate selbst einzuschleusen. Publizieren Sie gegen den öffentlichen MQTT-Broker unter broker.connectmq.com dieselbe Nachrichten-ID zweimal:
for n in 1 2; do
mosquitto_pub -h broker.connectmq.com -p 1883 -i dup-pub-t9v4 -q 1 \
-t connectmq/demo/t9v4/meter-3/energy \
-m '{"msgId":"meter-3-000042","kwh":0.25}'
done
Ein Konsument, der kwh ohne Deduplizierung aufsummiert, meldet jetzt 0,5 statt 0,25. Genau diese Art von Fehler verursacht die erneute QoS-1-Zustellung in der Produktion – nur seltener und schwerer zu entdecken.
Einen idempotenten Konsumenten bauen
Ein idempotenter Konsument liefert dasselbe Ergebnis, ob er eine Nachricht einmal oder fünfmal verarbeitet. Es gibt zwei gängige Techniken:
- Natürliche Idempotenz. Gestalten Sie die Wirkung so, dass Wiederholung harmlos ist: „Temperatur auf 21,5 setzen“ statt „um 0,5 erhöhen“; eine Zeile per Upsert mit Gerät und Zeitstempel als Schlüssel schreiben statt einzufügen.
- Explizite Deduplizierung. Geben Sie jeder Nachricht eine eindeutige ID und merken Sie sich kürzlich verarbeitete IDs.
Hier ein vollständiger Konsument mit paho-mqtt 2.x, der explizite Deduplizierung mit einem begrenzten, zeitlich befristeten Cache nutzt:
import json
import time
import uuid
from collections import OrderedDict
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/t9v4/+/energy"
DEDUP_TTL = 600 # Sekunden, die eine ID gemerkt wird
DEDUP_MAX = 50_000 # Speicherverbrauch begrenzen
seen = OrderedDict() # msgId -> Zeitpunkt der ersten Sichtung
totals = {}
def already_seen(msg_id):
now = time.time()
while seen:
oldest_id, ts = next(iter(seen.items()))
if now - ts > DEDUP_TTL or len(seen) > DEDUP_MAX:
seen.popitem(last=False)
else:
break
if msg_id in seen:
return True
seen[msg_id] = now
return False
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe(TOPIC, qos=1)
def on_message(client, userdata, msg):
try:
data = json.loads(msg.payload)
msg_id = data["msgId"]
except (ValueError, KeyError):
print("rejected message without msgId")
return
if already_seen(msg_id):
print(f"duplicate {msg_id} (dup flag={msg.dup}) ignored")
return
meter = msg.topic.split("/")[-2]
totals[meter] = totals.get(meter, 0) + data["kwh"]
print(f"{meter} total={totals[meter]:.2f} kWh")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"dedup-{uuid.uuid4().hex[:8]}")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()
Starten Sie ihn und führen Sie dann die Doppel-Publish-Schleife aus dem vorherigen Abschnitt aus. Die erste Nachricht aktualisiert die Summe; die zweite wird als Duplikat gemeldet und ignoriert.
Eine gute Nachrichten-ID wählen
Die ID muss vom ursprünglichen Publisher einmal erzeugt und bei jeder Wiederholung wiederverwendet werden. Optionen:
- Geräte-ID plus Sequenznummer, etwa
meter-3-000042. Kompakt und zudem nützlich für Prüfungen der MQTT-Nachrichtenreihenfolge. Persistieren Sie den Zähler oder ergänzen Sie eine Boot-ID, damit er sich nach einem Neustart nicht wiederholt. - UUID pro Nachricht. Einfach und global eindeutig, auf Kosten von 36 Bytes.
- Geräte-ID plus Messzeitstempel, sofern das Gerät nie zwei Messwerte mit demselben Zeitstempel erzeugt.
Erzeugen Sie die ID nicht im Konsumenten oder in einer Zwischenschicht; dort sieht ein Duplikat bereits wie eine neue Nachricht aus.
Deduplizierung skalieren
Der obige In-Memory-Cache funktioniert für einen einzelnen Konsumentenprozess. Für größere Systeme:
- Datenbank-Constraints. Ein eindeutiger Index auf
msg_idmacht Duplikate zu harmlosen Konflikten. Mit einem Upsert (INSERT ... ON CONFLICT DO NOTHINGin PostgreSQL) ist das oft die einzige Deduplizierung, die Sie brauchen. - Gemeinsamer Cache. Speist ein Konsument ein MES oder ERP, etwa mit gemeldeten Stückzahlen pro Auftrag, gehört die Deduplizierung vor die Buchung, nicht in einen nachträglichen Abgleich. Können mehrere Konsumenten einer Shared Subscription Wiederholungen derselben Nachricht erhalten, muss die Menge gesehener IDs geteilt werden, etwa in Redis mit TTL.
- TTL dimensionieren. Das Fenster muss nur realistische Verzögerungen bei der erneuten Zustellung abdecken: Ihr Session Expiry plus Puffer. Duplikate kommen selten Stunden auseinander an, es sei denn, Sessions leben so lange.
Sollten Sie einfach QoS 2 verwenden?
QoS 2 ergänzt einen vierstufigen Handshake, der Exactly-once-Zustellung auf jedem Hop garantiert. Das kostet mehr Round-Trips und Broker-Zustand und schützt Sie nicht vor einem Gerät, das in einen Timeout läuft und denselben Messwert auf Anwendungsebene als neue Nachricht erneut publiziert. In eingeschränkten Plänen ist es womöglich gar nicht verfügbar; so unterstützt der kostenlose ConnectMQ-Serverless-Plan QoS bis 1, während der kostenpflichtige Serverless-Plan QoS 2 ergänzt. Wie Broker QoS ankündigen und herabstufen, erklärt MQTT Maximum QoS. In den meisten Systemen ist QoS 1 plus idempotente Konsumenten das robustere und günstigere Design.
Checkliste zur Fehlersuche
- Treten Duplikate zusammen mit Reconnects auf? Prüfen Sie die Client-Logs auf Verbindungsabbrüche und Keepalive-Timeouts.
- Wiederholt Ihr Publisher zusätzlich zu QoS 1 auf Anwendungsebene? Das verdoppelt die Wiederholungspfade.
- Teilen sich zwei Clients eine Client ID und werfen sich gegenseitig hinaus, was ständige Session-Wiederaufnahmen auslöst? Siehe MQTT-Client trennt ständig die Verbindung.
- Bestätigt der Konsument langsam? Lange Verarbeitung im Callback verzögert das PUBACK und vergrößert das Fenster für erneute Zustellung.
- Trägt jede Nachricht eine an der Quelle erzeugte ID?
Duplikate und Ihre Broker-Wahl
ConnectMQ implementiert die Standardsemantik von QoS 1 und QoS 2, die obigen Ratschläge gelten also direkt. Da Client IDs an ausgestellte Zugangsdaten gebunden sind, lässt sich die Reconnect-Schleife durch doppelte Client IDs, die oft massenhafte erneute Zustellung verursacht, leichter vermeiden. Ein verwalteter MQTT-Broker übernimmt Session-Speicherung und Wiederholungen für Sie; die Idempotenz Ihrer Konsumenten bleibt auf jedem Broker Ihre Verantwortung. Das MQTT-Tutorial ist ein guter nächster Schritt für das Gesamtbild.
Starten Sie mit ConnectMQ
Erstellen Sie unter app.connectmq.com einen kostenlosen Broker im Serverless-Plan, ganz ohne Kreditkarte. Richten Sie den deduplizierenden Konsumenten mit Ihrem ConnectMQ-Benutzernamen, Passwort und Ihrer Client ID darauf aus, und Ihre Summen bleiben korrekt, auch wenn das Netzwerk es nicht ist.
Häufig gestellte Fragen
Warum erhalte ich dieselbe MQTT-Nachricht zweimal?
Bei QoS 1 führt eine verlorene Bestätigung dazu, dass der Absender erneut sendet. Der Empfänger erhält die Nachricht noch einmal, was die At-least-once-Garantie erlaubt.
Kann ich mich auf das DUP-Flag verlassen, um Duplikate zu verwerfen?
Nein. DUP kennzeichnet eine erneute Übertragung auf einem Hop, doch die erste Kopie ist womöglich nie angekommen, und der Broker reicht das Flag nicht an Subscriber durch.
Erzeugt QoS 0 Duplikate?
Nein, QoS 0 wird vom Protokoll nie erneut übertragen. Stattdessen können Nachrichten verloren gehen.
Wie lange sollte ich mir Nachrichten-IDs merken?
Lange genug, um Verzögerungen bei Reconnect und Session-Wiederaufnahme abzudecken, typischerweise Minuten. Bemessen Sie es anhand Ihres Session Expiry Interval plus einer Sicherheitsmarge.



