En MQTT classique, chaque abonné dont le filtre correspond à un topic reçoit sa propre copie de chaque message. C’est parfait pour la diffusion (fan-out), mais peu pratique lorsque vous voulez que plusieurs workers backend se répartissent un flux de messages. Les abonnements partagés (shared subscriptions), standardisés dans MQTT 5, résolvent ce problème : les abonnés rejoignent un groupe nommé, et le broker livre chaque message à un seul membre de ce groupe.
La syntaxe $share
Un abonnement partagé est une demande d’abonnement normale avec un filtre de topic particulier :
$share/<ShareName>/<TopicFilter>$share— un préfixe littéral qui marque l’abonnement comme partagé.ShareName— le nom du groupe. Il doit comporter au moins un caractère et ne doit contenir ni/, ni+, ni#.TopicFilter— un filtre ordinaire, jokers (wildcards) compris. Voir topics et jokers MQTT.
Par exemple, $share/ingest/factory/+/telemetry place l’abonné dans le groupe ingest pour tous les topics factory/<line>/telemetry. Les éditeurs n’ont rien de particulier à faire : ils publient toujours sur factory/line-3/telemetry et ne voient jamais le préfixe $share. Pour vérifier à quels topics correspond un filtre, collez la partie située après le nom du groupe dans le testeur de topics MQTT.
Comment les messages sont répartis
Les règles de livraison se comprennent mieux avec un exemple. Supposons que ces abonnements existent :
| Client | Abonnement | Reçoit un message publié sur orders/new ? |
|---|---|---|
| worker-a | $share/billing/orders/# | L’un des deux (le broker choisit) |
| worker-b | $share/billing/orders/# | |
| mailer-1 | $share/email/orders/new | Oui — c’est le seul membre du groupe email |
| audit | orders/# | Oui — les abonnements normaux ne sont pas affectés |
Chaque groupe reçoit donc une copie, et au sein d’un groupe, un seul membre la traite. La manière de choisir ce membre n’est pas définie par la spécification. Les brokers proposent des stratégies comme le round robin, l’aléatoire, le sticky (continuer d’envoyer au même membre tant qu’il est là) ou un hachage sur le client ID ou le topic — EMQX, par exemple, permet de configurer cela. Chaque membre conserve sa propre QoS d’abonnement, et les messages sont acquittés par membre comme d’habitude.
Support par les brokers
Les abonnements partagés font partie du standard MQTT 5, et un broker signale leur prise en charge via la propriété Shared Subscription Available du CONNACK. Si un broker ne les prend pas en charge, il refuse l’abonnement avec le reason code 0x9E (Shared Subscriptions not supported) — voir la référence des reason codes MQTT.
- Mosquitto prend en charge
$sharedepuis la version 1.6, pour les clients MQTT 5 et 3.1.1. - EMQX prend en charge
$shareainsi qu’un ancien raccourci,$queue/<filter>, qui se comporte comme un abonnement partagé dans un groupe unique par défaut. - HiveMQ prend en charge
$sharepour les deux versions du protocole.
Les brokers managés et cloud varient : consultez leur documentation avant de vous appuyer dessus. Notre comparatif des brokers MQTT passe en revue les principales options.
Exemple : deux workers qui se partagent un flux
Ouvrez trois terminaux. Les deux premiers rejoignent le même groupe ; le troisième publie dix messages :
mosquitto_sub -V mqttv5 -h broker.hivemq.com -q 1 \
-t '$share/workers/testmqtt/share-demo/jobs' -vfor i in $(seq 1 10); do
mosquitto_pub -V mqttv5 -h broker.hivemq.com -q 1 \
-t 'testmqtt/share-demo/jobs' -m "job $i"
doneChaque job apparaît dans exactement un terminal de worker. Notez les apostrophes simples autour du filtre : dans la plupart des shells, $share entre guillemets doubles serait interprété comme une variable. Le principe est le même dans le code :
import mqtt from 'mqtt';
const client = mqtt.connect('wss://broker.hivemq.com:8884/mqtt', { protocolVersion: 5 });
client.on('connect', () => {
client.subscribe('$share/workers/testmqtt/share-demo/jobs', { qos: 1 });
});
client.on('message', (topic, payload) => {
// topic is the real topic, without the $share prefix
console.log(topic, payload.toString());
});Vous pouvez jouer le rôle de l’éditeur depuis votre navigateur : ouvrez le client ci-dessous et publiez sur testmqtt/share-demo/jobs pendant que vos workers tournent.
Publier des jobs de test dans le client en ligne
Cas d’usage
- Scalabilité horizontale des consommateurs backend. Lancez N instances d’un service d’ingestion et laissez le broker répartir la télémétrie entre elles, sans file de messages séparée.
- Haute disponibilité. Si un consommateur plante, les membres restants continuent de recevoir les messages.
- Passerelles vers des bases de données ou des moteurs de streaming. Plusieurs writers peuvent se partager l’insertion des messages dans une base de séries temporelles ou leur transfert vers Kafka.
- Files de travail. Des commandes comme « redimensionner cette image » peuvent être traitées par le worker qui les reçoit, quel qu’il soit.
Concevoir une architecture à abonnements partagés
Un nom de groupe par type de consommateur
Nommez les groupes d’après la tâche, pas d’après l’instance : $share/ingest/..., $share/alerts/.... Chaque instance du service d’ingestion rejoint ingest ; chaque instance du service d’alertes rejoint alerts. Chaque service voit alors chaque message exactement une fois dans son ensemble, tandis que ses instances se répartissent le travail. Si vous donnez par erreur un nom de groupe unique à chaque instance, vous revenez à une simple diffusion et chaque instance traite chaque message.
Sessions et QoS des workers
Les workers s’abonnent généralement en QoS 1 pour qu’un message soit relivré si un worker plante avant de l’acquitter. La mise en file des messages pour un groupe dont tous les membres sont hors ligne dépend des sessions des membres : avec une session persistante, le broker peut continuer à les mettre en file ; avec des sessions propres, les messages publiés alors qu’aucun membre du groupe n’est connecté ne sont tout simplement pas livrés à ce groupe.
Abonnements partagés ou file de messages
Les abonnements partagés offrent de simples consommateurs concurrents au sein de MQTT. Ils n’apportent pas les fonctionnalités des systèmes de files dédiés, comme la relecture depuis un offset, les dead-letter queues ou les garanties d’ordre par partition. Pour de nombreux backends IoT, ce compromis est acceptable ; si vous avez besoin de relecture, transférez le flux d’un abonnement partagé vers un log comme Kafka et traitez-le là-bas.
Pièges et limites
- Pas d’ordre global. MQTT garantit l’ordre par topic pour un abonné unique, mais dès que les messages sont répartis sur plusieurs workers, ils sont traités en parallèle. Si l’ordre compte par appareil, utilisez une stratégie de broker qui hache sur le topic, ou partitionnez vous-même par topic.
- Pas de messages retenus. Selon la spécification MQTT 5, les messages retenus (retained) ne sont pas envoyés lors de l’établissement d’un abonnement partagé. Utilisez un abonnement normal pour l’état, comme les topics de statut retenus.
- No Local n’est pas autorisé. Activer l’option No Local sur un abonnement partagé est une erreur de protocole en MQTT 5.
- Désabonnez-vous avec le filtre complet. Pour quitter le groupe, désabonnez-vous de la chaîne exacte
$share/group/filter, et non du seul filtre. - ACL. Selon les brokers, les règles d’accès s’appliquent à la chaîne
$share/...ou au filtre sous-jacent. Testez vos permissions avec la vraie syntaxe. - Les doublons restent possibles. La QoS 1 signifie « au moins une fois », et la relivraison à un autre membre après une déconnexion peut produire des doublons. Concevez des handlers idempotents ; voir les niveaux de QoS MQTT.
Questions fréquentes
Qu’est-ce qu’un abonnement partagé MQTT ?
Un abonnement partagé permet à plusieurs clients de s’abonner en tant que groupe avec un filtre de topic de la forme $share/groupe/filtre. Chaque message correspondant n’est livré qu’à un seul membre du groupe au lieu de l’être à chaque abonné, ce qui répartit la charge entre plusieurs consommateurs.
Les abonnements partagés fonctionnent-ils avec MQTT 3.1.1 ?
Les abonnements partagés sont standardisés dans MQTT 5. Plusieurs brokers, dont Mosquitto, EMQX et HiveMQ, acceptent aussi la syntaxe $share de la part de clients MQTT 3.1.1 en tant qu’extension, mais le comportement dépend du broker : consultez sa documentation.
Pourquoi les abonnés partagés ne reçoivent-ils pas les messages retenus ?
La spécification MQTT 5 indique que les messages retenus ne sont pas envoyés à une session lorsqu’elle établit un nouvel abonnement partagé. Un message retenu représentant un état courant et non un travail à répartir, le livrer à un membre arbitraire du groupe n’aurait pas de sens. Utilisez un abonnement normal si vous avez besoin de la valeur retenue.