Ein MQTT-Broker-Ausfall ist keine Frage des Ob, sondern des Wann: Netze zerfallen in Partitionen, Zertifikate werden rotiert, Broker werden aktualisiert, und Cloud-Regionen haben schlechte Tage. Dieser Leitfaden zeigt, wie Sie Geräte- und Backend-Clients so gestalten, dass ein Broker-Ausfall, ob kurz oder lang, keine wichtigen Daten kostet und keine doppelten Aktionen auslöst. Die Muster gelten für jeden Broker, auch für ConnectMQ, und werden mit funktionierendem Python-Code geliefert.
Was bei einem MQTT-Broker-Ausfall tatsächlich passiert
Aus Sicht des Clients äußert sich ein Ausfall auf eine von drei Arten: Die TCP-Verbindung bricht ab, die Verbindung hängt still, bis der Keepalive abläuft, oder neue Verbindungsversuche werden abgelehnt. Die Ursache kann der Broker selbst sein, das Netz zwischen Ihnen und dem Broker, DNS, TLS oder sogar Ihre eigenen Zugangsdaten. Der Client kann das nicht immer unterscheiden und muss daher alle Fälle gleich behandeln: schnell bemerken, lokal weiterarbeiten und sich höflich neu verbinden.
Vier Dinge stehen auf dem Spiel:
- Ausgehende Daten, die während der Trennung publiziert werden, gehen verloren, wenn Sie sie nicht puffern.
- Eingehende Befehle, die gesendet wurden, während Sie offline waren, werden je nach Sitzungseinstellungen zugestellt oder nicht.
- Präsenzinformationen werden falsch, wenn andere Systeme das Gerät noch für online halten.
- Der Broker selbst kann überlastet werden, wenn sich Tausende Geräte im selben Moment neu verbinden.
Gutes Client-Design adressiert jeden dieser Punkte. In der Fertigung gilt zusätzlich: Maschinen und SPS müssen unabhängig vom Broker weiterlaufen. MQTT gehört dort in die Daten- und Überwachungsebene, etwa für Maschinendaten, OEE oder Energiemonitoring, nicht in Sicherheitsfunktionen oder Echtzeitregelkreise. Wenn Ihnen die Begriffe unbekannt sind, lohnt es sich zu lesen, was ein MQTT-Broker mit Sitzungen und wartenden Nachrichten macht.
Muster 1: Reconnect mit exponentiellem Backoff und Jitter
Der naive Ansatz ist, für immer jede Sekunde neu zu versuchen. Multipliziert mit zehntausend Geräten erhält der Broker in dem Moment, in dem er zurückkommt, eine Wand gleichzeitiger Verbindungen, die ihn sofort wieder umwerfen kann. Das ist das Thundering-Herd-Problem.
Die Lösung ist exponentieller Backoff mit zufälligem Jitter: 1 s warten, dann 2 s, 4 s, 8 s und so weiter bis zu einer Obergrenze, und jede Wartezeit randomisieren, damit sich die Geräte verteilen. Paho 2.x bietet mit reconnect_delay_set integrierte Unterstützung, fügt aber keinen Jitter hinzu, weshalb viele Teams die Schleife für volle Kontrolle selbst verwalten.
Achten Sie auch auf Ihre Zugangsdaten. Broker, die IP-Adressen nach wiederholt fehlgeschlagenen Logins sperren, wie ConnectMQ, behandeln ein Gerät, das es mit falschem Passwort jede Sekunde erneut versucht, wie einen Angriff. Fahren Sie bei Authentifizierungsfehlern deutlich aggressiver zurück als bei Netzwerkfehlern und machen Sie sie für einen Operator sichtbar.
Muster 2: ausgehende Daten lokal puffern
MQTT-Client-Bibliotheken halten Inflight-Nachrichten im Arbeitsspeicher, der verloren geht, wenn das Gerät während des Ausfalls neu startet. Für Telemetrie, auf die Sie nicht verzichten können, schreiben Sie jeden Messwert zuerst in einen kleinen lokalen Speicher und löschen ihn erst, wenn der Broker ihn mit einem PUBACK bestätigt. SQLite passt gut auf Gateways und Linux-Geräte; auf Mikrocontrollern erfüllt ein Ringpuffer im Flash denselben Zweck. Bei Edge-Gateways im Werk, die SPS-Daten in die Cloud weiterreichen, ist dieses Muster als Store and Forward bekannt.
Legen Sie vorab fest, was passiert, wenn der Puffer voll ist. Für die meiste Telemetrie ist es richtig, die ältesten Messwerte zu verwerfen. Bei Abrechnungs- oder Sicherheitsereignissen ziehen Sie es womöglich vor, stattdessen die Erfassung unkritischer Daten einzustellen.
Muster 3: Präsenz mit Last Will und Retained-Status melden
Setzen Sie beim Verbinden ein Last Will and Testament (LWT): eine Retained-Nachricht offline, die der Broker in Ihrem Namen publiziert, wenn Sie ohne sauberes Disconnect verschwinden. Publizieren Sie bei jedem erfolgreichen Verbindungsaufbau ein Retained online auf dasselbe Topic. Andere Systeme sehen dann eine korrekte Präsenz, ohne abfragen zu müssen.
Seien Sie bei der LWT-Semantik präzise. Der Will wird publiziert, wenn der Broker erkennt, dass die Sitzung unnormal geendet hat, meist nach dem 1,5-Fachen des Keepalive-Intervalls. Ist der Broker selbst ausgefallen, wird nichts publiziert, bis er zurück ist, und nach einem Broker-Neustart müssen die Clients den Retained-Zustand womöglich neu herstellen. MQTT 5 ergänzt ein Will Delay Interval, mit dem Sie flatternde Offline-Meldungen während eines kurzen Reconnects unterdrücken können.
Muster 4: Befehle idempotent machen
Mit QoS 1 wird eine Nachricht mindestens einmal zugestellt. Bei Ausfall und Reconnect werden Duplikate wahrscheinlich. Ein zweimal zugestellter Befehl „Relais umschalten" schaltet das Relais wieder aus; ein zweimal zugestelltes „10 ml dosieren" dosiert 20 ml. Abhilfe schafft es, jeden Befehl idempotent zu machen:
- Senden Sie absolute Zustände (
{"relay": "on"}) statt relativer Aktionen (toggle). - Fügen Sie jedem Befehl eine eindeutige
command_idhinzu und lassen Sie das Gerät sich kürzlich ausgeführte IDs merken. - Fügen Sie einen Ablauf hinzu: ein Message Expiry Interval aus MQTT 5 oder einen Zeitstempel
valid_until, damit ein während eines langen Ausfalls wartender Befehl nicht Stunden später ausgeführt wird, wenn er nicht mehr sicher ist.
Ein resilienter Python-Client
Der folgende Client kombiniert die vier Muster. Er puffert Telemetrie in SQLite, verbindet sich mit randomisiertem exponentiellem Backoff neu, pflegt ein Retained-Status-Topic mit LWT und dedupliziert Befehle. Er nutzt den öffentlichen MQTT-Broker, der für Tests gedacht ist, sodass Sie ihn sofort ausführen können; wechseln Sie für die Produktion auf Port 8883, TLS und Ihre Zugangsdaten aus dem Portal.
import json
import random
import sqlite3
import time
import uuid
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
DEVICE = f"dev-{uuid.uuid4().hex[:6]}"
ROOT = f"connectmq/demo/{DEVICE}"
STATUS, TELEMETRY, COMMANDS = f"{ROOT}/status", f"{ROOT}/telemetry", f"{ROOT}/cmd"
db = sqlite3.connect("outbox.db", check_same_thread=False)
db.execute("CREATE TABLE IF NOT EXISTS outbox (id INTEGER PRIMARY KEY, payload TEXT)")
db.commit()
pending = {} # mid -> Zeilen-ID im Outbox
seen_commands = [] # kürzliche Befehls-IDs zur Deduplizierung
connected = False
def on_connect(client, userdata, flags, reason_code, properties):
global connected
if reason_code.is_failure:
print("Connect refused:", reason_code)
return
connected = True
print("Connected")
client.publish(STATUS, "online", qos=1, retain=True)
client.subscribe(COMMANDS, qos=1)
def on_disconnect(client, userdata, flags, reason_code, properties):
global connected
connected = False
pending.clear() # unbestätigte Zeilen bleiben im Outbox und werden erneut gesendet
print("Disconnected:", reason_code)
def on_publish(client, userdata, mid, reason_code, properties):
row_id = pending.pop(mid, None)
if row_id is not None:
db.execute("DELETE FROM outbox WHERE id = ?", (row_id,))
db.commit()
def on_message(client, userdata, msg):
try:
cmd = json.loads(msg.payload)
except ValueError:
print("Ignoring malformed command")
return
if cmd.get("command_id") in seen_commands:
print("Duplicate command ignored:", cmd["command_id"])
return
if cmd.get("valid_until", float("inf")) < time.time():
print("Expired command ignored:", cmd.get("command_id"))
return
seen_commands.append(cmd.get("command_id"))
del seen_commands[:-100]
print("Executing:", cmd)
def flush_outbox(client):
rows = db.execute("SELECT id, payload FROM outbox ORDER BY id LIMIT 20").fetchall()
for row_id, payload in rows:
if row_id in pending.values():
continue
info = client.publish(TELEMETRY, payload, qos=1)
pending[info.mid] = row_id
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=DEVICE)
client.will_set(STATUS, "offline", qos=1, retain=True)
client.on_connect, client.on_disconnect = on_connect, on_disconnect
client.on_publish, client.on_message = on_publish, on_message
# Exponentieller Backoff zwischen Reconnects; ein zufälliges Minimum verteilt die Geräte
client.reconnect_delay_set(min_delay=random.randint(1, 5), max_delay=120)
client.connect_async(HOST, PORT, keepalive=30) # bei Fehlschlag von der Loop erneut versucht
client.loop_start()
while True:
reading = {"temperature": round(random.uniform(20, 25), 2), "ts": int(time.time())}
db.execute("INSERT INTO outbox (payload) VALUES (?)", (json.dumps(reading),))
db.commit()
if connected:
flush_outbox(client)
time.sleep(5)
Testen Sie ihn, indem Sie Ihr Netzwerk für eine Minute trennen. Messwerte sammeln sich in outbox.db, die Netzwerkschleife versucht es mit wachsenden Abständen weiter, und sobald das Netz zurück ist, wird der Rückstand der Reihe nach abgearbeitet, bis zu zwanzig Zeilen pro Durchlauf. Paho verdoppelt die Wartezeit zwischen Versuchen bis max_delay, fügt aber keinen eigenen Jitter hinzu, weshalb jedes Gerät eine zufällige Anfangsverzögerung wählt. In einer großen Flotte können Sie weiter gehen und mit einer eigenen Reconnect-Schleife jedem Versuch zufälligen Jitter hinzufügen.
Sitzungszustand und was Sie nicht voraussetzen sollten
Persistente Sitzungen (Clean Session false in MQTT 3.1.1 oder ein Session Expiry Interval in MQTT 5) lassen den Broker QoS-1- und QoS-2-Nachrichten für einen getrennten Client in die Warteschlange stellen. Sie helfen bei clientseitigen Ausfällen. Bei brokerseitigen Ausfällen helfen sie nur, wenn der Broker Sitzungen über Neustarts hinweg persistiert, was nicht jeder Broker tut. ConnectMQ garantiert keine Persistenz über Neustarts hinweg, und auf dem öffentlichen MQTT-Broker laufen Sitzungen nach höchstens 30 Minuten ab.
Gestalten Sie daher defensiv:
- Abonnieren Sie in
on_connectjedes Mal neu, statt sich darauf zu verlassen, dass der Broker sich Subscriptions merkt. - Publizieren Sie Retained-Status und Konfiguration nach dem Reconnect erneut.
- Betrachten Sie die geräteseitige Pufferung, nicht Broker-Warteschlangen, als maßgebliche Quelle für Daten, die Sie nicht verlieren dürfen.
- Lassen Sie das Backend den Zustand nach einer Lücke abgleichen, etwa indem es Geräte bittet, ihre aktuelle Konfiguration erneut zu publizieren.
Checkliste zur Ausfallbereitschaft
| Bereich | Frage | Gute Antwort |
|---|---|---|
| Reconnect | Nutzt jeder Client Backoff mit Jitter? | Ja, mit einer Obergrenze von ein bis zwei Minuten |
| Auth-Fehler | Werden falsche Zugangsdaten langsam wiederholt und gemeldet? | Ja, getrennt von Netzwerkfehlern |
| Pufferung | Wo liegen ungesendete Daten während eines Ausfalls? | Persistenter lokaler Speicher mit Größenlimit |
| Präsenz | Wie erfahren andere, dass ein Gerät weg ist? | LWT plus Retained-Status |
| Befehle | Was passiert, wenn ein Befehl zweimal ankommt? | Nichts; Befehle sind idempotent |
| Veraltete Befehle | Kann ein alter Befehl nach einem langen Ausfall ausgeführt werden? | Nein; ein Ablauf wird durchgesetzt |
| Subscriptions | Werden Subscriptions nach dem Reconnect wiederhergestellt? | Ja, in on_connect |
| Testen | Haben Sie einen Ausfall geprobt? | Ja, durch Trennen des Netzes in einem Testlauf |
Brokerseitige Maßnahmen, die diese Client-Muster ergänzen, finden Sie in unseren Leitfäden zu Hochverfügbarkeit für MQTT-Broker und MQTT-Broker-Monitoring; gehen Sie außerdem vor dem Launch die MQTT-Produktions-Checkliste durch.
Resiliente Clients auf ConnectMQ betreiben
Ein verwalteter MQTT-Broker beseitigt die häufigsten selbst verschuldeten Ausfälle wie abgelaufene Zertifikate und ungepatchte Server, während Ihre Clients den Rest erledigen. Legen Sie unter app.connectmq.com einen kostenlosen Broker im Serverless-Plan an, ohne Kreditkarte, testen Sie diese Muster dagegen und beobachten Sie Reconnects mit dem $SYS-Verbindungszustand und dem Alarmprotokoll im Portal. Erfahren Sie mehr über ConnectMQ oder vergleichen Sie Optionen in unserem Leitfaden zum Cloud-MQTT-Broker.
Häufig gestellte Fragen
Wie schnell sollte sich ein IoT-Client nach einem MQTT-Broker-Ausfall neu verbinden?
Versuchen Sie es anfangs schnell und fahren Sie dann exponentiell bis zu einer Obergrenze von ein bis zwei Minuten zurück, mit zufälligem Jitter auf jeder Wartezeit. So erholt sich der Client schnell von kurzen Aussetzern, ohne den Broker nach einem langen Ausfall zu überlasten.
Schützt QoS 1 Daten während eines Broker-Ausfalls?
Nur für Nachrichten, die bereits an die Client-Bibliothek übergeben wurden, und nur, solange das Gerät mit Strom versorgt bleibt. Daten, die Sie nicht verlieren dürfen, puffern Sie in persistentem lokalem Speicher, bis ein PUBACK eintrifft.
Wann wird die Last-Will-Nachricht gesendet?
Wenn der Broker eine unnormale Trennung erkennt, typischerweise nach etwa dem 1,5-Fachen des Keepalive-Intervalls. Bei einem sauberen Disconnect wird sie nicht gesendet, und solange der Broker selbst ausgefallen ist, wird nichts gesendet.
Sollten Befehle QoS 2 nutzen, um Duplikate zu vermeiden?
QoS 2 vermeidet Duplikate zwischen Client und Broker, doch Ende-zu-Ende-Duplikate können nach Reconnects oder Wiederholungen in der Anwendung trotzdem auftreten. Idempotente Befehle mit eindeutigen IDs sind die robustere Lösung.



