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.

Pour ce TP, un seul utilisateur est défini au sein du serveur 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 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 programmes 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.

RabbitMQ n'offre pas nativement de service assurant la transparence à l'hétérogénéité, ce qui nous allons explorer ici.

Dans cet exercice, 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 informations 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 intervenu 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();
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érialisation par la méthode split. La méthode split fonctionne de la façon suivante :
 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 (c-à-d le numéro de ligne de bus, soit 10), result[1] la 2ème information (l'événement, c-à-d Panne) et result[2] la 3ème information (le statut, c-à-d 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:

On implante maintenant les règles de filtrage appliquées par le programme Filtrage.java ainsi que la communication de 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. Compter le nombre total de pannes, d'accidents et de travaux, puis envoyer à chaque nouvel événement la valeur de ces 3 compteurs à 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 à réaliser est composée de 3 programmes et de 2 exchanges (voir figure ci-dessus) qui sont organisés de la façon suivante :
  1. Le programme Scatter.java pilote les calculs à réaliser par vague 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 exercice est d'implanter le patron de conception 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 exercice, 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 types de requetes à un worker: 1) soit une demande pour convertir un nombre en binaire 2) soit une demande afin pour que le worker 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






Dans ce exercice, on revient sur le patron de conception pooling statique de l'exercice 5 afin de rentre dynamique le fonctionnement du dispatcher. Le dispatcher utilise un tableau pour déterminer dans quel ordre le workers doivent être invoqués. Dans cet exercice, on souhaite modifier cet ordre à l'exécution. Pour ce faire :
  1. Récupérer les fichiers à partir du répertoire EXO8 . Sauver tous les fichiers dans un répertoire nommé EXO8. Copier les fichiers Pool.java, Dispatcher.java et Worker.java que vous avez implantés pour l'exercice 5.
  2. Modifier Pool.java en ajoutant deux méthodes pour sérialiser et désérialiser le tableau utilisé pour ordonnancer les workers.
  3. Implanter la classe Update_Pool.java qui définit avec Pool.java un nouvel ordonnancement des workers, le sérialise puis l'envoie à Dispatcher.java
  4. Vérifier que Dispatcher.java reçoit bien le nouvel ordonnancement et active effectivement les workers selon l'ordre défini par Update_Pool.java


Exercice 9 : design-pattern TMR



L'objectif de cet exercice est de mettre en oeuvre un exemple du patron TMR (Triple Modular Redundancy). Nous allons procéder en 2 temps, ou 2 questions. Dans la première question, on implante le mécanisme de vote permettant de recouvrir une perte de message ou des données produites par des programmes byzantins. Dans la seconde question, on adapte la solution précédente afin de déployer l'application sur deux serveurs RabbitMQ/machines afin de recouvrir une panne franche du serveur.

Question 1 :

Tout d'abord, récupérer les fichiers à partir du répertoire EXO9Q1 . Sauver tous les fichiers dans un répertoire nommé EXO9Q1.


Cette première application est composée de 4 processus : un voteur (qui exécute le programme Voter.java) et 3 capteurs (qui exécutent le programme Capteur.java). Ces processus interagissent par un exchange/une file d'attente et se comportent ainsi :
Implanter le mécanisme de vote et tester le avec les scripts lance.sh et capteur.sh.

capteur.sh vous permettra de tester quelqu'unes des règles ci-dessus mais il ne faut pas hésiter à le compléter si cela est nécessaire.


Question 2 :




L'objectif de cette deuxième question est d'appliquer un mécanisme de redondance active pour les brokers cette fois-ci. Dans ce patron, comme indiqué dans la figure ci-dessus, 1 voteur est instancié sur 2 brokers différents. Toutefois pour ce TP, les 2 voteurs seront instanciés dans le même broker pour simplifier la mise en place du TP.

Récupérer les fichiers à partir du répertoire EXO9Q2 . Sauver tous les fichiers dans un répertoire nommé EXO9Q2.

Modifier votre application de la question 1 de sorte que :
  1. Le programme Recepteur.java est instancié une fois.
  2. Comme indiqué dans la figure ci-dessus, 2 voteurs sont instanciés.
  3. Chacun des 3 processus capteurs envoient leur mesure à chacun des 2 voteurs.
  4. Chaque voteur applique l'algorithme implanté dans la question 1 et transmet le résultat du vote au processus Recepteur.java Les voteurs n'affichent donc plus les résultats de vote mais les transmettent à Recepteur.java
  5. Comme les voteurs, Recepteur.java est initialement en attente du résultat du vote pour le numéro de vote 1.
  6. Lorsque Recepteur.java reçoit le résultat du vote pour le numéro de mesure attendu, alors il affiche le résultat et commence à attendre le numéro suivant.
  7. Lorsque Recepteur.java est en attente du résultat du vote pour le numéro n et qu'il reçoit le résultat numéro k tel que k>n, alors : 1) il arrête d'attendre le résultat numéro n qui est considéré comme perdu, 2) affiche le résultat du vote k, 3) il se met en attente de résultat k+1.
  8. Lorsque Recepteur.java est en attente des mesures pour le numéro n et qu'il reçoit une mesure dont le numéro est inférieur à n, alors il ignore le message/le résultat de mesure reçu.
  9. Lorsque Recepteur.java a reçu le résultat pour le numéro n et qu'il reçoit le second résultat pour le numéro n, alors il ignore le dernier message/le résultat reçu.

Implanter cette seconde version du mécanisme de vote et tester le avec les scripts lance.sh et capteur.sh.

capteur.sh vous permettra de tester quelqu'unes des règles ci-dessus mais il ne faut pas hésiter à le compléter si cela est nécessaire.


Exercice 7 : contrôle de 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