MQTT managé dans le cloud

Concevoir des clients IoT qui résistent à une panne de broker MQTT

Tout broker finit par redémarrer. Découvrez les patterns côté client qui font d'une panne de broker MQTT un non-événement : reconnexions avec jitter, mémoire tampon hors ligne, statut via Last Will, gestion des sessions et commandes idempotentes.

Appareils IoT stockant localement leurs données pendant une panne de broker MQTT puis se reconnectant avec backoff au retour du broker
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Une panne de broker MQTT n'est pas une question de si mais de quand : les réseaux se partitionnent, les certificats sont renouvelés, les brokers sont mis à jour et les régions cloud ont leurs mauvais jours. Ce guide vous montre comment concevoir les clients des appareils et du backend pour qu'une panne de broker, courte ou longue, ne fasse perdre aucune donnée importante et ne provoque aucune action en double. Ces patterns s'appliquent à n'importe quel broker, ConnectMQ compris, et s'accompagnent d'un code Python fonctionnel.

Ce qui se passe réellement pendant une panne de broker MQTT

Du point de vue du client, une panne prend l'une de trois formes : la connexion TCP tombe, la connexion se fige silencieusement jusqu'à l'expiration du keepalive, ou les nouvelles tentatives de connexion sont refusées. La cause peut être le broker lui-même, le réseau entre vous et le broker, le DNS, TLS, voire vos propres identifiants. Le client ne peut pas toujours distinguer ces cas : il doit donc les traiter tous de la même manière, détecter vite, continuer à travailler localement et se reconnecter poliment.

Quatre éléments sont en jeu :

  1. Les données sortantes publiées pendant la déconnexion sont perdues si vous ne les mettez pas en mémoire tampon.
  2. Les commandes entrantes envoyées pendant que vous étiez hors ligne peuvent être livrées ou non, selon les paramètres de session.
  3. Les informations de présence deviennent fausses si d'autres systèmes croient l'appareil toujours en ligne.
  4. Le broker lui-même peut être submergé lorsque des milliers d'appareils se reconnectent au même instant.

Une bonne conception client traite chacun de ces points. Si ces notions vous sont peu familières, lisez ce que fait un broker MQTT des sessions et des messages en file.

Pattern 1 : se reconnecter avec un backoff exponentiel et du jitter

L'approche naïve consiste à réessayer chaque seconde indéfiniment. Multipliez cela par dix mille appareils : dès que le broker revient, il reçoit un mur de connexions simultanées qui peut le faire retomber aussitôt. C'est le problème du « thundering herd ».

La solution est le backoff exponentiel avec jitter aléatoire : attendre 1 s, puis 2 s, 4 s, 8 s et ainsi de suite jusqu'à un plafond, en rendant chaque délai aléatoire pour étaler les appareils. Paho 2.x le prend en charge nativement via reconnect_delay_set, mais n'ajoute pas de jitter ; beaucoup d'équipes gèrent donc la boucle elles-mêmes pour un contrôle total.

Faites aussi attention à vos identifiants. Les brokers qui bloquent les adresses IP après des échecs de connexion répétés, comme le fait ConnectMQ, considéreront un appareil au mot de passe erroné qui réessaie chaque seconde comme une attaque. Temporisez bien plus fortement sur les erreurs d'authentification que sur les erreurs réseau, et remontez-les à un opérateur.

Pattern 2 : mettre en mémoire tampon les données sortantes

Les bibliothèques clientes MQTT conservent les messages en cours d'envoi en mémoire, et ceux-ci disparaissent si l'appareil redémarre pendant la panne. Pour la télémétrie que vous ne pouvez pas vous permettre de perdre, écrivez d'abord chaque mesure dans un petit stockage local et ne la supprimez qu'après l'accusé de réception du broker (PUBACK). SQLite convient bien aux passerelles et aux appareils Linux ; sur les microcontrôleurs, un buffer circulaire en flash remplit le même rôle.

Décidez à l'avance de ce qui se passe quand la mémoire tampon est pleine. Pour la plupart des données de télémétrie, abandonner les mesures les plus anciennes est le bon choix. Pour les événements de facturation ou de sécurité, vous préférerez peut-être cesser d'échantillonner les données non critiques.

Pattern 3 : annoncer la présence avec Last Will et un statut retained

Définissez un Last Will and Testament (LWT) à la connexion : un message offline retained que le broker publie à votre place si vous disparaissez sans déconnexion propre. À chaque connexion réussie, publiez un online retained sur le même topic. Les autres systèmes voient alors une présence exacte sans avoir à interroger l'appareil.

Soyez précis sur la sémantique du LWT. Le will est publié lorsque le broker détecte que la session s'est terminée anormalement, généralement après 1,5 fois l'intervalle de keepalive. Si le broker lui-même est en panne, rien n'est publié avant son retour, et après un redémarrage du broker, l'état retained peut devoir être rétabli par les clients. MQTT 5 ajoute un will delay interval, qui permet de supprimer les messages offline intermittents lors d'une brève reconnexion.

Pattern 4 : rendre les commandes idempotentes

En QoS 1, un message est livré au moins une fois. Lors d'une panne suivie d'une reconnexion, les doublons deviennent probables. Une commande « basculer le relais » livrée deux fois remet le relais à l'arrêt ; « distribuer 10 ml » livrée deux fois distribue 20 ml. Le remède consiste à rendre chaque commande idempotente :

  • Envoyez des états absolus ({"relay": "on"}) plutôt que des actions relatives (toggle).
  • Incluez un command_id unique dans chaque commande et faites mémoriser à l'appareil les IDs récemment exécutés.
  • Incluez une expiration : un message expiry interval MQTT 5, ou un horodatage valid_until, pour qu'une commande mise en file pendant une longue panne ne soit pas exécutée des heures plus tard, quand elle n'est plus sûre.

Un client Python résilient

Le client suivant combine les quatre patterns. Il stocke la télémétrie dans SQLite, se reconnecte avec un backoff exponentiel aléatoire, maintient un topic de statut retained avec un LWT et déduplique les commandes. Il utilise le serveur MQTT public gratuit pour que vous puissiez l'exécuter immédiatement ; passez au port 8883, à TLS et aux identifiants de votre portail pour la production.

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 -> id de ligne de l'outbox
seen_commands = []      # IDs de commandes récentes pour la déduplication
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()  # les lignes non acquittées restent dans l'outbox et sont renvoyées
    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

# Backoff exponentiel entre les reconnexions ; un minimum aléatoire étale les appareils
client.reconnect_delay_set(min_delay=random.randint(1, 5), max_delay=120)
client.connect_async(HOST, PORT, keepalive=30)  # retentée par la boucle en cas d'échec
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)

Testez-le en coupant votre réseau pendant une minute. Les mesures s'accumulent dans outbox.db, la boucle réseau continue de réessayer avec des délais croissants, et dès le retour du réseau, l'arriéré se vide dans l'ordre, à raison de vingt lignes maximum par cycle. Paho double le délai entre les tentatives jusqu'à max_delay mais n'ajoute aucun jitter de lui-même : c'est pourquoi chaque appareil choisit un délai de départ aléatoire. Sur une grande flotte, vous pouvez aller plus loin et ajouter un jitter aléatoire à chaque tentative avec votre propre boucle de reconnexion.

L'état de session et ce qu'il ne faut pas présumer

Les sessions persistantes (clean session à false en MQTT 3.1.1, ou un session expiry interval en MQTT 5) permettent au broker de mettre en file les messages QoS 1 et 2 destinés à un client déconnecté. Elles aident lors des pannes côté client. Lors des pannes côté broker, elles n'aident que si le broker conserve les sessions à travers les redémarrages, ce que tous les brokers ne font pas. ConnectMQ ne garantit pas cette persistance à travers les redémarrages, et sur le serveur MQTT public gratuit, les sessions expirent au bout de 30 minutes maximum.

Concevez donc de manière défensive :

  1. Réabonnez-vous à chaque fois dans on_connect, plutôt que de compter sur le broker pour se souvenir des abonnements.
  2. Republiez le statut et la configuration retained après la reconnexion.
  3. Considérez la mémoire tampon de l'appareil, et non les files du broker, comme la source de vérité pour les données que vous ne pouvez pas perdre.
  4. Laissez le backend réconcilier l'état après une interruption, par exemple en demandant aux appareils de republier leur configuration actuelle.

Checklist de préparation aux pannes

Domaine Question Bonne réponse
Reconnexion Chaque client utilise-t-il un backoff avec jitter ? Oui, plafonné à une ou deux minutes
Erreurs d'authentification Les identifiants erronés sont-ils retentés lentement et signalés ? Oui, séparément des erreurs réseau
Mémoire tampon Où se trouvent les données non envoyées pendant une panne ? Stockage local persistant avec une taille limite
Présence Comment les autres apprennent-ils qu'un appareil a disparu ? LWT plus statut retained
Commandes Que se passe-t-il si une commande arrive deux fois ? Rien ; les commandes sont idempotentes
Commandes périmées Une ancienne commande peut-elle s'exécuter après une longue panne ? Non ; l'expiration est appliquée
Abonnements Les abonnements sont-ils restaurés après la reconnexion ? Oui, dans on_connect
Tests Avez-vous répété une panne ? Oui, en coupant le réseau lors d'un essai

Pour les mesures côté broker qui complètent ces patterns côté client, consultez nos guides sur la haute disponibilité d'un broker MQTT et la supervision d'un broker MQTT, et parcourez la checklist de mise en production MQTT avant le lancement.

Faites tourner des clients résilients sur ConnectMQ

Un broker MQTT managé élimine les pannes auto-infligées les plus courantes, comme les certificats expirés et les serveurs non patchés, tandis que vos clients gèrent le reste. Créez un broker gratuit sur app.connectmq.com avec le plan Serverless, sans carte bancaire, testez ces patterns dessus, et utilisez l'état de connexion $SYS et le journal des alertes du portail pour suivre les reconnexions. Découvrez ConnectMQ ou comparez les options dans notre guide du broker MQTT cloud.

Questions fréquentes

À quelle vitesse un client IoT doit-il se reconnecter après une panne de broker MQTT ?

Réessayez rapidement au début, puis espacez de manière exponentielle jusqu'à un plafond d'une à deux minutes, avec un jitter aléatoire sur chaque délai. Vous récupérez ainsi vite après une micro-coupure sans submerger le broker après une longue panne.

La QoS 1 protège-t-elle les données pendant une panne de broker ?

Uniquement pour les messages déjà confiés à la bibliothèque cliente, et uniquement tant que l'appareil reste alimenté. Pour les données que vous ne pouvez pas perdre, stockez-les dans un stockage local persistant jusqu'à l'arrivée d'un PUBACK.

Quand le message Last Will est-il envoyé ?

Lorsque le broker détecte une déconnexion anormale, généralement après environ 1,5 fois l'intervalle de keepalive. Il n'est pas envoyé lors d'une déconnexion propre, et rien n'est envoyé tant que le broker lui-même est en panne.

Les commandes doivent-elles utiliser la QoS 2 pour éviter les doublons ?

La QoS 2 évite les doublons entre le client et le broker, mais des doublons de bout en bout peuvent encore survenir après des reconnexions ou des nouvelles tentatives applicatives. Des commandes idempotentes avec des IDs uniques constituent la solution la plus robuste.

Articles liés

Broker MQTT BYOC : le modèle Bring Your Own Cloud expliqué

Un broker MQTT BYOC tourne dans votre compte cloud mais est exploité par un fournisseur. Découvrez le fonctionnement du modèle, la répartition des responsabilités et comment préparer votre compte.

9 min de lecture

Messages MQTT par seconde : comment estimer et mesurer le débit

La planification du débit MQTT se résume à quelques formules et à une mesure honnête. Découvrez comment estimer les messages entrants et sortants par seconde, tenir compte des pics et faire correspondre le résultat à une offre de broker.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.