În cadrul acestui laborator, veți parcurge mai întâi un breviar teoretic pentru a înțelege cum funcționează RabbitMQ, care sunt principalele concepte ale acestei tehnologii, cum să rulați o instanță de RabbitMQ local și cum să o utilizați cu Python. În această primă parte nu este necesar să rulați nimic; materialul oferit are rol de suport pentru partea a doua.
Partea a doua constă într-o serie de exerciții care vor aplica tot ceea ce s-a explicat în breviarul teoretic și vă vor ajuta să aprofundați cunoștințele prin exemple practice.
RabbitMQ este un broker de mesaje open-source bazat pe protocolul AMQP (Advanced Message Queuing Protocol) utilizat pentru a permite aplicațiilor să comunice eficient și asincron prin trimiterea de mesaje între ele. Acesta este folosit pentru a decupla componentele aplicațiilor, facilitând astfel comunicarea, scalabilitatea și reziliența în arhitecturi distribuite și microservicii.
routing key
exact.routing key
.routing key
, permițând un nivel ridicat de flexibilitate. https://www.rabbitmq.com/tutorials/tutorial-five-python
Pentru a conecta o aplicație Python la RabbitMQ, vei folosi biblioteca pika
, care este un client AMQP popular pentru Python. Aceasta permite crearea conexiunilor, configurarea exchange-urilor și queue-urilor, și trimiterea sau recepționarea mesajelor prin RabbitMQ.
Pentru a putea folosi o instanță de RabbitMQ, aveți două posibilități: să o instalați pe calculatorul personal (Installing RabbitMQ | RabbitMQ) sau să rulați următoarea comandă Docker Compose: docker compose up
folosind următorul fișier docker-compose.yamml
:
version: '3.8' services: rabbitmq: image: rabbitmq:3-management container_name: rabbitmq environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest ports: - "5672:5672" # Port for AMQP protocol - "15672:15672" # Port for RabbitMQ management UI volumes: - rabbitmq_data:/var/lib/rabbitmq networks: - rabbitmq_network volumes: rabbitmq_data: networks: rabbitmq_network: driver: bridge
Comandă rulare pentru docker-compose.yaml prezentat anterior:
docker-compose up -d
RabbitMQ oferă și o interfață grafică de management (Management UI) pentru o monitorizare și administrare mai ușoară a mesajelor, queue-urilor și exchange-urilor, fiind accesibilă în mod implicit pe portul 15672 (http://localhost:15672). User-ul și parola implicite sunt guest/guest, însă aceste credențiale funcționează doar pentru accesul de pe localhost; pentru accesul extern este necesară configurarea unor utilizatori noi.
Pentru a conecta o aplicație Python la RabbitMQ, vei folosi biblioteca pika, un client AMQP popular pentru Python. Aceasta permite crearea conexiunilor, configurarea exchange-urilor și queue-urilor, și trimiterea sau recepționarea mesajelor prin RabbitMQ.
Asigură-te că ai instalat biblioteca pika
. Poți face acest lucru rulând următoarea comandă:
pip install pika
Pentru a te conecta la RabbitMQ, va trebui să definești parametrii de conectare. În mod implicit, RabbitMQ rulează local pe portul 5672
, iar contul implicit este guest
cu parola guest
.
import pika # Configurarea conexiunii la RabbitMQ credentials = pika.PlainCredentials("guest", "guest") connection_params = pika.ConnectionParameters(host='localhost', port=5672, credentials=credentials) connection = pika.BlockingConnection(connection_params) channel = connection.channel() print("Conexiunea la RabbitMQ a fost stabilită.")
Acest cod:
localhost
.channel
, care este un canal de comunicare utilizat pentru a interacționa cu RabbitMQ.Notă: Dacă RabbitMQ rulează pe un alt server sau port, schimbălocalhost
cu adresa IP și/sau specifică portul prinConnectionParameters
.
Pentru a trimite și primi mesaje, ai nevoie de un queue (coadă) în care să fie stocate mesajele. Poți crea un queue folosind queue_declare
:
queue_name = 'my_queue' channel.queue_declare(queue=queue_name) print(f"Queue-ul '{queue_name}' a fost creat.")
Acest cod va crea un queue numit my_queue
(sau se va conecta la el dacă deja există). Queue-ul este o structură FIFO (First In, First Out) care stochează mesajele până când sunt preluate de consumatori.
Pentru a trimite un mesaj, vei folosi metoda basic_publish
:
message = "Salut din RabbitMQ!" channel.basic_publish(exchange="", routing_key=queue_name, body=message) print(f"Mesaj trimis: {message}")
exchange=””
specifică că mesajul este trimis direct către queue-ul specificat de routing_key
.routing_key=queue_name
indică queue-ul în care trebuie să ajungă mesajul.body=message
este conținutul mesajului trimis.Pentru a primi mesaje, definești un consumator. Acesta va utiliza un callback pentru a procesa fiecare mesaj primit:
def callback(ch, method, properties, body): print(f"Mesaj primit: {body.decode()}") channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True) print("Aștept mesaje...") channel.start_consuming()
callback
este o funcție care primește și afișează mesajele.basic_consume
configurează consumatorul să asculte queue-ul specificat (queue_name
) și să apeleze callback
pentru fiecare mesaj care este primit (“să consume mesjul”).auto_ack=True
confirmă automat mesajele ca fiind procesate imediat ce sunt primite. După ce sunt procesate, acestea vor fi șterse de pe queue.După ce ai terminat de trimis și primit mesaje, închide conexiunea la RabbitMQ pentru a elibera resursele:
connection.close() print("Conexiunea la RabbitMQ a fost închisă.")
Pentru a vedea un exemplu complet despre cum două aplicații, un sender
și un receiver
, comunică folosind RabbitMQ, precum și pentru a citi explicații mai detaliate, vizitați acest tutorial: Hello World.
În cadrul acestuia veți găsi exemplificat întregul proces de conectare la RabbitMQ: crearea unei cozi (queue), trimiterea și recepția mesajelor, precum și închiderea conexiunii.
Până acum am văzut cum putem crea o coadă (queue), să trimitem un mesaj pe aceasta și să consumăm acel mesaj. În cadrul exemplului anterior, am presupus că un mesaj este trimis către o singură coadă. În ceea ce urmează, vom face ceva complet diferit – vom livra un mesaj către mai mulți consumatori (îl vom publica pe mai multe queue-uri). Acest pattern este cunoscut sub denumirea de „publish/subscribe”.
În acest exemplu, vom:
fanout
numit broadcast_exchange
.queue1
și queue2
).routing key
(nefiind necesară pentru exchange-ul fanout
).import pika # Conectarea la RabbitMQ connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 1. Crearea unui exchange de tip 'fanout' exchange_name = 'broadcast_exchange' channel.exchange_declare(exchange=exchange_name, exchange_type='fanout') print(f"Exchange-ul '{exchange_name}' de tip 'fanout' a fost creat.") # 2. Crearea queue-urilor queue1 = 'queue1' queue2 = 'queue2' channel.queue_declare(queue=queue1) channel.queue_declare(queue=queue2) # 3. Legarea queue-urilor la exchange fără routing key (nu este necesară pentru fanout) channel.queue_bind(exchange=exchange_name, queue=queue1) channel.queue_bind(exchange=exchange_name, queue=queue2) print(f"Queue-urile '{queue1}' și '{queue2}' au fost conectate la exchange-ul '{exchange_name}'.") # 4. Trimiterea unui mesaj către exchange-ul 'fanout' message = "Salut tuturor consumatorilor!" channel.basic_publish(exchange=exchange_name, routing_key='', body=message) print(f"Mesaj trimis către exchange-ul '{exchange_name}': {message}") # Închiderea conexiunii connection.close()
- Mesajul „Salut tuturor consumatorilor!” va ajunge atât în queue1
, cât și în queue2
.
Exchange-urile fanout
sunt utile pentru aplicații de tip difuzare (broadcast), unde același mesaj trebuie să fie recepționat de toate queue-urile conectate. Acest tip de exchange este ideal pentru scenarii precum notificările de sistem, unde toate instanțele trebuie să fie informate simultan.
pip install -r requirements.txt
. Fișierul requirements.txt
poate fi descărcat de aici: requirements.txt.
1. Creează un script Python care să permită citirea textelor de la tastatură și publicarea acestora într-un exchange nou. (care este creat programatic după realizarea cu succes a conexiunii la RabbitMQ). Scriptul va citi textul de la utilizator și va publica fiecare mesaj în exchange-ul RabbitMQ (in mod fanout
).
try: while True: message = input("Message: ") channel.basic_publish(exchange=exchange_name, routing_key='', body=message) finally: connection.close()
2. Creează un script Python care să creeze un queue, să-l lege la exchange-ul din exercițiul 1 și să afișeze fiecare mesaj primit. Scriptul va crea un queue nou și îl va asocia (binding) cu exchange-ul creat în exercițiul anterior. Pentru fiecare mesaj primit în queue, scriptul va afișa conținutul acestuia în consolă.
channel.queue_bind(exchange=exchange_name, queue=queue_name)
3. Chat interactiv
Această aplicație presupune un server Flask care interacționează cu RabbitMQ și expune o interfață web pentru utilizator.
Implementare: Pornind de la codul pentru această aplicație de mesagerie, implementează funcționalitățile necesare, marcate cu TODO-uri in fisierul message_service.py
, pentru ca aceasta să funcționeze complet:
Folosește instanța locală și rulează scriptul initialize.py
din laborator, care configurează 3 utilizatori: stefan, alex și maria.
Testare: Pentru a testa aplicația, puteți rula două instanțe ale aplicației pe porturi diferite, pentru a simula doi utilizatori care folosesc aplicația pe dispozitive diferite. În cadrul testării, punctul a) poate fi verificat individual, iar punctele b) și c) vor fi testate împreună.
Pentru a rula aplicația, folosește comanda: python app.py
sau flask run
.
Aceasta va rula implicit pe portul 5000, iar pentru a vizualiza interfața pusă la dispoziție de aceasta, deschideți http://localhost:5000.
Pentru a rula aplicația pe un alt port decât cel implicit, rulați flask run --port 5001
.
Testare:
Host: acc-atomicsoftware.swedencentral.cloudapp.azure.com User: student Parola: întreabă coordonatorul de laborator
Important: Când te conectezi la instanța din cloud, utilizatorii vor avea formatul nume_prenume_grupa
. Dacă nu ești sigur care este utilizatorul pregătit pentru tine, conectează-te la:
http://acc-atomicsoftware.swedencentral.cloudapp.azure.com:15672
cu utilizatorul si parola de mai sus și caută în tab-ul Queues and Streams după numele tău. Queue-ul o să se numească chatUser.username, iar tu trebuie să folosești doar username pentru a te conecta. În situația în care totuși nu te regăsești, poți folosi unul dintre utilizatorii următori: bob, alice, stefan, alex.
def getChannel(self): connection = pika.BlockingConnection(self.connection_params) return connection.channel()
Diagrame explicative ale exercițiului 3: