TP RabbitMQ avec Java

Frank Singhoff

Sommaire




Lancement du serveur RabbitMQ



Dans un premier temps, il est nécessaire de mettre en place l'environnement pour l'utilisation de RabbitMQ avec java. Pour ce faire, il faut :

  1. Démarrer le serveur RabbitMQ
  2. Modifier votre environnement de travail afin d'utiliser le compilateur Java et les bibliothèques associées permettant d'écrire et exécuter des clients pour le serveur.
Concernant le serveur, il existe plusieurs solutions pour le lancer:
  1. Sur les machines du réseau enseignement, le plus simple et de lancer le script
    		./docker_rabbit.bash
    		
    qui va lancer un serveur via un container docker.
  2. Sur une machine Linux de type Ubuntu ou debian, il est possible d'installer les paquetages suivants :

    		sudo apt-get install rabbitmq-server
    		sudo rabbitmq-plugins enable rabbitmq_management
    		
    Le serveur RabbitMQ est alors lancé automatiquement au démarrage de la machine.
  3. Sur la machine virtuelle distribuée pour l'UE SOR, rabbitmq est déjà installé et automatiquement lancé.


Une fois lancé, le serveur est en attente sur la machine localhost . Le serveur utilise 2 ports TCP: Les numéros de port dépendent de la configuration du serveur et donc de son installation sur la machine hôte. Par ailleurs, Un serveur est partagé par potentiellement plusieurs utilisateurs. Ainsi, chaque client doit se connecter avec un login/password.

Dans ce serveur, un seul utilisateur est défini avec les informations suivantes :
Avec certaines distributions RabbitMQ, le service de supervision n'est pas lancé automatiquement. Si le service de supervision est lancé, il est possible de s'y connecter avec un navigateur web:
  1. Par exemple, lancer firefox en ouvrant l'URL http://localhost:PPPP où PPPP est le numéro de port associé au service de supervision.
  2. Et connectez vous au serveur avec un login/password comme celui donné ci-dessus.



Exercice 1 : exemple d'un programme RabbitMQ avec Java

L'objectif de cet exercice est de vous montrer comment créer une application avec RabbitMQ. Nous utiliserons des clients Java pour ce faire.

Afin de pouvoir compiler les clients Java et utiliser les jar associés, récupérer le fichier rabbit.bash suivant et appliquer la commande suivante :

source rabbit.bash


Puis, récupérez les fichiers de ce premier exercice à partir du répertoire EXO1 . Sauvez tous les fichiers dans un répertoire EXO1.





Cette application est composée d'un exchange et de deux programmes : Publisher.java et Subscriber.java ;

Attention aux informations sur les ports et login/password sur le serveur RabbitMQ qui doivent correspondre à la configuration du serveur. Pour compiler ces programmes, lancer la commande :
make



Puis, lancer les clients qui lisent les messages. Pour lancer et arrêter ces processus, vous pouvez soit utiliser les scripts lance.sh et stop.sh, ou taper directement ces commandes :
java Subscriber paul 'legumes.*' &
java Subscriber jacques legumes.tomate &
java Subscriber eric legumes.radis legumes.tomate &
Puis, lancer successivement les programmes qui émettent les messages par :
java Publisher legumes.radis coucou1
java Publisher legumes.tomate coucou2 
java Publisher plouf coucou3
Ce qui devrait générer l'affichage :
Publisher : legumes.radis coucou1
Subscriber paul recoit legumes.radis : coucou1
Subscriber eric recoit legumes.radis : coucou1
Publisher : legumes.tomate coucou2
Subscriber paul recoit legumes.tomate : coucou2
Subscriber eric recoit legumes.tomate : coucou2
Subscriber jacques recoit legumes.tomate':'coucou2
Publisher : plouf coucou3


Regardez ces programmes, puis,répondez aux questions suivantes :
  1. Indiquez les messages reçus par chaque processus.
  2. Pourquoi le message coucou3 n'est pas reçu ?
  3. Le programme qui lit les messages utilise-t-il des communications en mode push ou en mode pull ?
  4. Quel est le nom de l'exchange utilisé pour ces communications ?
  5. Quelles commandes contient le script stop.sh ?
  6. Lancer les 2 programmes selon plusieurs scénarios et naviguer dans l'interface d'administration pour voir l'effet de l'exécution de ces programmes.



Pour vous aider, l'ensemble de l'API Client Java/RabbitMQ est disponible ici .


Exercice 2 : premier programme RabbitMQ avec filtrage



Pour cet exercice, récupérez les fichiers à partir du répertoire EXO2 . Sauvez tous les fichiers dans un répertoire nommé EXO2. Compléter les programme Lecteur_Depeche.java et Redacteur_Depeche.java de sorte que :




Exercice 3 : sérialisation/désérialisation



Pour cet exercice, récupérez les fichiers à partir du répertoire EXO3 . Sauvez tous ces fichiers dans un répertoire nommé EXO3.

On vous demande d'implanter une application composée de 4 programmes et dont le but est d'émettre, de filtrer et de transmettre des messages permettant de connaitre l'état d'un réseau de lignes de bus.





L'application a réaliser est composée de 4 programmes et de 2 exchanges (voir figure ci-dessus).

Les scripts lance.sh et stop.sh vous montrent comment lancer et arrêter ces programmes. Chaque programme fonctionne de la façon suivante:

Question 1: sérialisation/désérialisation

Dans un premier temps, on regarde uniquement les communications entre Capteur.java et Filtrage.java. L'objectif de cette question est d'implanter la sérialisation et désérialisation des données échangées avec RabbitMQ. Dans cette question, Filtrage.java se contente de recevoir les messages de Capteur.java et de les afficher à l'écran.

On rappelle que chaque message produit par Capteur.java doit comporter 3 informations : le numéro de ligne de bus, l'événement qui est intervénu sur la ligne et l'impact sur la ligne. Chaque message est une chaine de caractères que doit construire Capteur.java. Pour produire dans Capteur.java les messages à partir des classes Statut et Evenement, vous pouvez utiliser la méthode name. Ainsi :
String s = Statut.Arret.name();
qui permet de sérialiser la valeur Arret de l'énuméré Statut.

On vous demande de séparer chacune des 3 informations concaténées dans un message par le caractère #. Grâce au caractère #, lorsqu'un message est reçu par Filtrage.java, les 3 informations du message peuvent être séparées lors de la désér par la méthode split de cette façon:
 String [] result = M.split("#");
 
Si le message est constitué de la chaine de caractères 10#Panne#Arret, alors result[0] contient la première information (ex: le numéro de ligne de bus, soit 10), result[1] la 2ème information (l'événement, soit "Panne") et result[2] la 3ème information (le statut, soit "Arret"). Enfin, pour désérialiser les énumérés, vous utiliserez les méthodes Evenement.valueOf et Statut.valueOf.

Travail à faire :

Implanter la sérialisation/désérialisation des messages dans la classe Message_bus.java.

Modifier les programmes Capteur.java et Filtrage.java en utilisant Message_bus.java afin d'implanter la communication entre ces 2 programmes et en particulier les opérations de sérialisation et désérialisation des messages.



Question 2:

Maintenant, on implante les règles de filtrage appliquées par le programme Filtrage.java ainsi que la communication entre Filtrage.java vers Usager.java et Atelier.java.

Pour chaque message reçu par Filtrage.java, ce programme doit :
  1. Transfèrer le message sans le modifier vers les usagers et l'atelier.
  2. Comptabiliser le nombre total de pannes, accidents et travaux, puis envoyer ces informations à l'atelier. La structure de ce nouveau message ainsi que les méthodes pour le sérialiser/désérialiser doivent être implantés dans la classe Message_stat.java


Concernant les règles de filtrage:

Travail à faire :

Modifier le programme Filtrage.java et écrire les programmes Usager/Atelier.java pour implanter la communication entre ces 3 programmes.





Exercice 4 : design-pattern Scatter/gather



Pour cet exercice, récupérez les fichiers à partir du répertoire EXO4 . Sauvez tous ces fichiers dans un répertoire nommé EXO4.


L'objectif de cet exercice est d'expérimenter un modèle classique d'interaction pour des entitées s'exécutant en parallèle sur une ou plusieurs machines : le design-pattern scatter/gather.
Nous allons implanter une application conforme à ce modèle dont l'objectif est de convertir des entiers décimaux vers une représentation binaire. L'application a réaliser est composée de 3 programmes et de 2 exchanges (voir figure ci-dessus) qui sont organisés qui fonctionnent de la façon suivante :
  1. Le programme Scatter.java pilote les calculs à réaliser par vaque successive. Un vague consiste à envoyer un nombre à convertir vers chacun des 3 processus exécutant Traitement.java.
  2. Une fois une vague lancée, Scatter.java attend d'être informé par Gather.java que la vague en cours est terminée avant d'envoyer la vague suivante, c-à-d envoyer 3 nouveaux entiers aux processus Traitement.java.
  3. Chaque Traitement.java attend de recevoir un nombre à convertir. Sur réception d'un nombre, ce programme le convertit en binaire et envoie le résultat au programme Gather.java. Pour traduire un entier en binaire, Traitement.java applique cet algorithme:
    1. Le programme divise par 2 l'entier reçu et mémorise le reste (0 ou 1).
    2. Si le résultat de la division est supérieur à 1, le programme refait une division avec le dividende de la division précédente.
    3. Si le résultat de la division est inférieur ou égal à 1, alors le calcul est terminé et les différents 0 et 1 mémorisés pendant le calcul constituent la représentation binaire du nombre reçu.
  4. Le programme Gather.java attend les 3 nombres convertis en binaire. Lorsqu'il reçoit un nombre en binaire, alors il l'affiche à l'écran. Lorsqu'il a reçu 3 nombres binaires, il affiche à l'écran que la vague actuelle est terminée, ce qui permet à l'utilisateur de Scatter.java de lancer la vague suivante.


Travail à faire :

Implanter les programmes Scatter/Gather/Traitement.java de cette application.

Tester vos programmes en traduisant en binaire les 12 nombres suivants. Comme l'application est composée de 3 instance du programme Traitement.java, il faudra donc 4 vagues pour traduire ces 12 nombres. Les nombres à traduire sont :
121456
223065
4593687
954067
3485654
20976364
2305435
1203409
34356
66540
2234000
11333084






Exercice 5 : design-pattern pooling statique



L'objectif de cet exerice est d'implanter le patron pooling. Dans cette première version, l'ordre d'utilisation des workers est statique.

Pour cet exercice, récupérez les fichiers à partir du répertoire EXO5 . Sauver tous les fichiers dans un répertoire nommé EXO5.


La première version de ce patron est une version simple composée de deux programmes : Dispatcher.java et Worker.java :
  1. Dans cet exemple, le pool comporte 3 workers. Chaque worker à un nom unique servant de clef pour les communications et exécute le programme Worker.java.
  2. Dispatcher.java pilote les calculs réalisés par le pool. A titre d'exemple, nous reprennons la traduction des nombres décimaux en binaire. Dispatcher.java maintient la liste des workers actifs et les invoque successivement dans un ordre prédéterminé grâce à la classe Pool.java.
  3. Le nom de chaque worker est donné dans le fichier lance.sh: les noms sont work1, work2 et work3.
  4. Dispatcher.java répartie les requêtes vers les workers selon un algorithme de round-robin. Il peut également décider de retirer un worker de pool. Dispatcher.java peut donc envoyer 2 requetes à un worker: 1) soit une demande pour convertir un nombre en binaire 2) soit une demande afin pour qu'il quitte le pool et donc arrête de fonctionner.
  5. Les workers attendent les requêtes de Dispatcher.java. Lorsqu'une requête est reçue par un worker, il l'exécute. Si la requête est une demande de convertion, alors il calcule cette conversion, affiche le résultat et attend la requête suivante. Si la requête est une demande de retrait du pool, alors le worker stoppe son exécution.




Modifier Dispatcher.java et Worker.java afin d'implanter le fonctionnement ci-dessus.

Exercice 8 : design-pattern pooling dynamique











Exercice 7 : controle sur la perte de message






Pour cet exercice, récuperer les fichiers qui sont disponibles dans ce répertoire . Sauvez tous les fichiers dans un répertoire EXO7.

Dans cet exercice, on cherche à contrôler la perte de message, suite, par exemple, à l'arrêt d'un subscriber. On vous demande de réaliser une application permettant de gérer et contrôler des operations bancaires. L'application est composée des 3 programmes suivants :

Question 1 :

Dans un premier temps, écrire les programmes Web.java et Compte.java. Tester que chaque opération bancaire envoyé par Web.java est bien enregistrée par Compte.java dans le fichier texte associé.



Question 2 :

L'objectif de la question 2 est maintenant d'implanter le controleur. Implanter le Controleur.java, puis, tester qu'en cas de panne de Compte.java, les messages de Web.java sont bien ré-injectés dans le serveur RabbitMQ quand ceux-ci n'ont pas pu être traités par Compte.java.




Exercice 6 : design-pattern RPC



Dans cet exercice, on illustre comment fonctionne un mini-broker d'objets répartis similaire à CORBA ou Java RMI. Ce mini-broker utilise RabbitMQ pour implanter les communications entre les clients et les serveurs qui hébergent les objets accessibles à distance.

Récupérer les fichiers de cet exercice à partir du répertoire EXO6 . Sauvez tous les fichiers dans un répertoire EXO6.


Question 1:




Question 2:

Le programme donné pour la question 1 ne comporte que le code nécessaire pour transmettre la requête du client vers le serveur. Avec cette première version, les objets ne peuvent pas retourner une réponse au client.

Pour cette question, nous vous donnons une nouvelle version de certains fichiers du mini-broker, fichiers qui sont disponibles dans ce répertoire EXO6Q2 . Sauvez tous les fichiers dans un répertoire EXO6Q2.

Modifier les autres classes Java constituant ce mini-broker afin de permettre à l'objet de transmettre une réponse au client via la souche et le squelette. Cette nouvelle version nécessite de mettre en place l'envoie d'un message constituant la réponse du serveur vers le client. Pour ce faire, vous aurez besoin d'un exchange supplémentaire entre l'ObjectAdapter et le Client.



Question 3:

Le programme précédent ne gère qu'un seul objet Calcul. On souhaite étendre ce programme afin de pouvoir manipuler plusieurs objets, c-à-dire plusieurs instances de la classe CalculImpl.java. Chaque objet est identifié par un identifiant unique. Au niveau de l'ObjectAdapter, chaque objet est associé à un thread (thread généralement appelé Servant) qui lit les requetes associées à l'objet et émet les réponses vers le client. Chaque objet doit utiliser une clef de filtrage dédiée.

Modifier les classes Java afin d'implanter cette nouvelle fonctionnalité. Pour tester, vous modifierai Server.java afin d'héberger 3 instances de CalculImpl. Pour tester, le client devra également être modifié.



©(Copyright) singhoff@univ-brest.fr