Laboratorul 07. Arhitecturi pe bază de mesaje. Chat interactiv

Obiective:

Î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.

Partea 1: Breviar teoretic:

Introducere în RabbitMQ

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.

Conținut

1. Caracteristici principale RabbitMQ
  • Asincron și fiabil: RabbitMQ permite transmiterea de mesaje între componente, eliminând nevoia procesării imediate. Mesajele sunt stocate în queue-uri, iar consumatorii le pot prelua și procesa în ritmul lor.
  • Queue-uri persistente: Mesajele pot fi salvate în queue-uri persistente, garantând livrarea chiar și în caz de restart sau întreruperi.
  • Scalabilitate: RabbitMQ poate fi scalat orizontal prin configurarea sa într-un cluster distribuit, ceea ce permite gestionarea unor volume mari de mesaje și distribuirea sarcinii între mai multe servere.
2. Componentele RabbitMQ
  1. Producer (Producător): Trimite mesaje către RabbitMQ.
  2. Exchange: Primește mesajele de la producători și decide cum să le ruteze către queue-uri pe baza regulilor de rutare.
  3. Queue (Coadă): Stochează mesajele până când acestea sunt preluate de consumatori.
  4. Consumer (Consumator): Preia mesajele din queue și le procesează.

3. Tipuri de Exchange
  • Direct Exchange: Rutează mesajele către un queue specific folosind un routing key exact.
  • Fanout Exchange: Trimite toate mesajele către toate queue-urile asociate, ignorând routing key.
  • Topic Exchange: Rutează mesajele pe baza unui pattern de routing key, permițând un nivel ridicat de flexibilitate. https://www.rabbitmq.com/tutorials/tutorial-five-python
  • Headers Exchange: Utilizează headerele mesajelor pentru rutarea acestora către queue-uri, fiind potrivit pentru rutarea pe criterii complexe.
4. Cum funcționează RabbitMQ
  1. Producerea mesajelor: Producătorii creează mesaje și le trimit către un exchange.
  2. Rutarea mesajelor: Exchange-ul distribuie mesajele către queue-uri în funcție de regulile de rutare configurate.
  3. Stocarea mesajelor: Queue-urile stochează mesajele temporar până când sunt preluate de consumatori.
  4. Consumarea mesajelor: Consumatorii se conectează la queue-uri și preiau mesajele pentru a le procesa.

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.

Instalare 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

Pentru o introducere în Docker, consultați: Quickstart | Docker Docs

Interfață grafică

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.

RabbitMQ Tutorial: Connecting with Python

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.

Pași pentru conectarea la RabbitMQ folosind Python
1. Instalarea Bibliotecii pika

Asigură-te că ai instalat biblioteca pika. Poți face acest lucru rulând următoarea comandă:

pip install pika
2. Configurarea unei Conexiuni la RabbitMQ

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:

  • Creează o conexiune la serverul RabbitMQ de pe localhost.
  • Creează un 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 prin ConnectionParameters.
3. Crearea unui Queue

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.

4. Trimiterea unui Mesaj în Queue

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.
5. Primirea unui Mesaj din Queue

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.
6. Închiderea Conexiunii

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ă.")
Extra

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.

Exemplu de Utilizare a unui Exchange ''fanout'' în RabbitMQ

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:

  1. Crea un exchange de tip fanout numit broadcast_exchange.
  2. Crea două queue-uri (queue1 și queue2).
  3. Conecta ambele queue-uri la exchange fără a folosi routing key (nefiind necesară pentru exchange-ul fanout).
  4. Trimite un mesaj către exchange, care va fi livrat tuturor queue-urilor conectate.
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()
Rezultatul Așteptat

- Mesajul „Salut tuturor consumatorilor!” va ajunge atât în queue1, cât și în queue2.

Utilizare

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.

Partea 2 - Exerciții:

Descărcați proiectul de pe GitHub: Messenger

Pentru a instala toate dependențele necesare pentru laboratorul de astăzi, rulați: pip install -r requirements.txt. Fișierul requirements.txt poate fi descărcat de aici: requirements.txt.

0. Rulați local o instanță de RabbitMQ, fie folosind docker-compose-ul pus la dispoziție mai sus, fie instalând RabbitMQ direct pe mașina voastră. Atenție, dacă optați pentru instalarea directă pe mașina voastră, va fi necesar să instalați separat plugin-ul care oferă acces la interfața web - Plugin.

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:

  • TODO a) Trimite la fiecare 30 de secunde un mesaj de tip broadcast pe exchange-ul 'chat_online_user_exchange' și fără nici un routing_key, prin care să informezi ceilalți participanți la chat că ești online și disponibil pentru conversație. Asigură-te că acest mesaj are un timp de viață de maximum 2 minute în queue.
  • TODO b) Consumă mesajele care sosesc pe queue-ul tău. Adaugă un `consumer_tag` cu numele queue-ului, astfel încât consumatorul să nu mai asculte la acest queue atunci când utilizatorul se deconectează (logout).
  • TODO c) Implementează trimiterea unui mesaj către un anumit utilizator pentru a putea iniția o conversație privată cu acesta.

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:

  • Testează funcționalitatea pentru a verifica dacă poți comunica cu un alt utilizator (folosind două instanțe locale de aplicație).
  • Schimbă conexiunea la RabbitMQ cu următoarea și testează dacă poți conversa cu alți colegi:
Host: acc-atomicsoftware.swedencentral.cloudapp.azure.com
User: student
Parola: întreabă coordonatorul de laborator

În cadrul acestui exercițiu, nu vei declara queue-uri sau exchange-uri, deoarece acestea sunt deja create pe instanța din cloud.

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.

În situația în care întâmpinați o eroare similară cu “connection lost” sau “unable to pop from an empty queue” atunci când începeți să consumați mesaje, este indicat să adăugați o metodă nouă pentru a crea un channel și să utilizați un channel nou creat atunci când începeți să ascultați.

  def getChannel(self):
    connection = pika.BlockingConnection(self.connection_params)
    return connection.channel()

Diagrame explicative ale exercițiului 3:

Click to display ⇲

Click to hide ⇱

isi/laboratoare/07.txt · Last modified: 2024/11/21 11:04 by sorin.ciolofan
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0