diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/Dockerfile b/kashin_maxim_lab_4/RabbitMQ_demoapp/Dockerfile new file mode 100644 index 0000000..bc045fa --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_demoapp/Dockerfile @@ -0,0 +1,12 @@ +# Используем Python 3.9 как базовый образ +FROM python:3.9-slim + +# Устанавливаем зависимости +RUN pip install pika + +# Копируем текущую директорию в контейнер +WORKDIR /app +COPY . /app + +# Указываем команду для запуска (переопределим её в docker-compose.yml) +CMD ["python", "publisher.py"] diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_1.py b/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_1.py new file mode 100644 index 0000000..4a54667 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_1.py @@ -0,0 +1,20 @@ +import pika +import time + +def callback(ch, method, properties, body): + print(f" [Consumer 1] {body.decode('utf-8')}") + time.sleep(3) + ch.basic_ack(delivery_tag=method.delivery_tag) + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='rabbitmq')) +channel = connection.channel() +channel.exchange_declare(exchange='lunch_logs', exchange_type='fanout') + +queue_name = "lunch_queue_slow" +channel.queue_declare(queue=queue_name) +channel.queue_bind(exchange='lunch_logs', queue=queue_name) + +print(' [*] Consumer 1 waiting for logs. To exit press CTRL+C') + +channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=False) +channel.start_consuming() diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_2.py b/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_2.py new file mode 100644 index 0000000..21750a5 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_2.py @@ -0,0 +1,19 @@ +import pika + +def callback(ch, method, properties, body): + print(f" [Consumer 2] {body.decode('utf-8')}") + ch.basic_ack(delivery_tag=method.delivery_tag) + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='rabbitmq')) +channel = connection.channel() +channel.exchange_declare(exchange='lunch_logs', exchange_type='fanout') + + +queue_name = "lunch_queue_fast" +channel.queue_declare(queue=queue_name) +channel.queue_bind(exchange='lunch_logs', queue=queue_name) + +print(' [*] Consumer 2 waiting for logs. To exit press CTRL+C') + +channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=False) +channel.start_consuming() diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/docker-compose.yml b/kashin_maxim_lab_4/RabbitMQ_demoapp/docker-compose.yml new file mode 100644 index 0000000..7b4930b --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_demoapp/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3' + +services: + rabbitmq: + image: rabbitmq:3-management + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + healthcheck: + test: ["CMD", "rabbitmqctl", "status"] + interval: 10s + timeout: 5s + retries: 5 + + publisher: + build: + context: . + container_name: publisher + environment: + - PYTHONUNBUFFERED=1 + command: python publisher.py + depends_on: + rabbitmq: + condition: service_healthy + + consumer_1: + build: + context: . + container_name: consumer_1 + environment: + - PYTHONUNBUFFERED=1 + command: python consumer_1.py + depends_on: + rabbitmq: + condition: service_healthy + + consumer_2: + build: + context: . + container_name: consumer_2 + environment: + - PYTHONUNBUFFERED=1 + command: python consumer_2.py + depends_on: + rabbitmq: + condition: service_healthy diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/publisher.py b/kashin_maxim_lab_4/RabbitMQ_demoapp/publisher.py new file mode 100644 index 0000000..fa506ca --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_demoapp/publisher.py @@ -0,0 +1,20 @@ +import pika +import time +import random + +connection = pika.BlockingConnection(pika.ConnectionParameters(host='rabbitmq')) +channel = connection.channel() +channel.exchange_declare(exchange='lunch_logs', exchange_type='fanout') + +events = [ + "Новый заказ на завтрак", + "Новый заказ на обед", + "Новый заказ на ужин", + "Пользователь запросил меню" +] + +while True: + message = random.choice(events) + channel.basic_publish(exchange='lunch_logs', routing_key='', body=message) + print(f" [x] Sent {message}") + time.sleep(1) diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/report/fast.png b/kashin_maxim_lab_4/RabbitMQ_demoapp/report/fast.png new file mode 100644 index 0000000..cd5ed79 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_demoapp/report/fast.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_demoapp/report/slow.png b/kashin_maxim_lab_4/RabbitMQ_demoapp/report/slow.png new file mode 100644 index 0000000..e56d1dc Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_demoapp/report/slow.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_1/receive.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/receive.py new file mode 100644 index 0000000..f118e0a --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/receive.py @@ -0,0 +1,25 @@ +import pika, sys, os + +def main(): + connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) + channel = connection.channel() + + channel.queue_declare(queue='hello') + + def callback(ch, method, properties, body): + print(f" [x] Received {body}") + + channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True) + + print(' [*] Waiting for messages. To exit press CTRL+C') + channel.start_consuming() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print('Interrupted') + try: + sys.exit(0) + except SystemExit: + os._exit(0) \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/receive_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/receive_1.png new file mode 100644 index 0000000..b2b707d Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/receive_1.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/send_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/send_1.png new file mode 100644 index 0000000..9b1c609 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/send_1.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_1/send.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/send.py new file mode 100644 index 0000000..41cfff2 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_1/send.py @@ -0,0 +1,11 @@ +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host='localhost')) +channel = connection.channel() + +channel.queue_declare(queue='hello') + +channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') +print(" [x] Sent 'Hello World!'") +connection.close() \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_2/new_task.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/new_task.py new file mode 100644 index 0000000..a2444e0 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/new_task.py @@ -0,0 +1,19 @@ +import pika +import sys + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host='localhost')) +channel = connection.channel() + +channel.queue_declare(queue='task_queue', durable=True) + +message = ' '.join(sys.argv[1:]) or "Hello World!" +channel.basic_publish( + exchange='', + routing_key='task_queue', + body=message, + properties=pika.BasicProperties( + delivery_mode=pika.DeliveryMode.Persistent + )) +print(f" [x] Sent {message}") +connection.close() \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/new_task_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/new_task_1.png new file mode 100644 index 0000000..c6a3b76 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/new_task_1.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_1.png new file mode 100644 index 0000000..d046484 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_1.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_2.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_2.png new file mode 100644 index 0000000..8c3b7b9 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_2.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_2/worker.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/worker.py new file mode 100644 index 0000000..8f8ab64 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_2/worker.py @@ -0,0 +1,22 @@ +import pika +import time + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host='localhost')) +channel = connection.channel() + +channel.queue_declare(queue='task_queue', durable=True) +print(' [*] Waiting for messages. To exit press CTRL+C') + + +def callback(ch, method, properties, body): + print(f" [x] Received {body.decode()}") + time.sleep(body.count(b'.')) + print(" [x] Done") + ch.basic_ack(delivery_tag=method.delivery_tag) + + +channel.basic_qos(prefetch_count=1) +channel.basic_consume(queue='task_queue', on_message_callback=callback) + +channel.start_consuming() \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_3/emit_log.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/emit_log.py new file mode 100644 index 0000000..45b6989 --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/emit_log.py @@ -0,0 +1,13 @@ +import pika +import sys + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host='localhost')) +channel = connection.channel() + +channel.exchange_declare(exchange='logs', exchange_type='fanout') + +message = ' '.join(sys.argv[1:]) or "info: Hello World!" +channel.basic_publish(exchange='logs', routing_key='', body=message) +print(f" [x] Sent {message}") +connection.close() \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_3/receive_logs.py b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/receive_logs.py new file mode 100644 index 0000000..60d881d --- /dev/null +++ b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/receive_logs.py @@ -0,0 +1,22 @@ +import pika + +connection = pika.BlockingConnection( + pika.ConnectionParameters(host='localhost')) +channel = connection.channel() + +channel.exchange_declare(exchange='logs', exchange_type='fanout') + +result = channel.queue_declare(queue='', exclusive=True) +queue_name = result.method.queue + +channel.queue_bind(exchange='logs', queue=queue_name) + +print(' [*] Waiting for logs. To exit press CTRL+C') + +def callback(ch, method, properties, body): + print(f" [x] {body}") + +channel.basic_consume( + queue=queue_name, on_message_callback=callback, auto_ack=True) + +channel.start_consuming() \ No newline at end of file diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_1.png new file mode 100644 index 0000000..4669497 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_1.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_2.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_2.png new file mode 100644 index 0000000..0568b4c Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_2.png differ diff --git a/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/receive_logs_1.png b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/receive_logs_1.png new file mode 100644 index 0000000..eb45ca0 Binary files /dev/null and b/kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/receive_logs_1.png differ diff --git a/kashin_maxim_lab_4/readme.md b/kashin_maxim_lab_4/readme.md new file mode 100644 index 0000000..d829310 --- /dev/null +++ b/kashin_maxim_lab_4/readme.md @@ -0,0 +1,172 @@ +# Кашин Максим ПИбд-42 + +## RabbitMQ tutorial - "Hello world!" +#### Работа файла receive +![receive_1.png](RabbitMQ_tutorial_1/report/receive_1.png) +#### Работа файла send +![send_1.png](RabbitMQ_tutorial_1/report/send_1.png) + +## RabbitMQ tutorial - Work Queues +#### Работа файла new_task +![new_task_1.png](RabbitMQ_tutorial_2/report/new_task_1.png) +#### Работа файла worker +![worker_1.png](RabbitMQ_tutorial_2/report/worker_1.png) +#### Работа файла worker (запущенная копия) +![worker_2.png](RabbitMQ_tutorial_2/report/worker_2.png) + +## RabbitMQ tutorial - Publish/Subscribe +#### Работа файла receive_logs +![receive_logs_1.png](RabbitMQ_tutorial_3/report/receive_logs_1.png) +##### Работа файла emit_log +![emit_log_1.png](RabbitMQ_tutorial_3/report/emit_log_1.png) +##### Работа файла emit_log (запущенная копия) +![emit_log_2.png](RabbitMQ_tutorial_3/report/emit_log_2.png) + +## Самостоятельная работа +### Предметная область +1. Выдача завтрака +2. Выдача обеда +3. Выдача ужина +4. Выдача меню + +### Компоненты + +1. **Издатель** (`publisher.py`): Генерирует случайные сообщения о заказах. +2. **Потребитель 1** (`consumer_1.py`): Обрабатывает сообщения медленно (3 секунды на сообщение). +3. **Потребитель 2** (`consumer_2.py`): Обрабатывает сообщения быстро (мгновенно). +4. **RabbitMQ**: Выступает в роли брокера сообщений. + +### Описание DockerFile + +`Dockerfile` определяет, как будет строиться образ для контейнера, в котором будут запускаться ваши Python-скрипты. Вот основные шаги, которые выполняет `Dockerfile`: + +1. **Базовый образ**: + ```dockerfile + FROM python:3.9-slim + ``` + Используется легковесный образ Python 3.9, который минимизирует размер конечного образа. + +2. **Установка зависимостей**: + ```dockerfile + RUN pip install pika + ``` + Устанавливается библиотека `pika`, необходимая для работы с RabbitMQ. + +3. **Копирование файлов**: + ```dockerfile + WORKDIR /app + COPY . /app + ``` + Устанавливается рабочая директория `/app`, и все файлы из текущей директории копируются в контейнер. + +4. **Команда по умолчанию**: + ```dockerfile + CMD ["python", "publisher.py"] + ``` + Указывается команда, которая будет выполняться при запуске контейнера. + +Таким образом, `Dockerfile` описывает, как создать контейнер с необходимой средой выполнения и зависимостями для приложения. + +## Описание Docker Compose + +`docker-compose.yml` используется для определения и управления многими контейнерами в проекте. В этом файле описаны необходимые сервисы для работы системы обмена сообщениями на RabbitMQ. Основные компоненты: + +1. **RabbitMQ**: + ```yaml + rabbitmq: + image: rabbitmq:3-management + container_name: rabbitmq + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + healthcheck: + test: ["CMD", "rabbitmqctl", "status"] + interval: 10s + timeout: 5s + retries: 5 + ``` + Этот сервис запускает RabbitMQ с интерфейсом управления, доступным по портам 5672 и 15672. + +2. **Publisher**: + ```yaml + publisher: + build: + context: . + container_name: publisher + environment: + - PYTHONUNBUFFERED=1 + command: python publisher.py + depends_on: + rabbitmq: + condition: service_healthy + ``` + Издатель, который запускает `publisher.py` для отправки сообщений. Он зависит от RabbitMQ и запускается только после его готовности. + +3. **Consumer 1**: + ```yaml + consumer_1: + build: + context: . + container_name: consumer_1 + environment: + - PYTHONUNBUFFERED=1 + command: python consumer_1.py + depends_on: + rabbitmq: + condition: service_healthy + ``` + Первый потребитель, обрабатывающий сообщения медленно. Он также зависит от RabbitMQ. + +4. **Consumer 2**: + ```yaml + consumer_2: + build: + context: . + container_name: consumer_2 + environment: + - PYTHONUNBUFFERED=1 + command: python consumer_2.py + depends_on: + rabbitmq: + condition: service_healthy + ``` + Второй потребитель, который обрабатывает сообщения быстро. Он, как и другие сервисы, зависит от RabbitMQ. + +### Запуск проекта + +Чтобы запустить проект, нужна следующую команду в терминале: + +```bash +docker-compose up +``` +### Анализ результатов +##### Работа медленного потребителя +![receive_logs_1.png](RabbitMQ_demoapp/report/slow.png) +##### Работа быстрого потребителя +![emit_log_1.png](RabbitMQ_demoapp/report/fast.png) + +### Анализ очередей RabbitMQ + +На представленных скриншотах RabbitMQ отображается состояние двух очередей: `lunch_queue_fast` и `lunch_queue_slow`. Рассмотрим, что можно сказать по каждому из них. + +### Анализ очереди `lunch_queue_fast` + +- **Сообщения в очереди**: Очередь пуста, сообщений в обработке нет. Графики не показывают значительных изменений, и все метрики по сообщениям равны нулю. +- **Скорость обработки**: Сообщения публикуются со скоростью 1 сообщение в секунду, и одно сообщение в секунду подтверждается клиентом (Consumer ack). +- **Потребители**: В этой очереди подключён один потребитель, который обрабатывает сообщения с максимальной скоростью публикации. + +### Анализ очереди `lunch_queue_slow` + +- **Сообщения в очереди**: В этой очереди находятся необработанные сообщения. В данный момент 28 сообщений «зависли» в статусе **Unacked** (неподтвержденные). +- **Скорость обработки**: Сообщения публикуются со скоростью 1 сообщение в секунду, однако подтверждение клиентом идёт со скоростью 0.4 сообщения в секунду. Это приводит к накоплению сообщений в очереди, так как потребитель не успевает их обрабатывать. +- **Потребители**: Как и в `lunch_queue_fast`, здесь подключён один потребитель, но его производительность значительно ниже, что и приводит к накоплению сообщений. + +### Основные выводы + +- **Разница в скорости обработки**: Очевидно, что `lunch_queue_slow` работает медленнее, и её потребитель не успевает обрабатывать поступающие сообщения. + +## Часть 3: Ссылка на видео +[Видео-отчёт Кашин Максим ПИбд-42](https://disk.yandex.ru/i/IcVxUh4C1rnQAw) \ No newline at end of file diff --git a/kashin_maxim_lab_4/requirements.txt b/kashin_maxim_lab_4/requirements.txt new file mode 100644 index 0000000..73da9f7 --- /dev/null +++ b/kashin_maxim_lab_4/requirements.txt @@ -0,0 +1 @@ +pika \ No newline at end of file