Messaging Java avec HornetQ

Alexis Hassler

HornetQ est la troisième génération de serveur de messaging de JBoss. Simple à installer et à configurer, il est aussi pratique à mettre en place dans des environnements plus complexes. Commentez

- Titre : Messaging Java avec HornetQ

- Auteur : Alexis Hassler

- Parution : 16 avril 2011

- Mise à jour : 16 avril 2011

- Licence : Copyright ® 2011 Alexis Hassler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Le messaging en Java

- II. Présentation d'HornetQ

- III. Installation

- IV. Configuration JMS

- V. Transfert de messages

- VI. Répartition de charge

- VII. Tolérance de panne

- Conclusion

- Remerciements

I - Le messaging en Java

Lorsqu'on parle de messaging en Java, on pense en priorité à JMS, Java Messaging Service. L'objet de cet article n'est pas de revenir sur cette API, Jean-Michel Doudoux l'a déjà fait dans les grandes largeurs. Il faut surtout retenir que JMS fonctionne par messages asynchrones en mode point à point, avec des Queues, ou en mode abonnement / publication, avec des Topics. Il faut retenir aussi que les messages sont généralement persistants et que les échanges sont transactionnels.

La portion de code ci-dessous reprend les principales phases de l'interaction avec un serveur JMS :

- recherche des ressources JMS dans le registre JNDI ;

- ouverture de la connexion au serveur JMS ;

- ouverture de la session de communication ;

- envoi ou réception de messages ;

- clôture des ressources.

L'exemple est simplifié car on n'y gère pas les exceptions, que les clôtures ne sont pas faites dans un bloc finally et que les messages sont simplement textuels.

// Recherche des ressources JMS dans le registre JNDI
Context jndiContext = new InitialContext();
ConnectionFactory connectionFactory = (ConnectionFactory) jndiContext.lookup(FACTORY_NAME);
Queue queue = (Queue) jndiContext.lookup(QUEUE_NAME);
// Ouverture de la connexion au serveur JMS
Connection connection = connectionFactory.createConnection();
connection.start();
// Ouverture de la session de communication
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);

// Envoi de messages
for (int i = 0; i < nbMessages; i++) {
sendMessages.sendMessage(message + " - " + i);
Thread.sleep(1000);
}

// Clôture des ressources
session.close();
connection.close();

En marge de ce standard, il existe des outils qui proposent leurs APIs propriétaires (WebsphereMQ, SonicMQ) ou basées sur des protocoles de communication tiers comme AMQP ( RabbitMQ, Qpid) ou Stomp.

II - Présentation d'HornetQ

HornetQ est la troisième génération de serveurs de messaging produite par JBoss. Nous avions JBossMQ qui était intégré à JBoss AS jusqu'à la version 4 puis JBoss Messaging, intégré à JBoss AS 5. HornetQ est intégré par défaut dans JBoss AS 6 et peut être intégré dans JBoss AS 5 et peut être utilisé en autonome ou embarqué dans une application.

Par rapport à ses prédécesseurs, HornetQ apporte à la fois un gain côté performances et côté interopérabilité. Pour les performances, quelques benchmarks (1)(2) le placent à de très bons classements. Pour l'interopérabilité, HornetQ supporte Stomp et supportera AMQP prochainement. HornetQ peut aussi s'appuyer sur les protocoles du Web, avec une API RESTful et le support des WebSockets.

La suite de l'article présente comment mettre en place un environnement HornetQ simple, en répartition de charge ou en tolérance de panne. Nous n'aborderons pas les sujets plus avancés, comme la gestion fine des transactions ou les intercepteurs.

III - Installation

L'installation d'un serveur HornetQ autonome est simple et rapide : il suffit de télécharger l'archive zip ou tar.gz, de la décompresser et de renseigner la variable d'environnement JAVA_HOME.

tar -xvf hornetq-2.1.2.Final.tar.gz
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
cd hornetq-2.1.2.Final/bin
./run.sh

L'installation dans JBoss AS est à peine plus compliquée. Il faut en plus une variable d'environnement JBOSS_HOME et lancer le script de build.

export JBOSS_HOME=/opt/java/jboss-5
cd ../config/jboss-as-5/
sh build.sh

Cette opération a pour effet de créer deux nouveaux profils dans JBoss AS 5, all-with-hornetq et default-with-hornetq, dans lesquels JBoss Messaging est remplacé par HornetQ. Au sein de ces profils, les fichiers de configuration dont nous parlerons ci-dessous sont dans le répertoire deploy/hornetq/.

IV - Configuration JMS

L'essentiel de la configuration JMS se fait dans le fichier config/stand-alone/non-clustered/hornetq-jms.xml. Le premier travail de configuration en JMS est l'ajout des destinations. Une destination JMS est soit une Queue, pour le mode point à point, soit un Topic, pour le mode abonnement / publication. Ceux qui ont eu l'occasion d'utiliser les serveurs JMS précédents de JBoss apprécieront la simplicité et l'expressivité de la configuration.

<queue name="sewa-queue">
<entry name="/queue/SewaQueue"/>
</queue>
<topic name="sewa-topic">
<entry name="/topic/SewaTopic"/>
</topic>

Pour chaque destination, l'attribut name est utilisé pour construire le nom interne à HornetQ, alors que l'élément entry correspond au nom JNDI.

Dans ce même fichier, il est aussi possible de configurer le ConnectionFactory, et en particulier de modifier ses noms JNDI, chose qui peut s'avérer pratique dans le cas du portage d'une application depuis un autre serveur JMS. On peut constater que la configuration par défaut propose deux ConnectionFactory. Le NettyThroughputConnectionFactory est optimisé pour gérer un flux important de petits messages ; il peut éventuellement être supprimé dans une utilisation traditionnelle.

La configuration des principaux composants internes à HornetQ se fait dans le fichier config/stand-alone/non-clustered/hornetq-configuration.xml. On peut y paramétrer les répertoires de stockage, en particulier pour la journalisation des messages persistants. On peut aussi configurer les composants réseau : acceptor pour choisir les ports à ouvrir sur le serveur et connector pour le côté client. Généralement, la configuration du client correspond à celle du serveur, sauf dans des environnements réseau complexes, avec translation d'adresse et/ou de port.

Par défaut, la configuration propose un connector qui s'appelle netty-throughput et un acceptor correspondant. Ils sont prévus pour fonctionner avec le NettyThroughputConnectionFactory ; si on a supprimé ce dernier, il faut supprimer son acceptor et son connector.

V - Transfert de messages

HornetQ propose un mécanisme intéressant pour gérer automatiquement le transfert de messages entre serveurs. Ce mécanisme s'appuie sur des bridges. Un bridge prélève les messages dans une queue et les envoie vers une autre queue HonretQ, typiquement sur un serveur distant.

L'environnement du bridge est constitué d'une queue cible avec un acceptor d'un côté et d'une queue d'origine, d'un connector et du bridge à proprement parler de l'autre côté.

<bridge name="sewa-bridge">
<queue-name>jms.queue.sewa-queue</queue-name>
<forwarding-address>jms.queue.sewa-queue</forwarding-address>
<connector-ref connector-name="bridge-connector" />
</bridge>

On notera dans cette configuration que les noms de queues sont les identifiants internes et pas les noms JMS.

VI - Répartition de charge

La répartition des messages est assurée par deux mécanismes en parallèle. Dans la terminologie de HornetQ, la répartition de charge est assurée par une installation en cluster. Un cluster HornetQ est donc un ensemble de noeuds qui se partagent la charge de messages, soit par un échange de messages entre serveurs et soit par une rotation assurée par les clients.

Il existe une configuration en cluster dans l'installation standard. Pour l'utiliser, il faut indiquer le bon répertoire au lancement d'HornetQ.

./run.sh ../config/stand-alone/clustered

Nous allons décortiquer cette configuration. Tout d'abord, on relève cet élément dans le fichier hornetq-configuration.xml :

<clustered>true</clustered>

Ensuite, on relève deux éléments de configuration réseau multicast, les broadcast-groups et discovery-groups. Les broadcast-groups permettent aux serveurs de diffuser les informations sur leurs connectors sur le réseau. Les discovery-groups permettent aux clients et aux autres serveurs de récupérer ces informations pour se connecter aux serveurs du cluster.

<broadcast-groups>
<broadcast-group name="bg-group1">
<group-address>231.7.7.7</group-address>
<group-port>9876</group-port>
<broadcast-period>5000</broadcast-period>
<connector-ref connector-name="netty"/>
</broadcast-group>
</broadcast-groups>
<discovery-groups>
<discovery-group name="dg-group1">
<group-address>231.7.7.7</group-address>
<group-port>9876</group-port>
<refresh-timeout>10000</refresh-timeout>
</discovery-group>
</discovery-groups>

Voyons maintenant les deux façons que HornetQ nous propose pour répartir les messages sur plusieurs serveurs.

Commençons par la technique proposée dans la configuration clustered par défaut. Le transfert des messages entre serveurs est assuré par des cluster-connections qui retrouvent les connectors auprès d'un discovery group.

<cluster-connections>
<cluster-connection name="sw-cluster">
<address>jms</address>
<discovery-group-ref discovery-group-name="dg-group1"/>
</cluster-connection>
</cluster-connections>

Ceci permet, pour toutes les destinations JMS, de transférer les messages vers les autres noeuds du cluster. Des bridges sont automatiquement mis en place, à partir des connectors découverts auprès du discovery-group.

Autre technique de répartition, dans le cas des JMS, la rotation des clients est paramétrée au niveau du ConnectionFactory, dans le fichier hornetq-jms.xml.

Pour cela, on remplace l'élément connectors par un discovery-group-ref.

<connection-factory name="ConnectionFactory">
<discovery-group-ref discovery-group-name="dg-group1"/>
...
</connection-factory>

Ceci n'est pas utilisé dans la configuration clustered par défaut.

VII - Tolérance de panne

La tolérance de panne est assurée par un couple de HornetQ avec un serveur actif et un serveur de retrait. Les deux serveurs partagent les mêmes messages persistants, via un stockage partagé ou par réplication. Les clients se connectent initialement au serveur actif et si celui-ci tombe, ils sont automatiquement redirigés vers le serveur de retrait.

Le serveur de retrait ne joue aucun rôle en fonctionnement normal, il n'entre en jeu que pour pallier une défaillance du serveur principal. Le serveur de retrait doit être déclaré comme tel dans le fichier hornetq-configuration.xml.

<backup>true</backup>

Le serveur actif doit déclarer un connecteur vers le serveur de retrait dans son fichier hornetq-configuration.xml. Chaque serveur actif ne peut déclarer qu'un seul serveur de retrait, qui doit être démarré en premier.

<backup-connector-ref connector-name="netty-replication"/>

La synchronisation des messages entre les serveurs peut être assurée par un stockage partagé ou par une réplication des messages. Le prérequis pour le stockage partagé est un disque partagé, par exemple en NFS. Ensuite, chaque serveur doit être configuré (hornetq-configuration.xml).

<shared-store>true<shared-store>
<journal-directory>some-shared-location</journal-directory>

Pour la réplication des messages, il faut que le serveur de retrait ait un acceptor pour recevoir les messages et que le serveur actif ait un connector pour envoyer les messages ; ce connector doit être associé à la ConnectionFactory.

<!-- hornetq-jms.xml -->
<connection-factory name="NettyConnectionFactory">
<connectors>
<connector-ref connector-name="netty"
backup-connector-name="netty-backup"/>
</connectors>
<entries>
...
</entries>
</connection-factory>

A l'heure actuelle, avec HornetQ 2.1, on ne peut pas encore parler d'environnement de haute disponibilité à cause de son caractère trop statique. En effet, le serveur actif doit déclarer une connexion vers un unique serveur de retrait, il n'y a pas de mécanisme de découverte comme pour la répartition de charge. Enfin si le serveur de backup prend la main, il n'y a plus d'instance HornetQ pour jouer le rôle de retrait et que pour remettre l'environnement en place, il faut redémarrer le couple d'instances.

Vous l'aurez noté, il existe des contraintes importantes, qui pourront certainement être assouplies dans un avenir proche. Cette faiblesse actuelle étant relevée, je m'empresse de la pondérer. En environnement clustered, avec la rotation des clients lorsqu'un serveur tombe, les autres continuent de recevoir et de distribuer les nouveaux messages. Et les messages persistants du serveur tombé seront distribués à son redémarrage. A vous de voir si ce délai est critique...

Conclusion

HornetQ constitue une avancée importante dans le messaging Java. C'est vrai que ce sujet, bien que fondamental dans bon nombre de systèmes d'information, a peu évolué. Il n'y a qu'à voir l'historique de JMS : on utilise la version 1.1 qui date d'une dizaine d'années. HornetQ apporte un vent de fraîcheur, avec les APIs d'interopérabilité et les connexions avec les technologies les plus récentes du Web. HornetQ bouscule aussi ce petit monde avec ses performances.

Pour finir, à l'heure où on parle beaucoup d'expressivité du code, avec des APIs fluent, j'ai apprécié de retrouver ces qualités dans les pratiques de configuration de HornetQ. Ayant pratiqué JBoss Messaging, j'ai beaucoup aimé la façon dont on travaille dans HornetQ : les paramètres sont clairs et peuvent souvent être compris sans documentation, ce qui apporte de la fluidité à notre travail. Et pour ne pas nuire à la qualité de l'ensemble, une documentation riche est disponible sur le site d'HornetQ.

Remerciements

Je souhaite adresser un grand merci à ceux qui m'ont aidé à rédiger cet article.

Tout d'abord merci à Jeff Mesnil. Sa présentation au LyonJUG m'a donné envie de me plonger dans HornetQ et sa relecture technique m'a rassuré quant au contenu de l'article.

Enfin, je remercie Claude Leloup qui, par sa relecture, a essayé de masquer mes lacunes en langue française.

(1)new SPECjms2007 benchmark record using HornetQ

(2)HornetQ - the Performance Leader in Enterprise Messaging