TP RabbitMQ avec Java
Frank Singhoff
Sommaire
L'objectif de cet exercice est de vous montrer comment créer
une application avec RabbitMQ.
Nous utiliserons des clients Java pour ce faire.
Dans un premier temps, il est nécessaire de mettre en place
l'environnement pour l'utilisation de RabbitMQ avec java.
Dans un terminal, lancer les commandes suivantes :
cd /home/tp/poc_rabbitmq
docker-compose up -d
Cette première commande lance le serveur RabbitMQ.
Une fois lancé, le serveur est en attente sur
la machine localhost et le port 8050.
Cette distribution de RabbitMQ
(disponible sur GitHub depuis http://github.com/hugosinghoff/poc_rabbitmq et utilisant une image docker fournie par Bitnami)
contient une interface de supervision qui permet de connaitre l'état du serveur (mémoire consommée, connexions réseaux en cours, états des queues, ...) et également
d'administrer les utilisateurs du serveur.
Dans ce serveur, un seul utilisateur est défini dont les données sont les suivantes :
- Login: user
- Password: bitnami
Pour vérifier que le serveur est correctement lancé, vous pouvez:
- Lancer firefox en ouvrant l'URL http://localhost:8051
- Vous connecter avec le login/password ci-dessous
- Puis inspecter les différentes informations offertes par l'interface d'administration de RabbitMQ.
Notez que c'est surtout quand vous ferrez tourner vos programmes que vous constaterez évoluer
les informations présentées dans cette interface.
Nous allons maintenant utiliser des clients Java pour ce serveur.
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 ces
fichiers dans un répertoire
EXO1.
Cette application est composée d'un exchange et de deux programmes:
Publisher.java et Subscriber.java ;
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 :
- Quels ports/machines sont utilisés par ces programmes ?
- Pourquoi le message coucou3 n'est pas reçu ?
- Le programme qui lit les messages utilise-t-il des communications en mode push ou en mode pull ?
- Quel est le nom de l'exchange utilisé pour ces communications ?
- 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 .
Pour cet exercice, récupérez les fichiers à partir
du répertoire EXO2 . Sauvez tous ces
fichiers dans un répertoire nommé EXO2.
Compléter les programme Lecteur_Depeche.java et Redacteur_Depeche.java de sorte que :
- Redacteur_Depeche.java prends 3 paramètres : 2 noms de ville et 1 message à envoyer.
Ce programme envoit le message deux fois avec une clef différente:
les deux noms de ville
sont utilisés comme clef.
- Lecteur_Depeche.java qui lit et affiche les messages pour une ville donnée.
Ce programme ne prend qu'un seul paramètre : le nom de ville dont le lecteur souhaite recevoir
les informations. Ici aussi, le nom de ville est employé comme une clef.
- On vous donne un exemple d'utilisation avec le programme lance.sh qui doit générer le résultat suivant :
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 3 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 3 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:
- Le programme Emetteur.java produit et émet les
messages vers le programme Filtrage.java.
Ce programme est employé pour signaler un événement sur une ligne de bus.
Chaque message émit/reçu décrit un événement intervenu sur une ligne de bus et décrit
également son impact sur la ligne en question.
Chaque message contient donc 3 informations: le numéro de ligne de bus
concerné, l'événement et l'état de la ligne.
Les classes Evenement.java et Statut.java décrivent
respectivement les événements possibles et l'état
de la ligne après l'événement.
- Le programme Filtrage.java réceptionne les messages
émis par Emetteur.java et renvoie également différents messages
vers Recepteur.java
- Le programme Recepteur.java permet de réceptionner les différents messages
envoyés par Filtrage.java.
Ce programme peut être utilisé soit par les usagers,
soit par l'atelier de maintenance de la compagnie de bus. Les usagers et l'atelier
utilisent tous
le programme Recepteur.java pour recevoir leurs messages.
Question 1:
Dans un premier temps, on regarde uniquement les communications entre
Emetteur.java et Filtrage.java.
Filtrage.java se contente de recevoir les messages
de Emetteur.java et de les afficher à l'écran.
On rappelle que chaque message produit par Emetteur.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 Emetteur.java.
Pour produire dans Emetteur.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 afin de l'ajouter
dans le message à transmettre.
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 #,
lorsque le message est reçu par Filtrage.java, les 3 informations du message
peuvent être séparées avant affichage par la méthode
split de cette façon:
String [] result = Message.split("#");
Où result[0] contient la première information (ex: le numéro de ligne de bus),
result[1] la 2ème information (ex: l'événement) et result[2] la 3ème
information (ex : le statut).
Enfin, pour désérialiser les énumérés, vous utiliserez les méthodes Evenement.valueOf et Statut.valueOf.
Travail à faire :
Modifier les programmes Emetteur.java et Filtrage.java
afin d'implanter la communication entre ces 2 programmes.
Question 2:
Maintenant, on implante les règles de filtrage appliquées par le programme Filtrage.java
ainsi
que
la communication entre Filtrage.java et Recepteur.java.
Pour chaque message reçu par Filtrage.java, ce programme doit :
- Transfèrer le message sans le modifier vers l'atelier.
- Envoyer un message vers les usagers en indiquant
l'état de ligne. Chacun de ces messages doit comporter le numéro de ligne concerné
ainsi que l'état de la ligne
(le message peut être construit avec la classe Statut.java).
- Comptabiliser le nombre d'événements et envoyer cette information à l'atelier.
Concernant les règles de filtrage:
-
Lorsque le programme Recepteur.java est utilisé par un usager des
bus, le programme ne réceptionne que les messages indiquant l'état des lignes
de bus et uniquement pour certains événements ayant modifiés l'état de la ligne.
L'événement constitue une clef pour Recepteur.java : en d'autres termes,
chaque récepteur indique pour quels événements il souhaite obtenir l'état des lignes.
Ainsi, le script lance.sh indique que usager1
souhaite recevoir l'état des lignes lorsque des évenements Travaux
et Accident interviennent.
-
Lorsque le programme Recepteur.java est utilisé par
l'atelier de la compagnie de bus, le programme doit recevoir tous les messages
émis par Filtrage.java.
Travail à faire :
Modifier
le programme Filtrage.java
et écrire le programme
Recepteur.java pour implanter la communication entre ces 2 programmes.
Pour cet exercice, récuperer les
fichiers qui
sont disponibles dans ce répertoire . Sauvez tous ces
fichiers dans un répertoire
EXO4.
Dans cet exercice, on cherche à controler 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 :
-
Le programme Web.java est un programme déclenché à chaque fois qu'un client
de la banque demande une opération bancaire.
A chaque fois qu'un utilisateur souhaite faire une opération de débit ou de crédit sur
son compte bancaire,
Web.java génère un message vers RabbitMQ. Chaque message est composé : 1) d'un numéro
d'opération unique permettant d'identifier chaque opération ; 2) du numéro de compte concerné ;
3) du type d'opération qui peut être soit
un débit soit un crédit ; 4) du montant (en euros) de l'opération.
Chaque message est envoyée dans un exchange avec comme clef le numéro de compte.
On remarque qu'au même instant, plusieurs programmes Web.java peuvent s'exécuter simultanément puisque
plusieurs clients peuvent se connecter à leur banque simultanément.
-
Le programme Compte.java gère un compte
bancaire donné. Le programme lit depuis l'exchange tous les messages associés au compte qu'il gère :
la clef constitue le numéro de compte associé au programme.
Le rôle du programme Compte.java est de stocker dans un fichier texte chaque opération
effectué sur le compte. Chaque opération est enregistrée sur une ligne du fichier texte. Chaque
ligne dans le fichier contient également le solde du compte après applicationn de l'opération.
On suppose qu'au lancement de Compte.java, le solde est égal à 0.
Pour chaque message lu par Compte.java, le programme :
- Lit la dernière ligne du fichier texte afin de récupérer le solde du compte
- Calcule le solde du compte après application de l'opération contenue dans le message recu
- Puis stocke le message ainsi que le nouveau solde en ajoutant une ligne dans le fichier texte.
Voici par exemple les messages produits par Web.java pour une opération de crédit
et deux opérations de débit, pour respectivement 100 euros, -10 euros et -20.
Ces trois opérations concerne le numéro de compte 1 (2ème champs du message). Le
premier champs constitue le numéro d'opération (respectivement 1, 2 et 3 pour la dernière opération).
tp@tphost:$java Web 1 1 credit 100
Compte 1 recoit:1#1#credit#100
tp@tphost:$ java Web 2 1 debit -10
Compte 1 recoit:2#1#debit#10
tp@tphost:$ java Web 3 1 debit -20
Compte 1 recoit:3#1#debit#20
Les 3 opérations conduisent à un fichier texte contenant 3 lignes. Chaque ligne
contient l'ensemble des informations d'une opération ainsi que le solde actuel du
compte. Ainsi, nous obtenons dans le fichier texte ci-dessous pour le compte numéro 1:
1#1#credit#100#100
2#1#debit#-10#90
3#1#debit#-20#70
Dans cet exemple de fichier,
la 3ème ligne montre qu'après la 3ième opération (opération de
débit de -20 euros) le solde du compte est de 70 euros.
En effet, le 1er champs est le numéro d'opération. Le second champs est
le numéro
de compte. Le 3ème champs est l'opération. La 4ième champs est le montant de l'opération et le
5ième champs constitue le solde du compte bancaire.
Pour les entrées/sorties sur fichier, vous pouvez utiliser le programme Linefile.java
qui permet
d'ajouter une ligne dans le fichier (méthode AppendLine), de lire la dernière
ligne du fichier (méthode ReadLastLine) ou de lire l'intégralité
du fichier ligne par ligne (méthode ReadAllLine).
-
Le 3ième programme, Controleur.java gère la perte de message.
On lance un programme Controleur.java pour chaque programme Compte.java.
Le programme Controleur.java recoit tous les messages à destinations
du programme Compte.java.
A chaque message reçu, Controleur.java, après une attente de 10 secondes,
vérifie que l'opération associée au message a bien été réalisée en lisant le fichier texte associé.
Si l'opération n'est pas présente dans le fichier texte, alors Controleur.java renvoit le message
vers le serveur RabittMQ afin que Compte.java puisse réaliser l'opération perdue.
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.
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 EXO5 . Sauvez tous ces
fichiers dans un répertoire
EXO4.
Question 1:
- Compiler, tester et étudier ces programmes.
- Chaque classe correspond à une entité CORBA.
Dites pour chaque classe à quoi elle correspond dans CORBA.
- Comment sont véhiculés les requêtes du client au serveur ?
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 EXO5Q2 . Sauvez tous ces
fichiers dans un répertoire
EXO5Q2.
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