ActiveMQ

Pendant une petite semaine j’ai regardé de plus près le serveur de JMS ActiveMQ. Je vais vous faire partager mes premières remarques.

Le coeur

ActiveMQ est un broker de message à utiliser pour le développement d’une architecture MOM (Middleware Oriente Messages). Je l’ai choisi aussi pour sa bonne intégration avec Spring et sa bonne tenue en charge.

Son architecture est simple : (source : http://activemq.apache.org/code-overview.html)

photo

Donc trois couches autour de la gestion des Queues et des Topics :

1 – Message Store

On a la possibilité de choisir comment persister (ou non) les messages.

Sans persistance (message en mémoire) le risque est bien sûr de perdre des messages en cas d’arrêt du processus, mais c’est aussi comme cela que l’on obtient les meilleures performances. Par contre je n’ai pas testé le cas sans consommateur de message et où on peut vite obtenir un OutOfMemory.

Par défaut, ActiveMQ propose une persistance sous forme de fichiers assez performante nommée simplement “AMQ Message Store”. Dans la même famille un module kaha sauvegarde les messages sur le disque dur.

Attention par contre j’ai constaté des problèmes de perte de messages avec une persitence kaha lorsque j’avais 100 threads en parallèle envoyant chacun dix messages par seconde. En passant sur AMQ Message Store je n’ai pas eu de problème.

Il y a aussi les solutions reposant sur une base de données. Par défaut ActiveMQ lance un Derby embarqué mais il est possible d’utiliser un pont JDBC pour se connecter à une vrai base. C’est même fortement recommendé au vue des temps d’accès plus que moyens de Derby.

Pour finir j’ai comparé le temps de création de 10 000 Messages en fonction du système de persistance choisi. Ces chiffres sont bien sûr à prendre uniquement comme éléments de comparaison entre les solutions. J’ai fait ces mesures sur mon petit PC.

</p>
Persistance Temps de création
Mémoire 1.3s
AMQ 2.6s
Kaha 3s
Base Oracle 61s
Derby 527s

</center>
On note donc un facteur deux entre une persistance mémoire et une persistance sur le file system. Les rapports allant de 40 à 400 pour les solutions base de données sont plus discutables. En effet mon disque dur n’est pas forcément bien défragmenté et ma base Oracle est sur mon réseau derriere plusieurs fire-wall que je ne maitrise pas.

2 – Connectors

ActiveMQ offre différents connecteurs pour échanger des messages. De base il est possible de se connecter en tcp, mais il est aussi possible d’échanger en texte via HTTP (REST ou SOAP).

Je ne m’attarderai pas trop sur le sujet car je n’ai pas beaucoup testé ces fonctionnalités

3 – Network Services

Dans cette section on trouve les différents moyens de dialogue entre les serveurs activeMQ. Entre autres un composant Discovery permettant de retrouver d’autres serveurs sur le même réseau automatiquement.

Par exemple un serveur A qui n’a pas de consommateur connecté pour lire des messages pour une queue B ne pourra pas traiter ces messages. Par contre il va pouvoir trouver sur le réseau un autre serveur avec le bon consommateur et lui envoyer ses messages.

Intégration avec Spring

L’utilisation dans spring est simple, il faut utiliser un BrokerFactoryBean pour démarrer le serveur.

<bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
      <property name="config" value="classpath:activemq.xml"></property>
      <property name="start" value="true"></property>
</bean>

Ensuite pour se connecter au broker on utilise une Factory activeMQ et un template JMS de Spring.


<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
  <property name="connectionFactory">
    <bean class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="broker">
      <property name="brokerURL">
        <value>vm:localhost</value>
      </property>
    </bean>
  </property>
</bean>



<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory">
    <ref local="jmsFactory" />
  </property>
</bean>



<bean id="queue1" class="org.apache.activemq.command.ActiveMQQueue">
  <constructor-arg index="0">
    <value>queue1</value>
  </constructor-arg>
</bean>

 
Enfin on peut déclarer nos beans producteurs et consommateurs





<bean id="consumer1" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="concurrentConsumers" value="5" ></property>
  <property name="connectionFactory" ref="jmsFactory" ></property>
  <property name="destination" ref="queue1" ></property>
  <property name="messageListener" ref="myConsumerBean" ></property>
</bean>



<bean id="AuthentificationBean" class="com.test.ProductorBean">
  <property name="template" ref="jmsTemplate" ></property>
  <property name="dest" ref="queue1" ></property>
</bean>

Répartition de charge

Le principal intérêt des architectures MOM est leur capacité à monter en charge en la distribuant sur plusieurs noeuds.

![photo](/images/uploads/2009/04/repartition1.png)

Dans cette configuration le producteur à un rôle de loadBalancer et il suffit d’ajouter des serveurs pour absorber plus de charges. De plus l’utilisation d’un NetworkConnector permet aux serveurs de dialoguer entre eux et d’échanger des messages en cas de surcharge d’un serveur ou simplement de l’arrêt d’un des consommateurs.

Le point faible de cette architecture est le risque de perte de message. En effet si le serveur A tombe la chaine n’est pas cassée grâce à B et C. Mais si A avait persisté des messages non consommés avant le crash, ils resteront en attente jusqu’au redémarrage du serveur. Pire, si la base de données ou le disque dur où ils étaient stockés est mort, ces messages sont définitivement perdus.

Master Slave

Une solution pour éviter d’avoir des messages en attente à cause d’un arrêt de serveur est l’utilisation de la configuration Master/Slave. ActiveMQ propose 4 solutions de mise en place :

1 – Pure Master Slave

Une configuration simple à mettre en place, il suffit d’indiquer dans le fichier de configuration du serveur esclave l’adresse du maitre.

Par contre le gros problème de cette solution vient du mode de reprise sur incident. En effet si le maitre s’arrête, l’esclave reprend bien le relais. Mais après avoir relancé le noeud maitre, on doit relancer l’esclave pour revenir à un état stable. Autre gros problème un maitre ne peu avoir qu’un seul esclave.

2 – Shared File System Master Slave

Dans ce cas le principe est d’utiliser une persistance fichier pour les messages et de faire pointer plusieurs instances d’activeMQ sur le meme repertoire. La premiere insctance pose un lock sur les fichier et devient le Master, les autres attendent de pouvoir poser leur lock et sont donc Slave pendant ce temps.

On ne retrouve pas le problème de redémarrage de la première solution, car l’esclave qui a repris le lock sur les fichiers est passé maitre et au redémarrage du noeud tombé il repasse naturellement esclave.

3 – JDBC Master Slave

Exactement le même fonctionnement que la solution 2 mais en utilisant une base de données à la place des fichiers.

4 – KahaDB Master Slave

Cette solution est intéressante sur le papier car elle permet un fonctionnement Master/Slave sans avoir besoin de partager des fichiers ou une base. Mais elle n’est pas livrée dans la release de activeMQ pour le moment et je n’ai pas réussi à faire fonctionner la version nightly-build.

Configuration choisie

Pour l’architecture de mon application je me suis orienté vers la seconde solution de configuration Master/Slave pour construire la solution suivante :

![photo](/images/uploads/2009/04/repartition2.png)

Dans cette configuration on garde une capacité à monter en charge par l’ajout de serveur mais on a en plus l’utilisation du mode Master/Slave pour prévenir la perte ou le blocage de message.
Pour le moment je suis resté sur une persistance fichier aux vues des résultats de performance, mais il faudra les confirmer sur les machines cibles et avec un disque réseau pour le partage.

J’ai aussi rencontré un problème au niveau des JVM contenant un activeMQ esclave. En effet le bean de lancement du serveur bloque la JVM pendant l’attente du lock et comme dans mon cas je voulais avoir mes consommateurs dans la même JVM, ils étaient bloqués.

Pour pouvoir avoir dans une JVM un serveur ActiveMQ et des consommateurs connectés au master, j’ai du modifier le BrokerFactoryBean pour lancer dans un autre Thread le serveur. Grâce à cela le serveur hébergeant le noeud esclave travaille même lorsque le maitre n’est pas mort.

Pour absorber plus de charge on a deux possibilités

  • Ajouter un autre groupe Master/Slave
  • Ajouter des slaves

Si on a un problème avec ActiveMQ qui n’absorbe pas assez bien les messages il faut choisir la première solution. Par contre si c’est un problème de consommation pas assez rapide, il suffit d’ajouter des esclaves et donc de nouveaux consommateurs.

Conclusion

Pour rendre plus concret ce dernier exemple vous pouvez télécharger une maquette de celui ci . J’ai ajouté des fichiers .launch eclipse pour configurer le port JMX. C’est très pratique pour observer avec la jconsole l’activité du serveur activeMQ.

Ces premiers essais avec activeMQ sont assez satisfaisants et donnent envie de l’utiliser dans nos projets. Je garde aussi un oeil sur les évolutions du mode “KahaDB Master Slave” qui devraient bientot etre livrées.