Fondamentaux MQTT

Garanties d'ordre des messages MQTT expliquées

MQTT préserve l'ordre des messages, mais uniquement selon des règles étroites. Découvrez précisément ce que garantit la spécification, les situations réelles qui réordonnent les messages et comment les numéros de séquence rendent vos consommateurs robustes.

Messages numérotés traversant un broker dans l'ordre, illustrant les garanties d'ordre des messages MQTT
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

L'ordre des messages MQTT est garanti, mais seulement dans des limites précises que beaucoup de développeurs ne lisent jamais. Les messages d'un éditeur sur un topic avec un même niveau de QoS arrivent dans l'ordre ; presque tout le reste n'est pas promis. Cet article explique exactement ce que garantit la spécification, les situations courantes où les messages semblent arriver dans le désordre, et comment ajouter des numéros de séquence pour que vos consommateurs puissent le détecter et le gérer.

Ce que garantit la spécification MQTT

MQTT 3.1.1 et MQTT 5 définissent tous deux un « Ordered Topic ». Par défaut, un broker doit traiter chaque topic comme ordonné, ce qui conduit à la garantie suivante :

Les messages publiés par un client sur un topic, avec le même niveau de QoS, sont livrés à chaque abonné dans l'ordre de leur publication.

La spécification exige aussi que, lorsqu'un client renvoie après une reconnexion des messages QoS 1 et QoS 2 non acquittés, il le fasse dans l'ordre d'origine, et que les accusés de réception soient envoyés dans l'ordre de réception des paquets.

Lisez attentivement cette garantie et ses lacunes sautent aux yeux. Elle ne dit rien sur :

  • Les messages de différents éditeurs sur le même topic.
  • Les messages d'un éditeur sur différents topics.
  • Les messages d'un éditeur sur le même topic mais avec différents niveaux de QoS.
  • Ce que fait votre application une fois que la bibliothèque cliente lui a remis le message.

Là où l'ordre des messages MQTT n'est plus garanti

Situation Ordre garanti ? Pourquoi
Un éditeur, un topic, même QoS Oui La règle de l'Ordered Topic
Un éditeur, deux topics Non Chaque topic est ordonné indépendamment
Deux éditeurs, un topic Non Pas d'horloge globale ; l'ordre d'arrivée au broker prévaut
Même topic, QoS 0 et QoS 1 mélangées Non Chemins de livraison et réessais différents
QoS 0 sur une connexion coupée Ordre conservé, mais avec des trous Les messages perdus ne sont pas renvoyés
QoS 1 avec relivraison Des doublons peuvent apparaître Un message renvoyé peut suivre des messages plus récents qu'il précédait déjà
Abonnement partagé Pas d'ordre global Les messages sont répartis entre consommateurs
Consommateur multithread Non Votre pool de workers réordonne le traitement

Deux de ces cas méritent qu'on s'y attarde.

Abonnements partagés. Avec $share/group/topic, le broker répartit les messages entre plusieurs consommateurs pour équilibrer la charge. Chaque consommateur voit un sous-ensemble et ils traitent en parallèle : il n'existe donc pas d'ordre global significatif. Si l'ordre par appareil compte, concevez le système pour que les messages de chaque appareil atteignent toujours le même consommateur, ou réordonnez en aval. Notre guide sur les abonnements partagés MQTT détaille ce compromis.

Relivraison et doublons. La QoS 1 est « au moins une fois ». Si un accusé de réception est perdu, l'émetteur retransmet avec le flag DUP activé. Un abonné peut alors revoir le message 5 après avoir déjà traité 6 et 7. Il s'agit d'un doublon plutôt que d'une véritable inversion, mais pour un code naïf, c'est comme si le temps revenait en arrière. La solution est décrite dans la gestion des messages dupliqués MQTT.

Démonstration de l'ordre sur un serveur MQTT public

L'expérience la plus rapide utilise le serveur MQTT public gratuit broker.connectmq.com, réservé aux tests. Notez sa limite de 10 messages par seconde et par client : l'éditeur ci-dessous se bride donc lui-même.

Abonné :

mosquitto_sub -h broker.connectmq.com -p 1883 -i ord-sub-h5d3 -q 1 \
  -t 'connectmq/demo/h5d3/#' -v

Éditeur, un topic, QoS 1, messages numérotés :

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

Vous verrez 1 à 20 dans l'ordre. Notez que chaque appel à mosquitto_pub est une connexion distincte : c'est la boucle séquentielle qui préserve l'ordre ici. Lancez maintenant deux boucles en parallèle sur deux topics différents, seq-a et seq-b. Chaque topic reste ordonné, mais les lignes des deux topics s'entrelacent de façon imprévisible. Rien d'anormal : l'ordre entre topics ne fait tout simplement pas partie du contrat.

Ajouter des numéros de séquence

Lorsque l'ordre compte pour votre application, transportez-le dans le payload. Un numéro de séquence monotone par éditeur, accompagné d'un horodatage, permet aux consommateurs de détecter les trous, les doublons et les messages périmés. Voici une paire complète éditeur/consommateur avec 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 = {}  # appareil -> plus grande séquence vue

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 envoyé volontairement en retard
    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()

Le consommateur applique 1, 2, 3, signale un trou avant 5, écarte le 4 arrivé en retard comme périmé, puis applique 6. Faut-il écarter, mettre en tampon ou accepter les messages tardifs ? Cela dépend des données, ce qui nous amène à la stratégie.

Choisir une stratégie d'ordonnancement

  1. La dernière valeur gagne (état). Pour des mesures comme la température, écartez tout ce qui est plus ancien que ce que vous avez déjà. C'est ce que fait le code ci-dessus.
  2. Tampon de réordonnancement (flux d'événements). Conservez brièvement les messages et libérez-les dans l'ordre des séquences, en renonçant aux trous après un timeout. Utile lorsque chaque événement compte, comme pour des compteurs.
  3. Upserts idempotents (stockage). Écrivez chaque message avec pour clé l'appareil et la séquence ou l'horodatage. La base de données finit correcte quel que soit l'ordre d'arrivée.
  4. Un topic par flux ordonné. Si des commandes doivent être appliquées dans l'ordre, publiez-les sur un seul topic, depuis un seul client, avec une seule QoS.
  5. Évitez de mélanger les QoS sur un même flux. Choisissez une QoS par topic.

L'ordre lors des reconnexions et des périodes hors ligne

C'est lors des reconnexions que les hypothèses sur l'ordre sont le plus souvent mises à l'épreuve. Lorsqu'un abonné avec session persistante revient, le broker renvoie d'abord les messages en vol non acquittés, puis livre les messages mis en file pendant que le client était hors ligne, puis reprend le trafic en direct. Pour un topic et un éditeur donnés, l'ordre est toujours respecté, mais le consommateur reçoit soudain une rafale de messages anciens suivie des messages actuels.

Deux choses aident ici. D'abord, gardez un traitement séquentiel par appareil, pour qu'une rafale soit appliquée dans l'ordre plutôt que disputée par des workers parallèles. Ensuite, utilisez les horodatages du payload pour décider comment présenter les données de rattrapage : un tableau de bord voudra peut-être passer directement à la dernière valeur, alors qu'un historian doit stocker chaque mesure avec son heure d'origine. Si les messages en file ne sont utiles que pendant un temps limité, un message expiry MQTT 5 exclut entièrement les plus anciens de la rafale.

Côté publication, les appareils qui mettent les mesures en tampon localement pendant une coupure réseau doivent envoyer ce tampon dans l'ordre des séquences avant de reprendre la publication en direct, et sur le même topic et avec la même QoS que le trafic normal.

Pièges et dépannage liés à l'ordre

La séquence repart à zéro après un redémarrage. Un appareil qui redémarre à seq 1 semble envoyer des données périmées. Incluez un identifiant de démarrage ou un compteur d'époque avec la séquence, ou persistez le compteur en flash.

L'ordonnancement par horloge échoue. Les horloges des appareils dérivent et repartent parfois en 1970. Préférez les numéros de séquence pour l'ordre et les horodatages pour l'affichage.

L'ordre se perd dans votre application. Les handlers asynchrones, pools de threads et files du consommateur réordonnent librement. Traitez le flux de chaque appareil sur un seul worker, ou partitionnez par identifiant d'appareil.

Effets de la fenêtre de messages en vol. Le receive maximum de MQTT 5 permet d'avoir plusieurs messages QoS 1 en vol simultanément. L'ordre sur le réseau reste respecté, mais les réessais en cas de perte de paquets rendent les doublons plus probables. Consultez l'article sur le receive maximum MQTT pour le réglage.

L'ordre sur un broker MQTT managé

ConnectMQ applique les règles d'ordre MQTT par topic pour les clients MQTT 3.1.1 et 5.0. Les abonnements partagés du plan Serverless payant vous offrent une mise à l'échelle horizontale des consommateurs, avec le compromis habituel sur l'ordre décrit plus haut. Utiliser un broker MQTT managé ne change rien aux garanties du protocole, mais supprime le travail d'exploitation pour que vous puissiez vous concentrer sur la conception des consommateurs. Pour le contexte plus large du protocole, consultez le tutoriel MQTT.

Démarrer avec ConnectMQ

Créez un broker sur le plan Serverless gratuit sur app.connectmq.com, sans carte bancaire. Exécutez l'exemple de numéros de séquence avec vos propres identifiants ConnectMQ et observez le flux dans l'explorateur de topics en direct du portail, puis passez en production sur un broker MQTT cloud qui évolue avec votre flotte.

Questions fréquentes

MQTT garantit-il l'ordre des messages ?

Oui, pour les messages d'un client sur un topic avec la même QoS. Il n'y a aucune garantie entre topics, entre éditeurs ou avec des niveaux de QoS mélangés.

La QoS 2 garantit-elle l'ordre ?

La QoS 2 garantit une livraison exactement une fois par saut. L'ordre suit toujours la même règle par topic et par éditeur.

Pourquoi vois-je un message ancien après un message plus récent ?

Généralement à cause d'une relivraison QoS 1, d'un second éditeur ou d'un traitement parallèle dans votre consommateur. Les numéros de séquence permettent de savoir facilement lequel.

Les abonnements partagés sont-ils ordonnés ?

Pas globalement. Chaque message va à un membre du groupe, et les membres traitent en parallèle.

Articles liés

Qui a inventé MQTT ? L'histoire, des oléoducs à l'IoT

MQTT a été inventé en 1999 par Andy Stanford-Clark (IBM) et Arlen Nipper (Arcom) pour surveiller des oléoducs par satellite. Suivez son parcours vers l'open source, OASIS, l'ISO et les brokers MQTT managés d'aujourd'hui.

8 min de lecture

Pourquoi votre client MQTT se déconnecte sans cesse (et comment y remédier)

Les boucles de reconnexion sont l'un des problèmes MQTT les plus courants. Découvrez les cinq causes habituelles, des client ID en double aux timeouts de keep alive et aux paquets trop volumineux, et suivez une méthode de diagnostic qui identifie la vôtre.

9 min de lecture

Taille maximale d'un message MQTT : limites et stratégies pour les gros payloads

MQTT peut techniquement transporter un paquet de 256 Mo, mais les brokers, les bibliothèques clientes et les appareils imposent des limites bien plus basses. Découvrez où se trouvent les vraies limites, ce qui se passe quand on les dépasse et comment envoyer de gros payloads.

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.