Messagerie et streaming d'événements
Middleware orienté messages : files, topics et communication asynchrone
Débutant12 min de lectureMis à jour
Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise
Un middleware orienté messages permet à des applications d'échanger des données via un broker plutôt que de s'appeler directement. À l'issue de ce guide, vous saurez expliquer les modèles point à point et publish-subscribe, décrire les composantes d'un message, comparer les principaux protocoles et brokers, et juger si la messagerie asynchrone est le bon style d'intégration pour un problème donné.
Ce que vous allez apprendre
- Comparer les quatre styles d'intégration et expliquer ce que la messagerie apporte par rapport au transfert de fichiers, à la base de données partagée et aux appels distants
- Décrire l'anatomie d'un message et les rôles du producteur, du broker, de la destination et du consommateur
- Distinguer les files point à point des topics publish-subscribe et choisir le bon modèle selon le cas d'usage
- Expliquer les trois formes de découplage qu'apporte un broker et ce qu'elles coûtent
- Situer JMS, AMQP, STOMP, MQTT et le protocole Kafka les uns par rapport aux autres
- Choisir entre ActiveMQ, RabbitMQ et Kafka pour une charge de travail donnée
Deux applications peuvent partager des données en échangeant des fichiers, en lisant la même base de données, en s'appelant par le réseau, ou en s'envoyant des messages par l'intermédiaire d'un tiers. Cette dernière option — le middleware orienté messages (MOM) — est celle qui permet à chaque partie de continuer à fonctionner quand l'autre est lente, surchargée ou arrêtée. C'est cette propriété qui fait de la messagerie la colonne vertébrale du traitement des commandes, des pipelines de paiement, de la collecte de logs et de la plupart des architectures orientées événements. Ce guide explique le modèle commun à tous les brokers, afin que les guides suivants sur JMS, ActiveMQ, RabbitMQ et Kafka se lisent comme des variations sur un même thème plutôt que comme quatre produits sans rapport.
Quatre styles d'intégration, un compromis
Tout problème d'intégration revient à déplacer des données et à déclencher des comportements entre des systèmes qui n'ont pas été conçus ensemble. La classification classique distingue quatre styles, chacun corrigeant une faiblesse du précédent au prix d'une nouvelle.
| Style | Fonctionnement | Point fort | Point faible |
|---|---|---|---|
| Transfert de fichiers | Un système écrit un fichier, un autre le lit plus tard | Simple, sans runtime partagé, tout langage | Latence de plusieurs heures, dérive du format, aucun retour |
| Base de données partagée | Plusieurs applications lisent et écrivent le même schéma | Données toujours cohérentes et à jour | Le schéma devient un contrat que personne ne peut modifier ; contention |
| Appel de procédure distante (HTTP, gRPC, RMI) | Une application appelle une fonction exposée par une autre | Réponse immédiate, modèle de programmation familier | L'appelant est bloqué ; les deux parties doivent tourner ; pannes en cascade |
| Messagerie | Les applications confient de petits paquets à un broker qui les livre | Asynchrone, fiable, découplé | Cohérence à terme, débogage plus difficile, davantage de pièces mobiles |
La messagerie n'est pas « meilleure » que les appels distants. C'est un contrat différent : l'émetteur apprend seulement que le broker a accepté le message, pas que quelqu'un l'a traité. Les systèmes bâtis sur la messagerie doivent donc s'accommoder d'un travail effectué plus tard et de résultats qui arrivent sous forme d'autres messages plutôt que de valeurs de retour.
Anatomie d'un message
Quel que soit le broker, un message comporte les trois mêmes parties.
- Les en-têtes (headers) sont des champs que le broker lui-même comprend et sur lesquels il peut agir : un identifiant unique, un horodatage, une destination, une priorité, une expiration, un mode de livraison (le broker doit-il écrire le message sur disque avant de l'acquitter ?) et, pour le request-reply, une adresse de réponse et un identifiant de corrélation.
- Les propriétés (que RabbitMQ et Kafka appellent aussi headers) sont des paires clé-valeur définies par l'application. Les consommateurs peuvent filtrer dessus, les routeurs s'en servir pour aiguiller, et les systèmes de tracing y propagent les identifiants de trace. C'est là que vont les métadonnées comme
tenant,eventTypeouschemaVersion. - Le corps (payload) est, du point de vue du broker, une suite d'octets opaque. JSON est l'encodage le plus courant aujourd'hui ; les formats binaires comme Avro ou Protocol Buffers sont préférés à fort volume, car ils sont plus compacts et portent un schéma.
Une bonne discipline consiste à placer tout ce dont un routeur ou un filtre a besoin dans les en-têtes ou les propriétés, et à ne jamais obliger quiconque à analyser le corps pour décider où va un message. Les brokers ne peuvent pas regarder dans le corps, et le code qui le fait se retrouve couplé au format du payload.
{
"headers": {
"messageId": "ID:app-1-45231-1725700000000-1:1:1:1",
"timestamp": 1725700000000,
"deliveryMode": "PERSISTENT",
"replyTo": null,
"correlationId": null
},
"properties": {
"eventType": "OrderPlaced",
"schemaVersion": "2",
"tenant": "eu-west"
},
"body": "{\"orderId\":\"o-9911\",\"total\":149.90,\"currency\":\"EUR\"}"
}
Les identifiants de l'en-tête permettent au consommateur de détecter les doublons et de rattacher une réponse à sa requête ; vous pouvez générer les vôtres avec un générateur d'UUID quand le broker n'en attribue pas.
Producteurs, consommateurs, destinations et broker
Le vocabulaire est réduit. Un producteur (publisher, émetteur) crée un message et le confie au broker. Le broker le stocke dans une destination et le livre à un ou plusieurs consommateurs (abonnés, récepteurs). Le producteur ne sait jamais qui sont les consommateurs, et les consommateurs ne savent jamais qui a produit le message : les deux ne connaissent que le nom de la destination.
C'est le broker qui rend l'ensemble fiable. Il accepte les messages même quand aucun consommateur n'est connecté, les persiste si on le lui demande, les relivre quand un consommateur échoue, et contrôle qui a le droit d'envoyer vers une destination ou d'y lire. C'est aussi un point unique pour la supervision : profondeur des files, nombre de consommateurs et débit sont visibles au même endroit.
Tous les systèmes de messagerie n'ont pas besoin d'un broker. Des bibliothèques sans broker comme ZeroMQ livrent directement d'un processus à l'autre et l'emportent sur le plan de la latence, mais un message n'a alors nulle part où attendre pendant que le destinataire est arrêté. Dès qu'il vous faut une file partagée par plusieurs applications et qui survive aux redémarrages, il vous faut un broker.
Deux domaines de messagerie : files et topics
Les destinations existent en deux variantes, et choisir la bonne est la première décision de conception de tout système de messagerie.
Point à point : la file
Une file (queue) livre chaque message à exactement un consommateur. Si dix consommateurs lisent la même file, le broker répartit les messages entre eux — un pattern appelé competing consumers —, et c'est ainsi qu'on augmente le débit horizontalement : ajoutez un consommateur, et la file se vide plus vite. Les messages attendent dans la file jusqu'à ce que quelqu'un les lise, si bien que producteur et consommateur n'ont pas besoin de tourner en même temps. Traitement de commandes, redimensionnement d'images et envoi d'e-mails sont des charges typiques pour une file : chaque unité de travail doit être faite une fois.
Publish-subscribe : le topic
Un topic livre chaque message à tous les abonnés actifs. Un seul événement « adresse du client modifiée » peut être diffusé au système de facturation, au système d'expédition et au pipeline d'analytics sans que le producteur sache qu'ils existent. Par défaut, un abonnement ne reçoit que les messages publiés pendant qu'il est actif ; un abonnement durable demande au broker de conserver les messages pour un abonné nommé temporairement hors ligne. Sans durabilité, un topic est une diffusion ; avec, chaque abonné dispose en pratique d'une file privée.
| File | Topic | |
|---|---|---|
| Consommateurs par message | Un seul | Zéro ou plusieurs |
| Consommateur hors ligne | Le message attend | Message perdu, sauf abonnement durable |
| Ajout de consommateurs | Augmente le débit | Ajoute des copies |
| Usage typique | Répartition du travail, commandes | Notification d'événements, diffusion |
Les brokers modernes brouillent la frontière : RabbitMQ construit les deux à partir d'exchanges et de bindings, et Kafka fournit les deux grâce aux partitions et aux consumer groups. Les deux intentions — fais ceci une fois contre préviens tout le monde — restent la bonne grille de lecture d'une conception, quel que soit le produit.
Ce que le découplage apporte réellement
Le « couplage faible » est la justification habituelle de la messagerie, mais il recouvre trois bénéfices distincts, chacun avec son prix.
Découplage dans le temps. Le consommateur n'a pas besoin de tourner quand le producteur envoie. Un système batch nocturne peut accepter des événements toute la journée ; un prestataire de paiement en maintenance reçoit son arriéré à son retour. Le prix : le producteur ne peut pas encore garantir à l'utilisateur que quoi que ce soit a eu lieu.
Découplage dans l'espace. Le producteur adresse un nom de destination, pas un hôte. Vous pouvez déplacer un consommateur sur une autre machine, en lancer cinq copies ou le remplacer par un service réécrit, sans toucher à la configuration du producteur. Le prix : le broker devient une infrastructure critique, qui a besoin de sa propre stratégie de haute disponibilité.
Découplage de la charge. Une file absorbe les pics. Un front-end web qui reçoit dix mille commandes en une minute peut les mettre en file en quelques secondes et laisser le service de préparation des commandes les traiter à son rythme, au lieu de tomber en timeout sous le pic. Le prix : la file peut grossir sans limite si les consommateurs prennent du retard, et chaque système a donc besoin d'un plan pour l'arriéré : davantage de consommateurs, expiration des messages ou backpressure sur le producteur.
Les garanties de livraison en un paragraphe
Un broker peut offrir une livraison at-most-once (fire and forget : rapide, mais des messages peuvent se perdre), une livraison at-least-once (le broker conserve le message jusqu'à ce que le consommateur l'acquitte, si bien qu'un plantage avant l'acquittement provoque une relivraison et éventuellement un doublon), ou une forme d'exactly-once limitée à un périmètre précis (les transactions Kafka entre topics Kafka, par exemple). La livraison persistante — écrire le message sur disque avant de le confirmer au producteur — permet à l'at-least-once de survivre à un redémarrage du broker. Presque tous les systèmes en production fonctionnent en at-least-once et rendent leurs consommateurs idempotents, c'est-à-dire que traiter deux fois le même message a le même effet que le traiter une fois. Le guide sur les patterns, plus loin dans ce sujet, explique comment.
Protocoles et API
Le mot « messagerie » recouvre une API standard, plusieurs protocoles réseau et un protocole propre à un produit ; les confondre provoque de vraies erreurs d'intégration.
- JMS (Jakarta Messaging) est une API Java, pas un protocole. Le code écrit avec
jakarta.jmsfonctionne avec tout broker qui fournit un client JMS, mais un client JMS dialogue avec son broker dans le format réseau choisi par l'éditeur. Deux brokers ne sont pas interopérables simplement parce que tous deux « supportent JMS ». - AMQP 0-9-1 est le protocole réseau autour duquel RabbitMQ a été conçu ; il définit exchanges, files, bindings et channels, et tout langage disposant d'un client AMQP 0-9-1 peut dialoguer avec RabbitMQ.
- AMQP 1.0 est une norme OASIS et ISO qui n'a guère en commun avec 0-9-1 que le nom. Elle définit la façon dont des pairs se transfèrent des messages, pas une topologie de broker. ActiveMQ Classic, ActiveMQ Artemis, RabbitMQ (nativement depuis la 4.0) et Azure Service Bus la parlent tous.
- STOMP est un protocole texte minimaliste que l'on peut piloter depuis une session Telnet. Il est apprécié pour les clients navigateur sur WebSocket et pour les langages de script.
- MQTT est un protocole publish-subscribe compact, conçu pour les appareils contraints et les réseaux peu fiables ; ActiveMQ et RabbitMQ acceptent tous deux des connexions MQTT.
- Le protocole Kafka est le protocole binaire propre à Kafka. Aucune norme ne le sous-tend, mais des clients existent pour tous les langages courants.
- OpenWire est le protocole binaire natif d'ActiveMQ Classic, utilisé par ses clients Java, .NET et C++.
Pour une équipe Java, la conséquence pratique est la suivante : utilisez JMS si vous voulez garder la possibilité de changer de broker, et le client natif (amqp-client pour RabbitMQ, kafka-clients pour Kafka) si vous voulez toutes les fonctionnalités du broker choisi.
Choisir un broker
Les trois brokers open source couverts par ce sujet résolvent des problèmes qui se recoupent sans être identiques.
| ActiveMQ Classic (et Artemis) | RabbitMQ | Apache Kafka | |
|---|---|---|---|
| Modèle | Files et topics JMS | Des exchanges routent vers des files | Log partitionné et répliqué |
| Durée de vie d'un message | Supprimé à l'acquittement | Supprimé à l'acquittement | Conservé pendant une durée configurée, qu'il soit consommé ou non |
| Ordre | Par file (par groupe de messages avec JMSXGroupID) | Par file avec un seul consommateur | Par partition |
| Rejouer d'anciens messages | Non | Non (sauf streams) | Oui, en réinitialisant les offsets |
| Souplesse du routage | Sélecteurs, virtual topics, destinations composites | Très élevée (types d'exchanges, bindings, en-têtes) | Faible : le producteur choisit un topic et une clé |
| Débit de prédilection | Des milliers à des dizaines de milliers de messages/s | Comparable, avec un routage riche | Des centaines de milliers à des millions d'enregistrements/s |
| Protocoles | OpenWire, AMQP 1.0, STOMP, MQTT, WebSocket | AMQP 0-9-1, AMQP 1.0, STOMP, MQTT, streams | Protocole Kafka |
| Terrain naturel | Applications Java EE et Spring qui ont besoin de JMS | Files de tâches, RPC, routage flexible, équipes polyglottes | Event streaming, pipelines de logs, intégration de données, rejeu |
Quelques règles empiriques tiennent bien la route. Si vos consommateurs sont des message-driven beans JMS ou des méthodes Spring @JmsListener et que vous avez besoin de files transactionnelles, ActiveMQ est l'option la moins contraignante ; préférez Artemis pour les nouveaux déploiements qui exigent plus de débit ou du clustering, et Classic là où vous avez déjà de la configuration et de l'outillage. Si vous devez router un même message vers différentes files selon plusieurs attributs, ou si vous avez des producteurs en Python et des consommateurs en Go, le modèle d'exchanges de RabbitMQ est difficile à battre. Si les données forment un flux d'événements que plusieurs équipes consommeront à des moments différents, qui doit pouvoir être rejoué ou qui arrive en très grand volume, le log conservé de Kafka est la bonne primitive, et le forcer à se comporter comme une file de tâches vous décevra.
Là où la messagerie tourne mal
- Se servir d'une file comme d'une base de données. Les files sont faites pour des messages en transit. Stocker de l'état dans une file pour le parcourir plus tard mène à une croissance sans limite et à un ordre imprévisible. Persistez l'état dans un store ; mettez les événements dans la file.
- Supposer un ordre entre consommateurs. Une file servie par trois consommateurs concurrents livre dans l'ordre mais traite en parallèle, si bien que le message 2 peut se terminer avant le message 1. Si l'ordre compte, utilisez un consommateur par clé (groupes de messages, single-active-consumer de RabbitMQ, partitions Kafka).
- Des messages très volumineux. Un PDF de 50 Mo dans le corps d'un message ralentit chaque saut et fait exploser les limites mémoire du broker. Stockez le fichier dans un stockage objet et envoyez une référence — c'est le pattern claim-check.
- Ignorer l'arriéré. Une file que personne ne surveille se remplit en silence jusqu'à ce que le broker atteigne sa limite disque et refuse les messages de tous les producteurs. Mettez en place des alertes sur la profondeur des files et le nombre de consommateurs dès le premier jour.
- Se coupler par le payload. Si chaque consommateur analyse le même JSON non documenté, le système « découplé » ne peut plus modifier un champ sans un déploiement coordonné. Versionnez le payload et ajoutez une propriété
schemaVersionà chaque message.
Points clés à retenir
- La messagerie est l'un des quatre styles d'intégration ; elle échange la réponse immédiate contre la résilience, l'élasticité et l'indépendance entre systèmes.
- Un message, ce sont des en-têtes que le broker comprend, des propriétés que l'application définit et un corps opaque.
- Les files livrent chaque message une seule fois et montent en charge avec des consommateurs concurrents ; les topics diffusent à chaque abonné, de façon durable si on le demande.
- Le découplage dans le temps, dans l'espace et de la charge, voilà ce que vous apporte un broker ; des arriérés sans limite et une pièce d'infrastructure critique, voilà ce qu'il vous coûte.
- JMS est une API Java, AMQP, STOMP et MQTT sont des protocoles réseau, et Kafka parle le sien ; ne présumez pas d'une interopérabilité sur la foi d'un sigle commun.
- ActiveMQ convient aux systèmes Java centrés sur JMS, RabbitMQ aux systèmes polyglottes qui routent beaucoup, et Kafka aux flux d'événements volumineux et rejouables.
Sources et lectures complémentaires
Questions fréquentes
- Un broker de messages est-il la même chose qu'un enterprise service bus ?
- Non. Un broker stocke et achemine des messages entre destinations, sans en comprendre le contenu. Un enterprise service bus s'appuie sur un broker ou un transport et y ajoute le routage selon le contenu, la transformation, les passerelles entre protocoles et l'orchestration. Beaucoup de produits ESB embarquent un broker comme ActiveMQ, mais un broker peut très bien fonctionner seul, et c'est le cas dans la plupart des systèmes modernes.
- Quand un simple appel HTTP vaut-il mieux qu'un message ?
- Quand l'appelant a réellement besoin de la réponse pour continuer — valider une carte bancaire avant de confirmer une commande, par exemple — et que l'appelé est censé être disponible. La messagerie prend l'avantage quand le traitement peut être différé, quand producteurs et consommateurs montent en charge à des rythmes différents, quand le même événement intéresse plusieurs consommateurs ou quand le système cible est parfois indisponible.
- Une file garantit-elle qu'un message est traité exactement une fois ?
- Pas à elle seule. Un broker peut promettre une livraison at-most-once ou at-least-once ; le consommateur peut toujours planter après le traitement et avant l'acquittement, et le même message est alors relivré. Le traitement exactly-once est une propriété de l'ensemble du pipeline, obtenue en général par des consommateurs idempotents ou par une comptabilité transactionnelle côté consommateur.
- Kafka est-il une file de messages ?
- Kafka est un commit log distribué et répliqué. Il permet de répartir le travail comme une file grâce aux consumer groups, et de faire du pub-sub avec des groupes indépendants, mais il ne supprime pas un enregistrement une fois consommé et ne suit pas d'acquittement par message. C'est cette différence de modèle qui explique pourquoi on présente généralement Kafka comme une plateforme d'event streaming plutôt que comme une file.