Messagerie et streaming d'événements
ActiveMQ Classic en production : KahaDB, haute disponibilité, sécurité et supervision
Avancé11 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
Faire tourner ActiveMQ Classic ailleurs que sur un portable, c'est savoir où vivent les messages et qui a le droit d'y toucher. Ce guide explique le journal et l'index de KahaDB, la livraison persistante et non persistante, la relivraison et les files de messages morts, le failover master/slave sur un store partagé, les plugins d'authentification et d'autorisation, TLS côté clients, ainsi que les outils JMX, Jolokia et CLI qui servent à surveiller un broker et à diagnostiquer une file bloquée.
Ce que vous allez apprendre
- Expliquer comment KahaDB journalise, indexe et purge les données des messages, et régler ses paramètres essentiels
- Configurer les politiques de relivraison, l'expiration et des files de messages morts par file
- Mettre en place un failover master/slave sur store partagé ou JDBC, en sachant contre quoi il protège et contre quoi il ne protège pas
- Sécuriser un broker avec l'authentification JAAS, des autorisations par destination et TLS
- Surveiller les files, la mémoire et l'occupation du store via JMX, Jolokia et la CLI activemq
- Diagnostiquer les pannes courantes en production : files bloquées, store qui grossit, producteurs freinés par le flow control
Entre un broker qui fonctionne sur votre machine et un broker à qui l'on peut confier des commandes, il y a trois différences : ce qui arrive à un message quand quelque chose plante, qui a le droit d'envoyer et de recevoir, et si quelqu'un s'aperçoit qu'une file ne bouge plus. Ce guide couvre ces trois domaines pour ActiveMQ Classic 6. Il suppose que vous avez un broker qui tourne et un activemq.xml de base, comme décrit dans le guide d'installation, et se concentre sur les décisions qui comptent une fois le vrai trafic arrivé.
Comment les messages sont stockés
ActiveMQ conserve chaque message persistant dans un persistence adapter jusqu'à ce que tous ses consommateurs l'aient acquitté. L'adaptateur choisi détermine le débit, le temps de reprise et vos options de failover.
KahaDB : journal et index
KahaDB est le store par défaut, et le bon choix pour presque tout le monde. Il écrit les messages dans un journal en ajout seul et tient à part un index qui associe files et abonnements à des positions dans le journal. Ce sont ces écritures séquentielles qui le rendent rapide : un message est écrit une seule fois, à la fin du fichier de journal courant, quelle que soit la file à laquelle il appartient.
Le répertoire data/kahadb contient plusieurs types de fichiers :
db-<n>.log— les fichiers de journal, de 32 Mo chacun par défaut. Messages, acquittements et enregistrements de transaction y sont ajoutés dans l'ordre. Quand un fichier est plein, le suivant est créé.db.data— l'index en B-tree : pour chaque file, les positions du journal qui contiennent ses messages en attente ; pour chaque abonnement durable, jusqu'où il a consommé.db.redo— un journal de reprise qui sert à reconstruiredb.datasi le broker a été tué en pleine écriture.lock— le verrou de fichier sur lequel attend un second broker partageant ce répertoire (la base du failover sur store partagé).
Deux tâches de fond maintiennent le store en bonne santé. Un checkpoint (toutes les 5 secondes par défaut) écrit l'index sur disque pour réduire la portion de journal à rejouer lors d'une reprise. Un cleanup (toutes les 30 secondes) supprime les fichiers de journal qui ne contiennent plus aucun message référencé. Cette seconde règle est à l'origine de la question classique « pourquoi mon disque est-il plein ? » : un fichier de journal survit tant qu'un seul de ses messages n'est pas consommé, si bien qu'un message oublié sur une file inutilisée peut retenir derrière lui des gigaoctets de fichiers de journal plus récents.
Les paramètres à connaître se trouvent sur l'élément <kahaDB> :
<!-- enableJournalDiskSyncs : fsync à chaque envoi persistant (sûr, plus lent).
indexCacheSize : pages d'index gardées en mémoire ; à augmenter si les destinations sont nombreuses. -->
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"
journalMaxFileLength="32mb"
enableJournalDiskSyncs="true"
checkpointInterval="5000"
cleanupInterval="30000"
indexCacheSize="10000"
concurrentStoreAndDispatchQueues="true"/>
</persistenceAdapter>
enableJournalDiskSyncs est le curseur entre sécurité et vitesse. Activé, le broker appelle fsync avant d'acquitter un envoi persistant, de sorte qu'une coupure de courant ne peut pas faire perdre un message acquitté ; désactivé, c'est le cache de pages du système d'exploitation qui décide, et le débit peut être plusieurs fois supérieur. concurrentStoreAndDispatchQueues permet au broker de remettre un message à un consommateur en attente pendant que l'écriture disque est encore en cours, et même de sauter l'écriture si le consommateur acquitte le premier — un gain important pour les files vidées aussi vite qu'elles se remplissent.
Quand un même broker héberge des destinations aux profils de trafic très différents, l'adaptateur mKahaDB attribue des instances KahaDB distinctes à des motifs de destinations, afin qu'une file lente chargée de vieux messages ne puisse pas retenir les fichiers de journal d'une file rapide.
Store JDBC
Le jdbcPersistenceAdapter place les messages dans trois tables (ACTIVEMQ_MSGS, ACTIVEMQ_ACKS, ACTIVEMQ_LOCK) de n'importe quelle base relationnelle prise en charge. Il est bien plus lent que KahaDB — chaque message coûte un INSERT et un DELETE —, mais il permet de réutiliser la sauvegarde, la réplication et la supervision de l'équipe base de données, et il fournit une table de verrou pour le failover master/slave sans dépendre de la sémantique d'un système de fichiers partagé.
<bean id="postgres" class="org.postgresql.ds.PGSimpleDataSource">
<property name="url" value="jdbc:postgresql://db:5432/activemq"/>
<property name="user" value="activemq"/>
<property name="password" value="${db.password}"/>
</bean>
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#postgres" createTablesOnStartup="true"/>
</persistenceAdapter>
Les documentations plus anciennes mentionnent aussi les adaptateurs LevelDB et l'AMQ store ; tous deux ont été supprimés, et une configuration qui y fait référence échoue en 6.x.
Livraison persistante et non persistante
Le store ne voit que les messages envoyés avec DeliveryMode.PERSISTENT (la valeur par défaut en JMS). Les messages non persistants vivent dans la mémoire du broker et, quand elle vient à manquer, débordent dans un store de fichiers temporaire borné par tempUsage. L'écart de latence côté producteur est important, car un envoi persistant attend l'écriture dans le journal, alors qu'un envoi non persistant est acquitté dès que le broker a reçu les octets. Pour les topics, la règle comporte une subtilité : un message persistant publié sur un topic n'est stocké que s'il existe un abonnement durable pour lui ; sinon, il n'y a personne pour qui le conserver.
Relivraison, expiration et files de messages morts
Un message que le consommateur n'arrive pas à traiter est relivré, pas jeté. Le nombre de tentatives est fixé par la RedeliveryPolicy du client : six relivraisons à une seconde d'intervalle par défaut, configurables sur la connection factory ou dans l'URL.
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("failover:(tcp://broker:61616)");
RedeliveryPolicy policy = factory.getRedeliveryPolicy();
policy.setMaximumRedeliveries(5);
policy.setInitialRedeliveryDelay(1_000);
policy.setUseExponentialBackOff(true);
policy.setBackOffMultiplier(3); // 1 s, 3 s, 9 s, 27 s, 81 s
policy.setMaximumRedeliveryDelay(60_000);
Après la dernière tentative, le message est déplacé vers une file de messages morts (DLQ, dead-letter queue). Par défaut, il s'agit d'une unique ActiveMQ.DLQ partagée par tout le broker, pratique pour une démo et inutile en production, puisqu'on ne peut pas savoir de quelle file vient un message mort sans l'ouvrir. La solution habituelle est une stratégie par destination dans la politique de destination :
<policyEntry queue=">">
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"
processExpired="true" processNonPersistent="false"/>
</deadLetterStrategy>
</policyEntry>
Les messages expirés — ceux dont le JMSExpiration est dépassé avant la livraison — partent eux aussi dans la DLQ quand processExpired vaut true, ce qui est la valeur par défaut. Cela surprend les équipes qui utilisent des durées de vie courtes pour se débarrasser des données périmées : ces données finissent dans la DLQ. Positionnez processExpired="false" sur ces destinations, ou donnez à la DLQ sa propre expiration avec l'attribut expiration de la stratégie pour qu'elle ne grossisse pas indéfiniment.
Quelle que soit votre configuration, surveillez les DLQ : un compteur qui monte est le tout premier signe qu'un consommateur a un bug ou qu'une dépendance en aval est défaillante.
Haute disponibilité : master/slave sur un store partagé
ActiveMQ Classic n'intègre aucune réplication. La haute disponibilité repose sur deux brokers ou plus qui pointent vers le même store ; un seul — le master — peut détenir le verrou du store et servir les clients. Les autres démarrent, n'obtiennent pas le verrou et attendent. Quand le master meurt, son verrou est libéré, un slave l'acquiert, rejoue l'index et démarre ses transport connectors, et les clients qui utilisent le transport failover: s'y reconnectent.
| Store partagé | Mécanisme de verrou | Points de vigilance |
|---|---|---|
| KahaDB sur système de fichiers partagé (NFSv4, GFS2, un SAN) | Verrou de fichier sur le fichier lock | Le système de fichiers doit implémenter correctement le verrouillage POSIX ; ce n'est pas le cas de NFSv3 ni de certains services de fichiers cloud, et un verrou jamais libéré vous laisse avec deux masters |
| Store JDBC | Verrou de ligne dans ACTIVEMQ_LOCK, rafraîchi périodiquement | Débit plus faible ; la base de données devient le point unique de défaillance et doit elle-même être hautement disponible |
La configuration du slave est identique à celle du master : même emplacement de store ; un brokerName différent n'est pas obligatoire mais aide à lire les logs. Le failover n'est pas instantané — rejouer l'index KahaDB d'un gros store prend de quelques secondes à quelques minutes — et les messages en cours de livraison à un consommateur au moment de la panne sont relivrés : les consommateurs doivent donc être idempotents. Notez que cette architecture protège contre la défaillance du processus broker ; si le stockage sous-jacent est perdu, les messages le sont aussi, et c'est pourquoi le store doit résider sur un stockage répliqué et figurer dans les sauvegardes.
Sécuriser le broker
Une installation ActiveMQ par défaut accepte les connexions anonymes sur tous les transports. La sécuriser implique trois couches : prouver l'identité d'un client, décider de ce qu'il a le droit de faire et chiffrer les échanges.
Authentification
Le simple authentication plugin déclare les utilisateurs directement dans activemq.xml et convient à un petit broker interne :
<plugins>
<simpleAuthenticationPlugin anonymousAccessAllowed="false">
<users>
<authenticationUser username="orders-svc" password="${orders.password}" groups="producers"/>
<authenticationUser username="fulfilment" password="${fulfilment.password}" groups="consumers"/>
<authenticationUser username="ops" password="${ops.password}" groups="admins"/>
</users>
</simpleAuthenticationPlugin>
</plugins>
Le plugin JAAS délègue à un login module Java standard configuré dans conf/login.config, avec par défaut les utilisateurs et les groupes dans conf/users.properties et conf/groups.properties, ou à un module LDAP pour les annuaires d'entreprise. C'est le bon choix dès que les comptes doivent être gérés en dehors de la configuration du broker :
<plugins>
<jaasAuthenticationPlugin configuration="activemq"/>
</plugins>
activemq {
org.apache.activemq.jaas.PropertiesLoginModule required
org.apache.activemq.jaas.properties.user="users.properties"
org.apache.activemq.jaas.properties.group="groups.properties";
};
Les clients fournissent leurs identifiants à la connexion : factory.createContext("orders-svc", password). Des certificats peuvent remplacer les mots de passe grâce au jaasCertificateAuthenticationPlugin, qui associe le distinguished name d'un certificat client à un utilisateur ; combinez-le avec des transports ssl:// qui exigent l'authentification du client (transport.needClientAuth=true).
Autorisation
L'authorization plugin accorde aux groupes les droits read, write et admin par motif de destination. admin est le droit de créer la destination, dont les producteurs ont besoin si les files sont créées à la première utilisation. Les advisory topics doivent rester lisibles par tout le monde, sinon les clients qui s'en servent échouent de manière déroutante.
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue="orders.>" read="consumers" write="producers" admin="producers,consumers"/>
<authorizationEntry queue="DLQ.>" read="admins" write="admins" admin="admins"/>
<authorizationEntry queue=">" read="admins" write="admins" admin="admins"/>
<authorizationEntry topic=">" read="admins" write="admins" admin="admins"/>
<authorizationEntry topic="ActiveMQ.Advisory.>" read="everyone" write="everyone" admin="everyone"/>
</authorizationEntries>
<tempDestinationAuthorizationEntry>
<tempDestinationAuthorizationEntry read="everyone" write="everyone" admin="everyone"/>
</tempDestinationAuthorizationEntry>
</authorizationMap>
</map>
</authorizationPlugin>
Les entrées sont évaluées de la plus spécifique à la plus générale : la règle orders.> l'emporte donc sur > pour les files de commandes. Les destinations temporaires ont leur propre entrée ; sans elle, les clients request-reply ne peuvent pas créer leurs files de réponse.
Chiffrement
Ajoutez un <sslContext> dans <broker> pointant vers un keystore, et exposez un connecteur ssl:// ou nio+ssl://. La JVM du broker doit pouvoir lire le keystore, et les clients doivent faire confiance au certificat — en entreprise, on s'appuie généralement sur une autorité de certification privée dont la racine est importée dans le truststore des clients. La console web a sa propre configuration TLS dans conf/jetty.xml, et ses identifiants dans conf/jetty-realm.properties ne doivent surtout pas rester à admin/admin ; les mots de passe de tous ces fichiers peuvent être chiffrés avec bin/activemq encrypt et référencés via ENC(...), pour ne pas versionner de texte en clair.
Supervision et diagnostic
Tout ce que le broker sait de lui-même est exposé via des MBeans JMX dans le domaine org.apache.activemq, et tous les autres outils ne sont que des vues sur ces beans.
| Outil | Accède à JMX via | Idéal pour |
|---|---|---|
Console web (http://host:8161/admin) | Dans le processus du broker | Coup d'œil rapide aux files et aux consommateurs, parcours et purge des messages |
bin/activemq bstat / dstat / query | JMX distant | Scripts et diagnostic en ligne de commande |
Jolokia (http://host:8161/api/jolokia) | Passerelle HTTP/JSON | Exporters Prometheus, tableaux de bord, tout ce qui parle HTTP |
| JConsole, VisualVM | JMX distant | Heap, threads et MBeans au même endroit |
| Hawtio | Jolokia | Console riche dans le navigateur, également utilisée par Artemis |
Le JMX distant est désactivé par défaut. Activez-le dans bin/env avec ACTIVEMQ_SUNJMX_START="-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.ssl=true ...", liez-le à une interface d'administration, et n'exposez jamais un port JMX non authentifié : il permet l'exécution de code arbitraire.
Le point d'accès Jolokia répond à de simples requêtes HTTP, ce qui facilite l'écriture de vérifications :
# Profondeur, consommateurs et messages en vol pour une file (identifiants de la console web)
curl -s -u ops:secret \
'http://broker:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=orders-broker,destinationType=Queue,destinationName=orders.placed/QueueSize,ConsumerCount,InFlightCount,EnqueueCount,DequeueCount'
Les attributs qui méritent une alerte sont peu nombreux :
- QueueSize par file — une file qui grossit pendant plus de quelques minutes a perdu ses consommateurs ou a un consommateur trop lent.
- ConsumerCount par file — zéro sur une file censée avoir des consommateurs, c'est un incident en cours.
- InFlightCount — les messages distribués mais non acquittés ; une valeur constante sans aucun dequeue signale un consommateur bloqué.
- MemoryPercentUsage, StorePercentUsage, TempPercentUsage sur le broker — à l'approche de 100, le producer flow control est sur le point de bloquer vos producteurs.
- ExpiredCount et la profondeur de chaque file
DLQ.*. - TotalConnectionsCount et le nombre de connexions par transport, à comparer au
maximumConnectionsque vous avez fixé.
data/activemq.log est l'autre source d'information. Deux messages valent un grep : l'avertissement de flow control (Usage Manager Memory Limit reached) et Slow KahaDB access, qui signifie que le disque ne suit pas les écritures du journal — c'est le moment de déplacer data/ vers un stockage plus rapide ou de reconsidérer enableJournalDiskSyncs.
Erreurs courantes
- KahaDB sur NFSv3 ou sur un partage de fichiers cloud sans verrouillage fiable. Les deux brokers croient détenir le verrou ; le store est corrompu. Utilisez NFSv4 avec un verrouillage vérifié, un système de fichiers en cluster ou un store JDBC.
- Une unique
ActiveMQ.DLQque personne ne lit. Configurez des DLQ par file, alertez dessus, et préparez une procédure de rejeu avant d'en avoir besoin. - Des TTL courts en guise de ramasse-miettes. Par défaut, les messages expirés atterrissent dans la DLQ. Positionnez
processExpired="false"là où les données périmées doivent simplement disparaître. - Oublier l'entrée des advisory topics dans la carte d'autorisation. Les clients reçoivent des
SecurityExceptionobscures à la connexion. GardezActiveMQ.Advisory.>lisible par tout le monde. - Désactiver les syncs disque du journal pour régler un problème de latence. Ça marche, et ça dégrade silencieusement la livraison persistante en « survit à un plantage du broker mais pas de la machine ». Faites-en une décision consciente, et consignez-la par écrit.
- Des abonnements durables non surveillés. Un abonné durable déconnecté depuis des mois retient des fichiers de journal indéfiniment. Supprimez les abonnements inactifs dans la console, ou définissez
offlineDurableSubscriberTimeoutsur l'élément<broker>pour que les abonnements hors ligne au-delà de ce délai soient supprimés automatiquement.
Points clés à retenir
- KahaDB ajoute les messages à des fichiers de journal et les indexe séparément ; un fichier n'est récupéré que lorsque tous ses messages ont été consommés, si bien qu'un seul message oublié peut retenir tout le store.
- La livraison persistante attend l'écriture dans le journal ; la livraison non persistante est plus rapide et ne perd des messages que si le processus du broker meurt.
- La relivraison est régie par la politique du client ; les messages ayant épuisé leurs tentatives et, par défaut, les messages expirés partent dans la file de messages morts — rendez-la propre à chaque destination et surveillez-la.
- La haute disponibilité, c'est une paire master/slave partageant un store KahaDB ou JDBC protégé par un verrou ; un réseau de brokers fait du routage, pas de la redondance.
- Sécurisez en trois couches : authentification JAAS ou simple, autorisations read/write/admin par destination, et TLS sur les transports comme sur la console.
- Surveillez QueueSize, ConsumerCount, InFlightCount, les pourcentages mémoire/store/temp et la profondeur des DLQ via JMX ou Jolokia, et déclenchez les alertes avant que le producer flow control ne bloque vos applications.
Sources et lectures complémentaires
Questions fréquentes
- Pourquoi le répertoire KahaDB grossit-il alors que mes files sont vides ?
- Un fichier de journal n'est supprimé que lorsqu'aucun des messages qu'il contient n'est encore référencé. Un seul message non consommé, un seul message non acquitté sur un abonnement durable ou une seule transaction non validée suffit à retenir tout le fichier de 32 Mo, et tous les fichiers écrits après lui. Cherchez une file contenant quelques vieux messages, un abonné durable inactif ou une transaction XA en attente, et faites le ménage : le cycle de nettoyage suivant récupère les fichiers.
- Un réseau de brokers suffit-il pour la haute disponibilité ?
- Non. Un réseau fait transiter les messages entre brokers en fonctionnement, mais les messages déjà stockés sur un broker tombé restent indisponibles jusqu'à son retour. Dans ActiveMQ Classic, la haute disponibilité passe par une paire master/slave partageant un même store — KahaDB sur un stockage partagé qui gère correctement le verrouillage de fichiers, ou un store JDBC — pour que le slave reprenne exactement les mêmes messages.
- La livraison non persistante signifie-t-elle qu'un message est perdu à la moindre panne ?
- Elle signifie que le broker ne l'écrit pas dans le store avant d'acquitter l'envoi : le message est donc perdu si le processus du broker meurt. Il n'est pas perdu si le consommateur plante ou si le réseau tombe, et le transport failover du client garde en mémoire les messages non envoyés pendant la reconnexion. Pour des métriques, des mises à jour de présence ou des invalidations de cache, ce compromis est généralement le bon.
- Que vérifier en premier quand une file ne se vide plus ?
- Si elle a des consommateurs, et ce qu'ils sont en train de faire. La console web ou le MBean de la file indique le nombre de consommateurs et le nombre de messages en vol ; un consommateur qui a des messages en vol et n'acquitte jamais est bloqué, souvent sur un appel en aval sans timeout. Si les consommateurs sont présents et inactifs, vérifiez si tous les messages appartiennent à un même groupe de messages ou sont bloqués par un sélecteur.