Merge pull request 'kashin_maxim_lab_4' (#123) from kashin_maxim_lab_4 into main
Reviewed-on: #123
12
kashin_maxim_lab_4/RabbitMQ_demoapp/Dockerfile
Normal file
@ -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"]
|
20
kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_1.py
Normal file
@ -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()
|
19
kashin_maxim_lab_4/RabbitMQ_demoapp/consumer_2.py
Normal file
@ -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()
|
50
kashin_maxim_lab_4/RabbitMQ_demoapp/docker-compose.yml
Normal file
@ -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
|
20
kashin_maxim_lab_4/RabbitMQ_demoapp/publisher.py
Normal file
@ -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)
|
BIN
kashin_maxim_lab_4/RabbitMQ_demoapp/report/fast.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_demoapp/report/slow.png
Normal file
After Width: | Height: | Size: 39 KiB |
25
kashin_maxim_lab_4/RabbitMQ_tutorial_1/receive.py
Normal file
@ -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)
|
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/receive_1.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_1/report/send_1.png
Normal file
After Width: | Height: | Size: 44 KiB |
11
kashin_maxim_lab_4/RabbitMQ_tutorial_1/send.py
Normal file
@ -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()
|
19
kashin_maxim_lab_4/RabbitMQ_tutorial_2/new_task.py
Normal file
@ -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()
|
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/new_task_1.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_1.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_2/report/worker_2.png
Normal file
After Width: | Height: | Size: 42 KiB |
22
kashin_maxim_lab_4/RabbitMQ_tutorial_2/worker.py
Normal file
@ -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()
|
13
kashin_maxim_lab_4/RabbitMQ_tutorial_3/emit_log.py
Normal file
@ -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()
|
22
kashin_maxim_lab_4/RabbitMQ_tutorial_3/receive_logs.py
Normal file
@ -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()
|
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_1.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/emit_log_2.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
kashin_maxim_lab_4/RabbitMQ_tutorial_3/report/receive_logs_1.png
Normal file
After Width: | Height: | Size: 61 KiB |
172
kashin_maxim_lab_4/readme.md
Normal file
@ -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)
|
1
kashin_maxim_lab_4/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
pika
|