TP RabbitMQ avec Java
Frank Singhoff
Sommaire
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 :
- Démarrer le serveur RabbitMQ
- 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:
- 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.
- 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.
- 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:
- Un premier port pour recevoir les messages AMQP (souvent 5672, à vérifier selon la configuration du serveur).
- Un deuxième port pour la supervision du serveur (souvent 15672, à vérifier également).
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 :
- Login: guest
- Password: guest
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:
- Par exemple, lancer firefox en ouvrant l'URL http://localhost:PPPP
où PPPP est le numéro de port associé au service
de supervision.
- Et connectez vous au serveur avec un login/password comme celui donné ci-dessus.
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 :
- Indiquez les messages reçus par chaque processus.
- 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 ?
- Quelles commandes contient le script stop.sh ?
- 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 les
fichiers dans un répertoire nommé EXO2.
Compléter les programme Lecteur_Depeche.java et Redacteur_Depeche.java de sorte que :
- Redacteur_Depeche.java
envoit un message deux fois.
Ce programme prends 3 paramètres : 2 noms de ville et 1 message à envoyer.
Chaque nom de ville
constitue une clef.
Ce programme soit envoyer le message deux fois : le message
doit être envoyé avec
chacune des 2 clefs passées en argument.
- 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.
Le programme filtre donc les messages qui ne concerne pas la ville passé en
argument.
- 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 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:
- Le programme Capteur.java produit et émet les
messages vers le programme Filtrage.java.
Le programme Capteur.java est employé pour signaler un événement sur une ligne de bus.
Chaque message émis/reçu décrit un événement intervenu sur une ligne de bus
ainsi que son impact sur la ligne en question.
Chaque message contient 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 Capteur.java et renvoie également différents messages
vers les processus executant Usager.java et Atelier.java.
- Les programmes Usager.java et Atelier.java
permettent de réceptionner les différents messages
envoyés par Filtrage.java.
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 :
- Transfèrer le message sans le modifier vers
les usagers et l'atelier.
- 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:
-
Lorsque le programme Usager.java est utilisé par un usager des
bus, le programme ne réceptionne que les messages associés aux
événements que l'usager souhaitent recevoir.
L'événement constitue une clef pour Usager.java : en d'autres termes,
chaque usager indique pour quels événements il souhaite obtenir les informations de ligne de bus.
Ainsi, le script lance.sh indique que usager1
souhaite recevoir les évenements Travaux
et Accident alors
que usager3 souhaite ne recevoir que les événements Panne.
-
Le programme Atelier.java est utilisé doit recevoir tous les messages
émis par Filtrage.java, et en particulier tous les événements.
Travail à faire :
Modifier
le programme Filtrage.java
et écrire les programmes
Usager/Atelier.java pour implanter la communication entre ces 3 programmes.
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 :
- 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.
-
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.
- 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:
- Le programme divise par 2 l'entier reçu et mémorise le reste (0 ou 1).
- 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.
-
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.
- 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
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 :
- 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.
- 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.
-
Le nom de chaque worker est donné dans le fichier lance.sh: les noms
sont work1, work2 et work3.
-
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.
- 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.
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 :
-
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 EXO6 . Sauvez tous les
fichiers dans un répertoire
EXO6.
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 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