MQTT 5 connaissances minimales

Après avoir utilisé MQTT pendant des années, nous avons constaté que beaucoup de malentendus et d'idées fausses sont encore répandus à propos du protocole. Voici donc un document qui liste ce que MQTT est, et ce qu'il n'est pas capable de faire.

MQTT est un protocole pub/sub

MQTT a été créé pour synchroniser les données entre les appareils d'un réseau.

Il s'agit principalement d'un protocole de sérialisation (c'est-à-dire : comment encoder et décoder des données en octets) et d'une hiérarchie distribuée (c'est-à-dire que les données encodées sont stockées à une certaine position dans une hiérarchie, comme un système de fichiers virtuel).

MQTT est hautement asymétrique.

La partie client ne s'occupe que de certains fichiers virtuels dans la hiérarchie et peut les mettre à jour ou recevoir des mises à jour. La partie broker (courtier) est un client qui gère également l'abonnement à un certain niveau de la hiérarchie. Lorsqu'un fichier virtuel est modifié par un client, le rôle du courtier est de le notifier et de l'envoyer aux autres clients qui se sont inscrits à cette branche de la hiérarchie. C'est de cette partie que vient la plus grande partie de la complexité.

MQTT est petit.

MQTT fonctionne sur TCP avec (optionnellement) la sécurité TLS. Ce n'est pas le plus petit protocole de sérialisation mais c'est un bon compromis entre un protocole lisible et un protocole implémentable. Il est utilisé sur les petits appareils parce qu'il ne nécessite pas trop de flash ou de RAM. Il peut être implémenté sans heap et peut donc fonctionner sur des micro-contrôleurs très limités.

MQTT n'est pas comme ActivityPub ou Mastodon.

MQTT n'effectue aucune sorte de filtrage des messages, aucune métadonnée n'est attachée aux données (par exemple un compteur « J'aime »), aucun lien entre les clients n'est maintenu, et sa gestion des sessions est stupide. Il ne permet pas d'interroger l'arborescence des fichiers virtuels. Vous ne pouvez pas utiliser MQTT pour n'importe quel type de réseau social directement.

MQTT ne peut pas récupérer un fichier virtuel tant qu'il n'a pas été modifié (sauf avec l'option retain, voir ci-dessous).

Le mythe de la robustesse de MQTT

Dans MQTT, il y a un indicateur de qualité de service (plus tard : QoS) attaché à tout fichier virtuel qui change la façon dont les données sont traitées entre les clients et les courtiers.

On entend souvent les gens conseiller d'utiliser QoS 1 ou 2 parce que c'est plus résilient.

C'est faux.

La gestion de la QoS dans MQTT est une très grande source de confusion pour de nombreuses raisons :

1. MQTT fonctionne sur TCP

TCP est fiable contre quelques pertes de paquets.

Si vous utilisez la QoS par peur d'une perte de paquet, arrêtez de le faire, la couche TCP suffit.

MQTT ne retransmet jamais un paquet, même avec la QoS la plus élevée sur une connexion valide.

2. Abandon de la connexion TCP

Si la connexion de l'appareil est sporadique, la couche TCP ne parviendra pas à se synchroniser. Cela signifie que la connexion TCP sera abandonnée. À son tour, la session MQTT sera abandonnée.

Cependant, en raison de la façon dont TCP fonctionne, l'autre bout ne peut détecter la perte de la connexion jusqu'à ce qu'elle soit utilisée (ou après le temps d'attente TCP linger qui peut être assez long sur votre système).

MQTT prévoit un temps linger pendant lequel une session précédente peut être reconnectée (via une nouvelle connexion TCP). Nous appellerons plus tard cette reconnexion de session le mode SOS. Cependant, la plupart du temps, le temps de linger (appelé Keep Alive dans MQTT) expirera avant que le client ne se reconnecte ou le client aura subi un redémarrage entre-temps et il ne sera donc pas en mesure de reprendre la session.

Dans ce cas, la session du client est une nouvelle session lorsqu'il se reconnecte, et tout paquet ou fichier virtuel modifié pendant la période de déconnexion est perdu pour le client, quel que soit le niveau de QoS.

3. Mode SOS

Si vous avez de la chance et que le client se reconnecte au courtier alors que sa session est toujours en cours, cette reconnexion est spéciale.

Tout paquet avec une QoS non nulle sera échangé immédiatement après la connexion.

Cela signifie que, si la connexion du client a été interrompue à 03:00 et reprise à 05:00, vous pouvez recevoir un paquet qui a été envoyé à 03:01 ou à 04:59 (vous ne pouvez pas le dire, il n'y a pas d'horodatage attaché au paquet dans le protocole). Ou, si dix versions du fichier virtuel ont été envoyées entre ces deux extrémités, vous ne recevrez que la dernière (ou la première), ou vous les recevrez toutes en rafale (en fonction de la version de MQTT et/ou de certaines propriétés de CONNECT dans MQTTv5).

Ce mode est le seul mode où un paquet est renvoyé dans MQTT et il est excessivement complexe à mettre en place.

Comme vous pouvez le voir, la valeur ajoutée ici est faible pour cette qualité de service.

En fait, dans MQTT, un autre mécanisme a été créé pour résoudre les problèmes ci-dessus : le flag retain. Si le paquet contient un flag retain, le client recevra la dernière version du fichier virtuel lorsqu'il se reconnectera au courtier et se réabonnera au fichier, comme s'il ne s'était jamais déconnecté au départ. Retain est complètement indépendant de la QoS et fonctionne donc avec n'importe quelle QoS de bas niveau.

4. Pas de données tant qu'elles ne sont pas modifiées

Dans MQTT (simple), tant qu'un client ne modifie pas un fichier virtuel (ou n'en crée pas un), l'autre client ne pourra pas récupérer la version précédente de ce fichier. C'est vraiment le principal défaut du protocole, à notre avis, car si votre appareil redémarre pour quelque raison que ce soit, il ne sera pas en mesure de connaître l'état du système surveillé (jusqu'à ce que tous les fichiers virtuels requis soient modifiés).

Prenons l'exemple d'une vanne qui doit arroser votre jardin de 08h00 à 09h00. Vous pourriez avoir un fichier virtuel appelé valve/on_time contenant « 08:00 » et un autre appelé valve/off_time contenant « 09:00 ». Si la vanne est redémarrée, elle ne fonctionnera pas jusqu'à ce que l'interface ou un autre périphérique modifie leur valeur. S'il n'y a pas de modification, le changement n'est pas propagé aux clients abonnés.

Heureusement, le flag retain de MQTT résout partiellement ce problème. Lorsque la vanne redémarre, en s'abonnant à /valve/on_time, elle recevra la dernière valeur de ce fichier. Cependant, le flag retain ne stocke que la dernière valeur et pas les métadonnées (comme quand il a été modifié, par qui, ...).

5. Latence QoS

Contrairement à l'opinion générale, l'utilisation de la QoS augmente le temps de latence pour votre client.

Un paquet avec une QoS plus élevée sera livré plus tard (au pire) ou retardera tout autre paquet (au mieux).

C'est parce que tout paquet de QoS implique un accusé de réception. L'accusé de réception implique l'envoi d'un à trois paquets supplémentaires par paquet PUBLISH.

Certains clients sont typiquement synchrones (c'est beaucoup plus facile à écrire et à respecter), donc une QoS de 1 divisera la bande passante des paquets par 2 (donc, au pire, multipliera la latence par 2), et une QoS de 2 fera la même chose avec un facteur 4.

Pour une question de réentrance (pensez à : puis-je publier pendant que je reçois un paquet), de nombreux clients envoyeront simplement l'accusé de réception avant d'appeler le callback de réception, ce qui augmentera la latence visible des paquets). Les clients asynchrones (ceux qui stockent l'accusé de réception dans un buffer pour le traiter plus tard) peuvent probablement délivrer le premier paquet QoS sans latence supplémentaire, mais l'overhead sera lissé plus tard lors de la transmission des paquets suivants.

De plus, un paquet de QoS 2 n'est jamais envoyé avant un paquet de QoS 1 (contrairement à la QoS pour la VoIP par exemple), donc utiliser un niveau de QoS supérieur comme priorité pour votre paquet est une erreur.

6. Gestion des métadonnées

Dans MQTT v3.1.1, un paquet (un fichier virtuel) ne peut pas contenir de métadonnées. Donc, si vous avez besoin de savoir, par exemple, quand ce fichier a été modifié, vous devez stocker l'heure de modification dans le fichier lui-même. Il est très peu pratique de modifier les données pour les adapter à une fonctionnalité manquante du protocole.

Avec MQTT v5, un paquet peut avoir des propriétés, et plus particulièrement des propriétés de l'utilisateur. Vous pouvez donc désormais stocker ces informations dans le paquet, sans modifier la charge utile, c'est-à-dire le fichier virtuel.

Cependant, sur un système embarqué avec une connexion sporadique, afin de sauvegarder un timestamp dans votre communication, vous devez avoir une horloge et la synchroniser. Cela se fait soit avec un RTC (mais tous les systèmes embarqués ne disposent pas d'un RTC et d'une batterie pour maintenir l'horloge), soit une autre connexion est nécessaire pour synchroniser l'horloge avec le réseau.

MQTT ne fournit pas de schéma de synchronisation de l'horloge. Quelques courtiers implémentent un fichier virtuel $SYS/broker/time qui contient l'heure actuelle, mais la plupart ne le font pas.

Cela signifie que vous aurez besoin soit d'un client spécifique qui met à jour un tel fichier une fois par seconde (cette solution permet une dépendance minimale sur votre système embarqué) mais nécessite un code spécifique pour se désabonner sur le client une fois que le premier contenu a été reçu.

Soit vous devrez implémenter un client NTP/SNTP dans votre système embarqué (plus de dépendances, et aussi besoin d'un serveur NTP/SNTP sur votre infrastructure réseau).

7. MQTT vs REST

Une autre méthode pour synchroniser les données sur un système consiste à utiliser un client/serveur HTTP et à pousser/tirer des données via le protocole REST.

MQTT et REST fonctionnent tous deux sur TCP, et la version HTTP/REST est plus facile à déboguer.

Cependant, HTTP est principalement un protocole client/serveur et, à moins de maîtriser l'ensemble du réseau, vous ne pouvez pas vous attendre à ce que votre système embarqué soit en mesure de se connecter facilement à l'internet.

Pourtant, REST implique également que vous exécutiez un serveur HTTP sur le système embarqué (probablement nécessaire pour amorcer le système également). Cela signifie également que le système embarqué fonctionne 24 heures sur 24, 7 jours sur 7, puisque vous ne pouvez pas utiliser le courtier MQTT pour stocker des données lorsque le système est endormi.

Vous pouvez logiquement attendre de votre client qu'il dispose d'un navigateur web (un client HTTP, donc tout ce qui est nécessaire pour se connecter au système), mais vous ne pouvez pas attendre de lui qu'il dispose d'un client MQTT.

Article précédent