Merge branch 'main' into dozorova_alena_lab_6
2
bogdanov_dmitry_lab_2/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
data/
|
||||
result/
|
41
bogdanov_dmitry_lab_2/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Лабораторная работа №2
|
||||
|
||||
## Богданов Дмитрий ПИбд-42
|
||||
|
||||
### Для выполнения была проделана следующая работа:
|
||||
Были написаны и развернуты 3 сервиса: генератор файлов, 2 приложения для работы с этими файлами по вариантам 2 и 1 соответственно:
|
||||
|
||||
Вариант 2 (для первого приложения):
|
||||
|
||||
```Формирует файл /var/result/data.txt из первых строк всех файлов каталога /var/data.```
|
||||
|
||||
Вариант 1 (для второго приложения):
|
||||
|
||||
```Ищет набольшее число из файла /var/data/data.txt и сохраняет его вторую степень в /var/result/result.txt.```
|
||||
|
||||
Приложения работают совместно, используя общий монтированный том для записи и получения информации:
|
||||
```
|
||||
volumes:
|
||||
- ./data:/var/data
|
||||
```
|
||||
```
|
||||
volumes:
|
||||
- ./data:/var/data
|
||||
- ./result:/var/result
|
||||
```
|
||||
```
|
||||
volumes:
|
||||
- ./result:/var/result
|
||||
```
|
||||
|
||||
|
||||
### Запуск лабораторной:
|
||||
Необходимо перейти в папку с файлом docker-compose.yaml и ввести следующую команду:
|
||||
```
|
||||
docker compose up --build
|
||||
```
|
||||
Сервис генератора сгенерирует папки data и result, где будут сгенерированы входные файлы и файл-результат их обработки соответственно.
|
||||
|
||||
## Видео с результатом запуска:
|
||||
|
||||
Видео-демонстрацию работы можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1CmVZjJuMStqNFFKbsMLjw4ihTiMnR7it/view).
|
7
bogdanov_dmitry_lab_2/app-1/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY app.py /app/
|
||||
|
||||
CMD ["python", "app.py"]
|
30
bogdanov_dmitry_lab_2/app-1/app.py
Normal file
@ -0,0 +1,30 @@
|
||||
import os
|
||||
|
||||
# Вариант 2 - Формирует файл /var/result/data.txt из первых строк всех файлов каталога /var/data.
|
||||
def solve(dir_files, dir_result, filename_result):
|
||||
# Получаем список файлов в директории
|
||||
filenames = os.listdir(dir_files)
|
||||
result = ''
|
||||
# Проходим через каждый файл
|
||||
for filename in filenames:
|
||||
filepath = os.path.join(dir_files, filename)
|
||||
file = open(filepath, "r")
|
||||
# Читаем первую строку, добавляем к результату
|
||||
result += f"{file.readline()}"
|
||||
file.close()
|
||||
|
||||
# Если директории для сохранения результата нет - создаём
|
||||
if not os.path.exists(dir_result):
|
||||
os.makedirs(dir_result)
|
||||
# Если директория с результатом не пустая - завершаем работу
|
||||
if os.listdir(dir_result):
|
||||
return
|
||||
# Пишем результат в файл
|
||||
filepath_result = os.path.join(dir_result, filename_result)
|
||||
result_file = open(filepath_result, "w")
|
||||
result_file.write(result)
|
||||
print(f"Результат записан в файл {filepath_result}")
|
||||
result_file.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
solve('/var/data', '/var/result', 'data.txt')
|
7
bogdanov_dmitry_lab_2/app-2/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY app.py /app/
|
||||
|
||||
CMD ["python", "app.py"]
|
21
bogdanov_dmitry_lab_2/app-2/app.py
Normal file
@ -0,0 +1,21 @@
|
||||
import os
|
||||
|
||||
# Вариант 1 - Ищет набольшее число из файла /var/data/data.txt и сохраняет его вторую степень в /var/result/result.txt.
|
||||
def solve(dir_input, dir_result, filename_result):
|
||||
file_input = open(os.path.join(dir_input, 'data.txt'))
|
||||
# Считываем все числа из файла
|
||||
inputs = [int(line) for line in file_input.readlines()]
|
||||
if inputs:
|
||||
# Максимальное число
|
||||
max_num = max(inputs)
|
||||
print(f"Наибольшее число: {max_num}")
|
||||
# Возводим во 2 степень
|
||||
result = max(inputs) ** 2
|
||||
file_result = open(os.path.join(dir_result, filename_result), "w")
|
||||
# Пишем результат в файл
|
||||
file_result.write(str(result))
|
||||
print(f"Получен результат {result}")
|
||||
file_result.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
solve("/var/result", '/var/result', 'result.txt')
|
7
bogdanov_dmitry_lab_2/app-generator/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY generator.py /app/
|
||||
|
||||
CMD ["python", "generate_files.py"]
|
30
bogdanov_dmitry_lab_2/app-generator/generator.py
Normal file
@ -0,0 +1,30 @@
|
||||
import os
|
||||
import random as rnd
|
||||
import string
|
||||
|
||||
# Генератор названий файлов
|
||||
def generate_filename(l):
|
||||
return ''.join(rnd.choices(string.ascii_lowercase + string.digits, k=l)) + '.txt'
|
||||
|
||||
def generate_files(dir, num_files, num_lines):
|
||||
# Если директории для сохранения файлов нет - создаём
|
||||
if not os.path.exists(dir):
|
||||
os.makedirs(dir)
|
||||
|
||||
# Если директория для сохранения файлов не пустая - завершаем работу
|
||||
if os.listdir(dir):
|
||||
return
|
||||
|
||||
# Создание файлов
|
||||
for i in range(num_files):
|
||||
filename = generate_filename(20)
|
||||
filepath = os.path.join(dir, filename)
|
||||
|
||||
file = open(filepath, "w")
|
||||
# Запись строк в файл
|
||||
for j in range(num_lines):
|
||||
file.write(f"{rnd.randint(-1000, 1000)}\n")
|
||||
file.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_files('/var/data', 50, 50)
|
27
bogdanov_dmitry_lab_2/docker-compose.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
services:
|
||||
# Генератор файлов
|
||||
generator:
|
||||
build:
|
||||
context: ./app-generator # Путь к контексту (докер файл + скрипт)
|
||||
volumes:
|
||||
- ./data:/var/data # Папка контейнера : папка локальная
|
||||
entrypoint: python generator.py # Точка входа
|
||||
|
||||
# Первое приложение
|
||||
app1:
|
||||
build:
|
||||
context: ./app-1 # Путь к контексту
|
||||
volumes:
|
||||
- ./data:/var/data # Монтирование папок
|
||||
- ./result:/var/result
|
||||
depends_on:
|
||||
- generator # Указываем, что запускается только после успешной работы сервиса generator
|
||||
|
||||
# Второе приложение, настройка аналогична сервисам выше
|
||||
app2:
|
||||
build:
|
||||
context: ./app-2
|
||||
volumes:
|
||||
- ./result:/var/result
|
||||
depends_on:
|
||||
- app1
|
2
bogdanov_dmitry_lab_3/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/.idea
|
||||
/.venv
|
22
bogdanov_dmitry_lab_3/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Лабораторная работа №3
|
||||
|
||||
## Богданов Дмитрий ПИбд-42
|
||||
|
||||
### Для выполнения были выбраны следующие сущности:
|
||||
|
||||
* Message - содержит uuid (генерируется), text, datetime_sent, user_id
|
||||
* User - содержит uuid (генерируется), name, surname
|
||||
|
||||
Одному пользователю может быть присвоено несколько сообщений.
|
||||
|
||||
Соответственно были развернуты 2 сервиса для управления этими сущностями.
|
||||
|
||||
### Запуск лабораторной:
|
||||
Необходимо перейти в папку с файлом compose.yaml и ввести следующую команду:
|
||||
```
|
||||
docker-compose up --build -d
|
||||
```
|
||||
|
||||
## Видео с результатом запуска и тестами...
|
||||
|
||||
...можно посмотреть по данной [ссылке](https://drive.google.com/file/d/1cJz0z4KduSz1oltmAuieUW7GxxVLNPNo/view).
|
27
bogdanov_dmitry_lab_3/compose.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
services:
|
||||
|
||||
user_service:
|
||||
container_name: userService
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./userService/Dockerfile
|
||||
expose:
|
||||
- 20001
|
||||
|
||||
message_service:
|
||||
container_name: messageService
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./messageService/Dockerfile
|
||||
expose:
|
||||
- 20002
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
depends_on:
|
||||
- user_service
|
||||
- message_service
|
11
bogdanov_dmitry_lab_3/messageService/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY messageService/messageService.py .
|
||||
|
||||
CMD ["python", "messageService.py"]
|
138
bogdanov_dmitry_lab_3/messageService/messageService.py
Normal file
@ -0,0 +1,138 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from uuid import uuid4
|
||||
import uuid
|
||||
import datetime
|
||||
import requests
|
||||
|
||||
class Message:
|
||||
def __init__(self, text: str, datetime_sent: datetime, uuid_: uuid, user_id: uuid):
|
||||
if uuid_ is None:
|
||||
self.uuid_ = uuid4()
|
||||
else:
|
||||
self.uuid_ = uuid.UUID(uuid_)
|
||||
self.text = text
|
||||
self.datetime_sent = datetime_sent
|
||||
self.user_id = uuid.UUID(user_id)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'text': self.text,
|
||||
'datetime_sent': self.datetime_sent,
|
||||
'user_id': self.user_id,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
def to_dict_for_users(self):
|
||||
return {
|
||||
'title': self.text,
|
||||
'datetime_sent': self.datetime_sent,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
def to_dict_with_info(self, user: dict):
|
||||
return {
|
||||
'title': self.text,
|
||||
'datetime_sent': self.datetime_sent,
|
||||
'user_id': self.user_id,
|
||||
'user_info': user,
|
||||
'uuid': self.uuid_
|
||||
}
|
||||
|
||||
messages = [
|
||||
Message(text='Hi!', datetime_sent=datetime.datetime.now(), uuid_='4add0525-1857-477d-ad35-56790d400b72', user_id='94b171ea-39f6-4a67-9c67-061743f67cfd'),
|
||||
Message(text='Hello this is a message', datetime_sent=datetime.datetime.now(), uuid_='dd69758d-89e8-49b5-86bf-54ae2adb64e8', user_id='724a3192-70dd-4909-9b0f-c9060a4ab1bd'),
|
||||
Message(text='Test', datetime_sent=datetime.datetime.now(), uuid_='92389e8d-4365-457e-b37e-78abbc07f194', user_id='94b171ea-39f6-4a67-9c67-061743f67cfd'),
|
||||
Message(text='Anyone here?', datetime_sent=datetime.datetime.now(), uuid_='f3a1c526-aca2-47e2-afd3-a1c2eac92458', user_id='724a3192-70dd-4909-9b0f-c9060a4ab1bd'),
|
||||
Message(text='Mambo', datetime_sent=datetime.datetime.now(), uuid_='00abbdb5-e480-4842-bc32-f916894757eb', user_id='46672ea5-3d7b-4137-a0ac-efd898ca4db6')
|
||||
]
|
||||
|
||||
def list_jsonify():
|
||||
return jsonify([message.to_dict() for message in messages])
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
users_url = 'http://userService:20001/'
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_all():
|
||||
return list_jsonify(), 200
|
||||
|
||||
@app.route('/info', methods=['GET'])
|
||||
def get_all_full():
|
||||
users: list[dict] = requests.get(users_url).json()
|
||||
response = []
|
||||
for message in messages:
|
||||
for user in users:
|
||||
if message.user_id == uuid.UUID(user.get('uuid')):
|
||||
response.append(message.to_dict_with_info(user))
|
||||
|
||||
return response, 200
|
||||
|
||||
@app.route('/by-user/<uuid:user_uuid>', methods=['GET'])
|
||||
def get_by_user_id(user_uuid):
|
||||
return [message.to_dict_for_users() for message in messages if message.user_id == user_uuid], 200
|
||||
|
||||
@app.route('/info/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one_full(uuid_):
|
||||
for message in messages:
|
||||
if message.uuid_ == uuid_:
|
||||
response = requests.get(users_url + str(message.user_id))
|
||||
return message.to_dict_with_info(response.json()), 200
|
||||
|
||||
return f'Сообщение с uuid {uuid_} не найдено', 404
|
||||
|
||||
@app.route('/', methods=['POST'])
|
||||
def create():
|
||||
data = request.json
|
||||
text = data.get('text', None)
|
||||
datetime_sent = datetime.datetime.now()
|
||||
user_id = data.get('user_id', None)
|
||||
checking = requests.get(users_url + f'/check/{user_id}')
|
||||
print(checking)
|
||||
if checking.status_code == 200:
|
||||
new_message = Message(text, datetime_sent, None, user_id)
|
||||
messages.append(new_message)
|
||||
return get_one(new_message.uuid_)
|
||||
if checking.status_code == 404:
|
||||
return f'Пользователь с uuid {user_id} не существует', 404
|
||||
|
||||
return 'Неизвестная ошибка', 500
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['PUT'])
|
||||
def update_by_id(uuid_):
|
||||
data = request.json
|
||||
new_text = data.get('text', None)
|
||||
|
||||
for message in messages:
|
||||
print(message.uuid_)
|
||||
|
||||
if message.uuid_ == uuid_:
|
||||
if new_text is not None:
|
||||
message.text = new_text
|
||||
return get_one(message.uuid_)
|
||||
|
||||
return f'Сообщение с uuid {uuid_} не найдено', 404
|
||||
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['DELETE'])
|
||||
def delete(uuid_):
|
||||
for message in messages:
|
||||
if message.uuid_ == uuid_:
|
||||
messages.remove(message)
|
||||
return 'Сообщение успешно удалено', 200
|
||||
|
||||
return f'Сообщение с uuid {uuid_} не найдено', 404
|
||||
|
||||
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one(uuid_):
|
||||
for message in messages:
|
||||
if message.uuid_ == uuid_:
|
||||
return message.to_dict(), 200
|
||||
|
||||
return f'Сообщение с uuid {uuid_} не найдено', 404
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=20002, debug=True)
|
25
bogdanov_dmitry_lab_3/nginx.conf
Normal file
@ -0,0 +1,25 @@
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location /userService/ {
|
||||
proxy_pass http://userService:20001/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /messageService/ {
|
||||
proxy_pass http://messageService:20002/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
2
bogdanov_dmitry_lab_3/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask==3.0.3
|
||||
requests==2.32.3
|
11
bogdanov_dmitry_lab_3/userService/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY userService/userService.py .
|
||||
|
||||
CMD ["python", "userService.py"]
|
115
bogdanov_dmitry_lab_3/userService/userService.py
Normal file
@ -0,0 +1,115 @@
|
||||
from flask import Flask, jsonify, request
|
||||
from uuid import uuid4
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self, name, surname, uuid_: uuid):
|
||||
if uuid_ is None:
|
||||
self.uuid_: uuid = uuid4()
|
||||
else:
|
||||
self.uuid_: uuid = uuid.UUID(uuid_)
|
||||
self.name: str = name
|
||||
self.surname: str = surname
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"uuid": self.uuid_,
|
||||
"name": self.name,
|
||||
"surname": self.surname
|
||||
}
|
||||
|
||||
def to_dict_with_messages(self, messages: list):
|
||||
return {
|
||||
"uuid": self.uuid_,
|
||||
"name": self.name,
|
||||
"surname": self.surname,
|
||||
"messages": messages
|
||||
}
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
users: list[User] = [
|
||||
User(name='Dr.', surname='Kino', uuid_='94b171ea-39f6-4a67-9c67-061743f67cfd'),
|
||||
User(name='Caspian', surname='Holstrom', uuid_='724a3192-70dd-4909-9b0f-c9060a4ab1bd'),
|
||||
User(name='Admin', surname='Admin', uuid_='46672ea5-3d7b-4137-a0ac-efd898ca4db6')
|
||||
]
|
||||
|
||||
messages_url = 'http://messageService:20002/'
|
||||
|
||||
|
||||
def list_jsonify():
|
||||
return jsonify([user.to_dict() for user in users])
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_all():
|
||||
return list_jsonify(), 200
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one(uuid_):
|
||||
for user in users:
|
||||
if user.uuid_ == uuid_:
|
||||
return user.to_dict(), 200
|
||||
|
||||
return f'Пользователь с uuid {uuid_} не найден', 404
|
||||
|
||||
@app.route('/info/<uuid:uuid_>', methods=['GET'])
|
||||
def get_one_with_messages(uuid_):
|
||||
for user in users:
|
||||
if user.uuid_ == uuid_:
|
||||
response = requests.get(messages_url + f'by-user/{uuid_}')
|
||||
print(response.json())
|
||||
return user.to_dict_with_messages(response.json()), 200
|
||||
|
||||
return f'Пользователь с uuid {uuid_} не найден', 404
|
||||
|
||||
@app.route('/check/<uuid:uuid_>', methods=['GET'])
|
||||
def check_exist(uuid_):
|
||||
for user in users:
|
||||
if user.uuid_ == uuid_:
|
||||
return '', 200
|
||||
return '', 404
|
||||
|
||||
@app.route('/', methods=['POST'])
|
||||
def create():
|
||||
data = request.json
|
||||
name = data.get('name', None)
|
||||
surname = data.get('surname', None)
|
||||
if name is None or surname is None:
|
||||
return 'Недостаточно информации для создания пользователя', 404
|
||||
|
||||
new_user = User(name, surname, None)
|
||||
users.append(new_user)
|
||||
return get_one(new_user.uuid_)
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['PUT'])
|
||||
def update_by_id(uuid_):
|
||||
data = request.json
|
||||
new_name = data.get('name', None)
|
||||
new_surname = data.get('surname', None)
|
||||
|
||||
for user in users:
|
||||
if user.uuid_ == uuid_:
|
||||
if new_name is not None:
|
||||
user.name = new_name
|
||||
if new_surname is not None:
|
||||
user.surname = new_surname
|
||||
return get_one(user.uuid_)
|
||||
|
||||
return f'Пользователь с uuid {uuid_} не найден', 404
|
||||
|
||||
|
||||
@app.route('/<uuid:uuid_>', methods=['DELETE'])
|
||||
def delete(uuid_):
|
||||
for user in users:
|
||||
if user.uuid_ == uuid_:
|
||||
users.remove(user)
|
||||
return 'Пользователь удален', 200
|
||||
|
||||
return f'Пользователь с uuid {uuid_} не найден', 404
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=20001, debug=True)
|
50
borschevskaya_anna_lab_4/README.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Отчет. Лабораторная работа 4
|
||||
|
||||
## Описание
|
||||
В ходе лабораторной работы были изучены главы туториала о работе с RabbitMQ. Результат выполнения заданий каждой главы
|
||||
отражен на скриншотах в папке /images:
|
||||
- Tutorial-Task1.png
|
||||
![Tutorial-Task1](images/Tutorial-Task1.png)
|
||||
- Tutorial-Task2.png
|
||||
![Tutorial-Task2](images/Tutorial-Task2.png)
|
||||
- Tutorial-Task3.png
|
||||
![Tutorial-Task3](images/Tutorial-Task3.png)
|
||||
|
||||
Задание из 3-ей главы туториала было расширено условиями, которые были поставлены в задании к данной лабораторной работе.
|
||||
Для демонстрации работы сервисов посредством ассинхронного общения через брокер сообщений RabbitMQ была выбрана
|
||||
предметная область "Обработка заказов".
|
||||
|
||||
Сервис-издатель "Publisher" публикует в очередь сообщений событие поступления заказа с некоторым номером.
|
||||
|
||||
Сервисы-подписчики обрабатывают сообщения о заказах, при этом подписчики обрабатывают сообщение по-разному. Один вид
|
||||
подписчика обрабатывает с задержкой в несколько секунд, другой - "мгновенно", они получают одни и те жа сообщения,
|
||||
но соединены с разными очередями.
|
||||
В качестве эксперимента изначально были запущены по одному экземпляру каждого вида.
|
||||
На изображении Consumer2.png представлена работа мгновенно обрабатывающего подписчика. Он справляется с нагрузкой,
|
||||
так как размер очереди не растет.
|
||||
![Consumer 2](images/Consumer2.png)
|
||||
На изображении Consumer1.png представлена работа подписчика, обрабатывающего сообщения с задержкой. Как мы видим,
|
||||
в очереди накапливаются сообщения в состоянии 'Ready' - эти сообщения готовы для того, чтобы быть доставленными подписчикам.
|
||||
Сервис не справляется с нагрузкой, так как отправляются сообщения быстрее, чем обрабатываются.
|
||||
![Consumer 1](images/Consumer1.png)
|
||||
Для того, чтобы обеспечить равную скорость отправки и обработки, увеличиваем количество экземпляров-подписчиков данного типа до трех.
|
||||
На изображении видно, что теперь длина очереди не растет и система справляется с поступающими сообщениями. Также скорость "publish" и
|
||||
"consumer ack" стали равны.
|
||||
![Consumer 1](images/Consumer1-scaling.png)
|
||||
## Как запустить
|
||||
Для того, чтобы запустить сервисы, необходимо выполнить следующие действия:
|
||||
1. Установить и запустить Docker Engine или Docker Desktop
|
||||
2. Через консоль перейти в папку, в которой расположен файл docker-compose.yml
|
||||
3. Выполнить команду для запуска брокера сообщений rabbitmq:
|
||||
```
|
||||
docker compose up rabbit -d
|
||||
```
|
||||
4. Выполнить команду для запуска остальных контейнеров:
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
Такой порядок запуска важен для того, чтобы брокер сообщений успел полностью запуститься
|
||||
и произвести действия для того, чтобы быть готовым принимать соединения от сервисов. Потому что указания depends_on не хватает
|
||||
для отслеживания завершения всех необходимых подготовительных процессов брокера.
|
||||
## Видео-отчет
|
||||
Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/G0vsfp7vwazYHw).
|
23
borschevskaya_anna_lab_4/consumer-app/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
# Используем образ Maven для сборки
|
||||
FROM maven:3.8-eclipse-temurin-21-alpine AS build
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем остальные исходные файлы
|
||||
COPY pom.xml .
|
||||
COPY src src
|
||||
|
||||
# Собираем весь проект
|
||||
RUN mvn clean package -DskipTests
|
||||
RUN mvn dependency:copy-dependencies
|
||||
|
||||
# Используем официальный образ JDK для запуска собранного jar-файла
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
|
||||
# Копируем jar-файл из предыдущего этапа
|
||||
COPY --from=build /app/target/*.jar /app.jar
|
||||
COPY --from=build /app/target/dependency /
|
||||
|
||||
# Указываем команду для запуска приложения
|
||||
CMD ["java", "-jar", "app.jar"]
|
50
borschevskaya_anna_lab_4/consumer-app/pom.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.somecompany</groupId>
|
||||
<artifactId>consumer-app</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>5.22.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,15 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import ru.somecompany.config.property.RabbitProperties;
|
||||
|
||||
@SpringBootApplication
|
||||
@ConfigurationPropertiesScan(basePackageClasses = RabbitProperties.class)
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package ru.somecompany.config;
|
||||
|
||||
import com.rabbitmq.client.BuiltinExchangeType;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.somecompany.config.property.RabbitProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class ConnectionFactoryConfig {
|
||||
|
||||
private final RabbitProperties rabbitProperties;
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
factory.setHost(rabbitProperties.getHost());
|
||||
factory.setPort(rabbitProperties.getPort());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Connection connection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
|
||||
return connectionFactory.newConnection();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Channel channel(Connection connection) throws IOException {
|
||||
var exchange = rabbitProperties.getExchange();
|
||||
var queue = rabbitProperties.getQueue();
|
||||
var channel = connection.createChannel();
|
||||
|
||||
channel.exchangeDeclare(exchange, BuiltinExchangeType.FANOUT);
|
||||
channel.queueDeclare(queue, true, false, true, null);
|
||||
channel.queueBind(queue, exchange, "");
|
||||
return channel;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package ru.somecompany.config.property;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "app.rabbit-properties")
|
||||
public class RabbitProperties {
|
||||
|
||||
private String host;
|
||||
|
||||
private Integer port;
|
||||
|
||||
private Integer delay;
|
||||
|
||||
private String queue;
|
||||
|
||||
private String exchange;
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package ru.somecompany.consumer;
|
||||
|
||||
import com.rabbitmq.client.AMQP;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.DefaultConsumer;
|
||||
import com.rabbitmq.client.Envelope;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.somecompany.config.property.RabbitProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class Consumer {
|
||||
|
||||
private final RabbitProperties rabbitProperties;
|
||||
|
||||
private final Connection connection;
|
||||
private final Channel channel;
|
||||
|
||||
@PostConstruct
|
||||
public void consume() {
|
||||
try {
|
||||
channel.basicQos(1);
|
||||
channel.basicConsume(rabbitProperties.getQueue(), false, new DefaultConsumer(channel) {
|
||||
@Override
|
||||
public void handleDelivery(String consumerTag,
|
||||
Envelope envelope,
|
||||
AMQP.BasicProperties properties,
|
||||
byte[] body) throws IOException {
|
||||
long deliveryTag = envelope.getDeliveryTag();
|
||||
|
||||
String message = new String(body, StandardCharsets.UTF_8);
|
||||
System.out.println(" [x] Received '" + message + "'");
|
||||
|
||||
var delay = rabbitProperties.getDelay();
|
||||
try {
|
||||
doWork(delay);
|
||||
} finally {
|
||||
System.out.println(" [x] Processed '" + message + "'");
|
||||
channel.basicAck(deliveryTag, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception exception) {
|
||||
log.error("Error while set up connection with rabbit", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doWork(Integer delay) {
|
||||
if (delay > 0) {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException _ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanUp() throws Exception {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
server:
|
||||
port: ${SERVER_PORT:8081}
|
||||
|
||||
app:
|
||||
rabbit-properties:
|
||||
host: ${RABBIT_HOST:localhost}
|
||||
port: ${RABBIT_PORT:5672}
|
||||
delay: ${PROCESS_DELAY:0}
|
||||
queue: ${QUEUE_NAME:queue-1}
|
||||
exchange: ${EXCHANGE_NAME:order-events}
|
79
borschevskaya_anna_lab_4/docker-compose.yml
Normal file
@ -0,0 +1,79 @@
|
||||
services:
|
||||
rabbit:
|
||||
container_name: rabbit
|
||||
image: rabbitmq:3-management
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
- "5671:5671"
|
||||
networks:
|
||||
- local
|
||||
publisher:
|
||||
build: ./publisher-app
|
||||
container_name: publisher
|
||||
depends_on:
|
||||
- rabbit
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_PORT: 5672
|
||||
networks:
|
||||
- local
|
||||
consumer-1:
|
||||
build: ./consumer-app
|
||||
container_name: consumer-1
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_PORT: 5672
|
||||
PROCESS_DELAY: 3000
|
||||
QUEUE_NAME: queue1
|
||||
EXCHANGE_NAME: order-events
|
||||
networks:
|
||||
- local
|
||||
consumer-2:
|
||||
build: ./consumer-app
|
||||
container_name: consumer-2
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_PORT: 5672
|
||||
PROCESS_DELAY: 0
|
||||
QUEUE_NAME: queue2
|
||||
EXCHANGE_NAME: order-events
|
||||
networks:
|
||||
- local
|
||||
consumer-12:
|
||||
build: ./consumer-app
|
||||
container_name: consumer-12
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_PORT: 5672
|
||||
PROCESS_DELAY: 3000
|
||||
QUEUE_NAME: queue1
|
||||
EXCHANGE_NAME: order-events
|
||||
networks:
|
||||
- local
|
||||
consumer-13:
|
||||
build: ./consumer-app
|
||||
container_name: consumer-13
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_PORT: 5672
|
||||
PROCESS_DELAY: 3000
|
||||
QUEUE_NAME: queue1
|
||||
EXCHANGE_NAME: order-events
|
||||
networks:
|
||||
- local
|
||||
|
||||
networks:
|
||||
local:
|
34
borschevskaya_anna_lab_4/helloworld-tutorial/pom.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.somecompany</groupId>
|
||||
<artifactId>helloworld-tutorial</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>5.22.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>1.7.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,28 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import com.rabbitmq.client.DeliverCallback;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class Main {
|
||||
|
||||
private static final String QUEUE_NAME = "hello-world";
|
||||
|
||||
public static void main(String[] args) {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
factory.setHost("localhost");
|
||||
try(Connection connection = factory.newConnection();
|
||||
Channel channel = connection.createChannel();) {
|
||||
var sender = new Sender(channel);
|
||||
var receiver = new Receiver(channel);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println(" [*] Error in Hello-World");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.DeliverCallback;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Receiver {
|
||||
|
||||
private static final String QUEUE_NAME = "hello-world";
|
||||
|
||||
public Receiver(Channel channel) throws IOException {
|
||||
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
|
||||
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
|
||||
String message = new String(delivery.getBody(), "UTF-8");
|
||||
System.out.println(" [x] Received '" + message + "'");
|
||||
};
|
||||
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Sender {
|
||||
|
||||
private static final String QUEUE_NAME = "hello-world";
|
||||
|
||||
public Sender(Channel channel) throws IOException {
|
||||
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
|
||||
String message = "Hello World!";
|
||||
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
|
||||
System.out.println(" [x] Sent '" + message + "'");
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
# Root logger option
|
||||
log4j.rootLogger=INFO, stdout
|
||||
|
||||
# Direct log messages to stdout
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.Target=System.out
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
BIN
borschevskaya_anna_lab_4/images/Consumer1-scaling.PNG
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
borschevskaya_anna_lab_4/images/Consumer1.PNG
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
borschevskaya_anna_lab_4/images/Consumer2.PNG
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
borschevskaya_anna_lab_4/images/Tutorial-Task1.PNG
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
borschevskaya_anna_lab_4/images/Tutorial-Task2.PNG
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
borschevskaya_anna_lab_4/images/Tutorial-Task3.PNG
Normal file
After Width: | Height: | Size: 848 KiB |
38
borschevskaya_anna_lab_4/publisher-app/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
21
borschevskaya_anna_lab_4/publisher-app/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
# Используем образ Maven для сборки
|
||||
FROM maven:3.8-eclipse-temurin-21-alpine AS build
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем остальные исходные файлы
|
||||
COPY pom.xml .
|
||||
COPY src src
|
||||
|
||||
# Собираем весь проект
|
||||
RUN mvn clean package -DskipTests
|
||||
|
||||
# Используем официальный образ JDK для запуска собранного jar-файла
|
||||
FROM eclipse-temurin:21-jdk-alpine
|
||||
|
||||
# Копируем jar-файл из предыдущего этапа
|
||||
COPY --from=build /app/target/*.jar /app.jar
|
||||
|
||||
# Указываем команду для запуска приложения
|
||||
CMD ["java", "-jar", "app.jar"]
|
49
borschevskaya_anna_lab_4/publisher-app/pom.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.somecompany</groupId>
|
||||
<artifactId>publisher-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>5.22.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,17 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import ru.somecompany.config.property.RabbitProperties;
|
||||
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
@ConfigurationPropertiesScan(basePackageClasses = RabbitProperties.class)
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package ru.somecompany.config;
|
||||
|
||||
import com.rabbitmq.client.BuiltinExchangeType;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import ru.somecompany.config.property.RabbitProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class ConnectionFactoryConfig {
|
||||
|
||||
private final RabbitProperties rabbitProperties;
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
factory.setHost(rabbitProperties.getHost());
|
||||
factory.setPort(rabbitProperties.getPort());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Connection connection(ConnectionFactory connectionFactory) throws IOException, TimeoutException {
|
||||
return connectionFactory.newConnection();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Channel channel(Connection connection) throws IOException {
|
||||
var channel = connection.createChannel();
|
||||
channel.exchangeDeclare(rabbitProperties.getExchange(), BuiltinExchangeType.FANOUT);
|
||||
return channel;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package ru.somecompany.config.property;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "app.rabbit-properties")
|
||||
public class RabbitProperties {
|
||||
|
||||
private String host;
|
||||
|
||||
private Integer port;
|
||||
|
||||
private String exchange;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package ru.somecompany.scheduler;
|
||||
|
||||
import com.rabbitmq.client.BuiltinExchangeType;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SenderScheduler {
|
||||
|
||||
private static final String EXCHANGE_NAME = "order-events";
|
||||
private static final String MESSAGE = "Поступил заказ №%d";
|
||||
private Integer index = 0;
|
||||
|
||||
private final ConnectionFactory connectionFactory;
|
||||
private final Connection connection;
|
||||
private final Channel channel;
|
||||
|
||||
@Scheduled(cron = "*/1 * * * * *")
|
||||
public void sendMessage() {
|
||||
try {
|
||||
var message = String.format(MESSAGE, index);
|
||||
|
||||
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));
|
||||
index++;
|
||||
System.out.println(" [x] Sent '" + message + "'");
|
||||
} catch (IOException e) {
|
||||
System.out.println(" [x] Error while send message");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
server:
|
||||
port: ${SERVER_PORT:8080}
|
||||
|
||||
app:
|
||||
rabbit-properties:
|
||||
host: ${RABBIT_HOST:localhost}
|
||||
port: ${RABBIT_PORT:5672}
|
||||
exchange: ${EXCHANGE_NAME:order-events}
|
30
borschevskaya_anna_lab_4/workqueue-tutorial/pom.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.somecompany</groupId>
|
||||
<artifactId>workqueue-tutorial</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>amqp-client</artifactId>
|
||||
<version>5.22.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>1.7.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,23 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.Connection;
|
||||
import com.rabbitmq.client.ConnectionFactory;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static final String QUEUE_NAME = "task_queue";
|
||||
|
||||
public static void main(String[] args) {
|
||||
ConnectionFactory factory = new ConnectionFactory();
|
||||
factory.setHost("localhost");
|
||||
try(Connection connection = factory.newConnection();
|
||||
Channel channel = connection.createChannel();) {
|
||||
var sender = new Sender(channel);
|
||||
sender.send("Work Queue message");
|
||||
var receiver = new Receiver(channel);
|
||||
} catch (Exception e) {
|
||||
System.out.println(" [*] Error in Work-Queue: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.DeliverCallback;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static ru.somecompany.Main.QUEUE_NAME;
|
||||
|
||||
public class Receiver {
|
||||
|
||||
public Receiver(Channel channel) throws IOException {
|
||||
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
|
||||
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
|
||||
|
||||
channel.basicQos(1);
|
||||
|
||||
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
|
||||
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
|
||||
|
||||
System.out.println(" [x] Received '" + message + "'");
|
||||
try {
|
||||
doWork(message);
|
||||
} finally {
|
||||
System.out.println(" [x] Done");
|
||||
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
|
||||
}
|
||||
};
|
||||
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> { });
|
||||
}
|
||||
|
||||
private static void doWork(String task) {
|
||||
for (char ch : task.toCharArray()) {
|
||||
if (ch == '.') {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException _ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package ru.somecompany;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.MessageProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static ru.somecompany.Main.QUEUE_NAME;
|
||||
|
||||
public class Sender {
|
||||
|
||||
private Channel channel;
|
||||
|
||||
public Sender(Channel channel) throws IOException {
|
||||
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void send(String msg) throws IOException {
|
||||
String message = String.join(" ", msg);
|
||||
|
||||
channel.basicPublish("", QUEUE_NAME,
|
||||
MessageProperties.PERSISTENT_TEXT_PLAIN,
|
||||
message.getBytes("UTF-8"));
|
||||
System.out.println(" [x] Sent '" + message + "'");
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
# Root logger option
|
||||
log4j.rootLogger=INFO, stdout
|
||||
|
||||
# Direct log messages to stdout
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.Target=System.out
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
30
borschevskaya_anna_lab_5/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Отчет. Лабораторная работа 5
|
||||
|
||||
## Описание
|
||||
В рамках лабораторной работы была реализована программа, которая производит умножение матриц с применением последовательного и паралелльного алгоритма.
|
||||
При этом последовательный алгоритм достигается с помощью выделения одного потока на выполнение.
|
||||
|
||||
При указании одного потока подзадачи по умножению матриц полностью выполняются одним потоком. В качестве подзадачи было
|
||||
выбрано нахождение строки результирующей матрицы.
|
||||
|
||||
По условию задания необходимо было замерить результаты выполнения алгоритмов на квадратных матрицах размерами 100x100,
|
||||
300x300, 500x500. На всех прогонах можно увидеть, что последовательное выполнение умножения матриц происходит медленнее
|
||||
в несколько раз медленее. При этом чем больше потоков выделяется для выполнения подзадач, тем быстрее выполняется
|
||||
алгоритм параллельного умножения.
|
||||
|
||||
Результаты представлены на следующих изображениях:
|
||||
![100](images/100x100.PNG)
|
||||
![300](images/300x300.PNG)
|
||||
![500](images/500x500.PNG)
|
||||
|
||||
## Как запустить
|
||||
Необходимо иметь установленную JDK 21. Можно воспользоваться встроенным в нее компилятором (javac), а затем запустить исполняемый файл (java)
|
||||
или запускать из среды разработки.
|
||||
При запуске нужно указать аргументы командной строки:
|
||||
1. размер матриц (integer)
|
||||
2. режим отладки (boolean) - позволяет выводить в консоль исходные матрицы и промежуточные результаты работы
|
||||
|
||||
## Видео-отчет
|
||||
Работоспособность лабораторной работы можно оценить в следующем [видео](https://disk.yandex.ru/i/ZafQV9CGjBIKIw).
|
||||
Запуск происходил через IDEA с различными конфигурациями запуска (отличался размер умножаемых матриц и параметр отладки),
|
||||
чтобы увидеть результаты выполнения на матрицах всех размеров, необходимых по условию задачи.
|
BIN
borschevskaya_anna_lab_5/images/100x100.PNG
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
borschevskaya_anna_lab_5/images/300x300.PNG
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
borschevskaya_anna_lab_5/images/500x500.PNG
Normal file
After Width: | Height: | Size: 30 KiB |
38
borschevskaya_anna_lab_5/matrix-mul/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
17
borschevskaya_anna_lab_5/matrix-mul/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.uni.rvip</groupId>
|
||||
<artifactId>matrix-mul</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,98 @@
|
||||
package ru.uni.rvip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
var size = Integer.parseInt(args[0]);
|
||||
var debugMode = Boolean.parseBoolean(args[1]);
|
||||
System.out.printf("Размер матриц %dx%d\n", size, size);
|
||||
|
||||
var matrix1 = createRandomMatrix(size);
|
||||
if (debugMode) {
|
||||
printMatrix(matrix1);
|
||||
}
|
||||
var matrix2 = createRandomMatrix(size);
|
||||
if (debugMode) {
|
||||
printMatrix(matrix2);
|
||||
}
|
||||
var startTime = System.currentTimeMillis();
|
||||
var result1 = mulMatrix(matrix1, matrix2, 1); // сначала передаем в метод 1 поток-исполнитель
|
||||
var timeOfExecution = System.currentTimeMillis() - startTime;
|
||||
if (debugMode) {
|
||||
printMatrix(result1);
|
||||
}
|
||||
System.out.printf("Время умножения матриц с помощью последовательного алгоритма: %d ms\n", timeOfExecution);
|
||||
|
||||
var threadCounts = new int[] {2, 4, 6, 8};
|
||||
for (var threadCount: threadCounts) { // тестирование на разном количестве потоков-исполнителей
|
||||
startTime = System.currentTimeMillis();
|
||||
var result2 = mulMatrix(matrix1, matrix2, threadCount);
|
||||
timeOfExecution = System.currentTimeMillis() - startTime;
|
||||
if (debugMode) {
|
||||
printMatrix(result2);
|
||||
}
|
||||
System.out.printf("Время умножения матриц с помощью параллельного алгоритма (%d threads): %d ms\n",threadCount, timeOfExecution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int[][] createRandomMatrix(Integer size) {
|
||||
var matrix = new int[size][size];
|
||||
var random = new Random();
|
||||
for (var i = 0; i < size; i++) {
|
||||
for (var j = 0; j < size; j++) {
|
||||
matrix[i][j] = random.nextInt(100);
|
||||
}
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
private static int[][] mulMatrix(int[][] matrix1, int[][] matrix2, Integer threadCount) {
|
||||
if (matrix1[0].length != matrix2.length) {
|
||||
throw new IllegalArgumentException("Количество столбцов первой матрицы должна соответствовать количеству строк второй матрицы");
|
||||
}
|
||||
var rows = matrix2.length;
|
||||
var columns = matrix1[0].length;
|
||||
var result = new int[columns][rows];
|
||||
try (var executorService = Executors.newFixedThreadPool(threadCount)) {
|
||||
var futures = new ArrayList<Future<Integer>>();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
final int rowI = i;
|
||||
futures.add(executorService.submit(() -> calculate(rowI, matrix1, matrix2, result)));
|
||||
}
|
||||
|
||||
for (var future : futures) {
|
||||
future.get();
|
||||
}
|
||||
|
||||
executorService.shutdown();
|
||||
return result;
|
||||
} catch (Exception ignored) {
|
||||
throw new RuntimeException("Ошибка во время выполнения алгоритма");
|
||||
}
|
||||
}
|
||||
|
||||
private static int calculate(int i, int[][] matrix1, int[][] matrix2, int[][] result) {
|
||||
for (int j = 0; j < matrix1[0].length; j++) {
|
||||
result[i][j] = 0;
|
||||
for (int k = 0; k < matrix2[0].length; k++) {
|
||||
result[i][j] += matrix1[i][k] * matrix2[k][j];
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private static void printMatrix(int[][] matrix) {
|
||||
for (int[] ints : matrix) {
|
||||
for (int elem : ints) {
|
||||
System.out.printf("%5d\t", elem);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
30
dozorova_alena_lab_4/ConsumerDelay/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
17
dozorova_alena_lab_4/ConsumerDelay/ConsumerDelay.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>.</DockerfileContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
25
dozorova_alena_lab_4/ConsumerDelay/ConsumerDelay.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.35004.147
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumerDelay", "ConsumerDelay.csproj", "{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4DD86D5F-D90D-4BBB-AAA4-F16DA855B51E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3E7AED20-0868-42FE-9C39-581BC9D2BB22}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
22
dozorova_alena_lab_4/ConsumerDelay/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["ConsumerDelay.csproj", "."]
|
||||
RUN dotnet restore "./ConsumerDelay.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/."
|
||||
RUN dotnet build "./ConsumerDelay.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./ConsumerDelay.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "ConsumerDelay.dll"]
|
24
dozorova_alena_lab_4/ConsumerDelay/Program.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using ConsumerDelay;
|
||||
|
||||
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
|
||||
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
|
||||
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
|
||||
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
|
||||
var rabbitQueue = Environment.GetEnvironmentVariable("RABBIT_QUEUE") ?? "Second";
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var receiver = new Receiver(rabbitHost, rabbitUsername, rabbitPassword);
|
||||
|
||||
receiver.SubscribeTo(rabbitExchange, (message) =>
|
||||
{
|
||||
var rnd = new Random();
|
||||
|
||||
Console.WriteLine($"Пришло сообщение: {message}");
|
||||
|
||||
Thread.Sleep(rnd.Next(2000, 3000));
|
||||
Console.WriteLine($"Обработка сообщения завершена");
|
||||
},
|
||||
rabbitQueue);
|
||||
|
||||
while (true) ;
|
82
dozorova_alena_lab_4/ConsumerDelay/Receiver.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using System.Text;
|
||||
|
||||
namespace ConsumerDelay
|
||||
{
|
||||
public class Receiver : IDisposable
|
||||
{
|
||||
private readonly ConnectionFactory _connectionFactory;
|
||||
private readonly IConnection _connection;
|
||||
private readonly IModel _channel;
|
||||
|
||||
public Dictionary<string, HashSet<string>> Queues { get; private set; } = new();
|
||||
|
||||
public Receiver(string brockerHost, string brockerUsername, string brockerPassword)
|
||||
{
|
||||
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
|
||||
_connection = _connectionFactory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
}
|
||||
|
||||
public bool SubscribeTo(string exchange, Action<string> handler, string? queueName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Queues.ContainsKey(exchange))
|
||||
{
|
||||
_channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
|
||||
Queues.Add(exchange, new HashSet<string>());
|
||||
}
|
||||
if (queueName != null)
|
||||
_channel.QueueDeclare(queue: queueName,
|
||||
durable: true,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
queueName = queueName ?? _channel.QueueDeclare().QueueName;
|
||||
|
||||
|
||||
_channel.QueueBind(queue: queueName,
|
||||
exchange: exchange,
|
||||
routingKey: string.Empty);
|
||||
|
||||
var consumer = new EventingBasicConsumer(_channel);
|
||||
consumer.Received += (model, ea) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
|
||||
handler(message);
|
||||
_channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
|
||||
};
|
||||
_channel.BasicConsume(queue: queueName,
|
||||
autoAck: false,
|
||||
consumer: consumer);
|
||||
|
||||
Queues[exchange].Add(queueName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
~Receiver() => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
_channel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
30
dozorova_alena_lab_4/ConsumerSimple/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
17
dozorova_alena_lab_4/ConsumerSimple/ConsumerSimple.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>.</DockerfileContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
25
dozorova_alena_lab_4/ConsumerSimple/ConsumerSimple.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.35004.147
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsumerSimple", "ConsumerSimple.csproj", "{ACA8DE52-E29E-41BA-B3DA-213AF316685E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ACA8DE52-E29E-41BA-B3DA-213AF316685E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {73265D6C-436C-470E-AE8A-17047E6C2ECC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
22
dozorova_alena_lab_4/ConsumerSimple/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["ConsumerSimple.csproj", "."]
|
||||
RUN dotnet restore "./ConsumerSimple.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/."
|
||||
RUN dotnet build "./ConsumerSimple.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./ConsumerSimple.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "ConsumerSimple.dll"]
|
23
dozorova_alena_lab_4/ConsumerSimple/Program.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using ConsumerSimple;
|
||||
|
||||
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
|
||||
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
|
||||
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
|
||||
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
|
||||
var rabbitQueue = Environment.GetEnvironmentVariable("RABBIT_QUEUE") ?? "First";
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var receiver = new Receiver(rabbitHost, rabbitUsername, rabbitPassword);
|
||||
|
||||
receiver.SubscribeTo(rabbitExchange, (message) =>
|
||||
{
|
||||
var rnd = new Random();
|
||||
|
||||
Console.WriteLine($"Пришло сообщение: {message}");
|
||||
|
||||
Console.WriteLine($"Сообщение обрабатывается мгновенно");
|
||||
},
|
||||
rabbitQueue);
|
||||
|
||||
while (true) ;
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ConsumerSimple": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker"
|
||||
}
|
||||
}
|
||||
}
|
82
dozorova_alena_lab_4/ConsumerSimple/Receiver.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
using System.Text;
|
||||
|
||||
namespace ConsumerSimple
|
||||
{
|
||||
public class Receiver : IDisposable
|
||||
{
|
||||
private readonly ConnectionFactory _connectionFactory;
|
||||
private readonly IConnection _connection;
|
||||
private readonly IModel _channel;
|
||||
|
||||
public Dictionary<string, HashSet<string>> Queues { get; private set; } = new();
|
||||
|
||||
public Receiver(string brockerHost, string brockerUsername, string brockerPassword)
|
||||
{
|
||||
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
|
||||
_connection = _connectionFactory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
}
|
||||
|
||||
public bool SubscribeTo(string exchange, Action<string> handler, string? queueName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Queues.ContainsKey(exchange))
|
||||
{
|
||||
_channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
|
||||
Queues.Add(exchange, new HashSet<string>());
|
||||
}
|
||||
if (queueName != null)
|
||||
_channel.QueueDeclare(queue: queueName,
|
||||
durable: true,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
queueName = queueName ?? _channel.QueueDeclare().QueueName;
|
||||
|
||||
|
||||
_channel.QueueBind(queue: queueName,
|
||||
exchange: exchange,
|
||||
routingKey: string.Empty);
|
||||
|
||||
var consumer = new EventingBasicConsumer(_channel);
|
||||
consumer.Received += (model, ea) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
|
||||
handler(message);
|
||||
_channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
|
||||
};
|
||||
_channel.BasicConsume(queue: queueName,
|
||||
autoAck: false,
|
||||
consumer: consumer);
|
||||
|
||||
Queues[exchange].Add(queueName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
~Receiver() => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
_channel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
15
dozorova_alena_lab_4/EmitLog/EmitLog..csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>EmitLog_</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
24
dozorova_alena_lab_4/EmitLog/Program.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
|
||||
|
||||
var message = GetMessage(args);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
channel.BasicPublish(exchange: "logs",
|
||||
routingKey: string.Empty,
|
||||
basicProperties: null,
|
||||
body: body);
|
||||
Console.WriteLine($" [x] Sent {message}");
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
static string GetMessage(string[] args)
|
||||
{
|
||||
return ((args.Length > 0) ? string.Join(" ", args) : "info: Hello World!");
|
||||
}
|
14
dozorova_alena_lab_4/NewTask/NewTask.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
32
dozorova_alena_lab_4/NewTask/Program.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.QueueDeclare(queue: "task_queue",
|
||||
durable: true,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
var message = GetMessage(args);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
var properties = channel.CreateBasicProperties();
|
||||
properties.Persistent = true;
|
||||
|
||||
channel.BasicPublish(exchange: string.Empty,
|
||||
routingKey: "task_queue",
|
||||
basicProperties: properties,
|
||||
body: body);
|
||||
Console.WriteLine($" [x] Sent {message}");
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
static string GetMessage(string[] args)
|
||||
{
|
||||
return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!");
|
||||
}
|
30
dozorova_alena_lab_4/Publisher/.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
20
dozorova_alena_lab_4/Publisher/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["Publisher.csproj", "."]
|
||||
RUN dotnet restore "./Publisher.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/."
|
||||
RUN dotnet build "./Publisher.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./Publisher.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Publisher.dll"]
|
40
dozorova_alena_lab_4/Publisher/Program.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Publisher;
|
||||
using System.Text;
|
||||
|
||||
var rabbitHost = Environment.GetEnvironmentVariable("RABBIT_HOST") ?? "localhost";
|
||||
var rabbitUsername = Environment.GetEnvironmentVariable("RABBIT_USERNAME") ?? "user";
|
||||
var rabbitPassword = Environment.GetEnvironmentVariable("RABBIT_PASSWORD") ?? "password";
|
||||
var rabbitExchange = Environment.GetEnvironmentVariable("RABBIT_EXCHANGE") ?? "ReportIn";
|
||||
|
||||
var sender = new Sender(rabbitHost, rabbitUsername, rabbitPassword);
|
||||
|
||||
sender.AddExcange(rabbitExchange);
|
||||
|
||||
var rnd = new Random();
|
||||
|
||||
while (true)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
var type = rnd.Next();
|
||||
switch (type%2)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
sb.Append($"Был запрошен отчет о данных под номером {rnd.Next(1000)}");
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
sb.Append($"Был запрошен отчет об ошибках под номером {rnd.Next(1000)}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var text = sb.ToString();
|
||||
Console.WriteLine($"Было опубликовано сообщение: {text}");
|
||||
sender.PublishToExchange(rabbitExchange, text);
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Publisher": {
|
||||
"commandName": "Project"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker"
|
||||
}
|
||||
}
|
||||
}
|
17
dozorova_alena_lab_4/Publisher/Publisher.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>.</DockerfileContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
dozorova_alena_lab_4/Publisher/Publisher.csproj.user
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>Container (Dockerfile)</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
25
dozorova_alena_lab_4/Publisher/Publisher.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.10.35004.147
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Publisher", "Publisher.csproj", "{C23890FA-A4DD-4E5B-897F-37210C2F60CE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C23890FA-A4DD-4E5B-897F-37210C2F60CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3BB20EB3-DE49-46CE-8C7A-D956E3DE90BC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
69
dozorova_alena_lab_4/Publisher/Sender.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using RabbitMQ.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Publisher
|
||||
{
|
||||
public class Sender : IDisposable
|
||||
{
|
||||
private readonly ConnectionFactory _connectionFactory;
|
||||
private readonly IConnection _connection;
|
||||
private readonly IModel _channel;
|
||||
|
||||
public HashSet<string> Exchanges { get; private set; } = new HashSet<string>();
|
||||
|
||||
public Sender(string brockerHost, string brockerUsername, string brockerPassword)
|
||||
{
|
||||
_connectionFactory = new ConnectionFactory() { HostName = brockerHost, UserName = brockerUsername, Password = brockerPassword };
|
||||
_connection = _connectionFactory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
}
|
||||
|
||||
public bool AddExcange(string exchange, string exchangeType = ExchangeType.Fanout)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.ExchangeDeclare(exchange, exchangeType);
|
||||
Exchanges.Add(exchange);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PublishToExchange(string exchange, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Exchanges.Contains(exchange))
|
||||
return false;
|
||||
|
||||
var messageBody = Encoding.UTF8.GetBytes(message);
|
||||
_channel.BasicPublish(exchange: exchange,
|
||||
routingKey: string.Empty,
|
||||
basicProperties: null,
|
||||
body: messageBody);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
~Sender() => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
_channel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
39
dozorova_alena_lab_4/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Лабораторная работа 4
|
||||
В рамках данной работы были реализованы несколько проектов, работающих с RabbitMQ.
|
||||
## tutorial
|
||||
Для каждого урока были созданы консольные проекты
|
||||
### "Hello World!"
|
||||
![Task 1](image-4.png)
|
||||
### Work Queues
|
||||
![Task 2](image-5.png)
|
||||
### Publish/Subscribe
|
||||
![Task 3](image-6.png)
|
||||
## Описание
|
||||
В качестве предметной области была выбрана система запросов отчета двух видов: отчета и ошибок.
|
||||
## Запуск
|
||||
Для запуска лабораторной работы необходимо иметь запущенный Docker на устройстве.
|
||||
Необходимо перейти в папку, где располагается данный файл. Далее открыть терминал и ввести команду:
|
||||
```
|
||||
docker compose up -d --build
|
||||
```
|
||||
Порты для RabbitMQ были 8081 (для UI) и 5672.
|
||||
## Анализ
|
||||
Первоначальный вариант запуска предполагает, что имеется всего 2 потребителя:
|
||||
1. Тратит на обработку сообщения 2-3 секунды
|
||||
2. Тратит на обработку сообщения крайне малое время
|
||||
Задержка при обработке понижает пропускную способность обработчика, что вызывает переполнение очереди. Это подтверждается скринами.
|
||||
<br/>
|
||||
![alt text](image.png)
|
||||
<br/>
|
||||
![alt text](image-1.png)
|
||||
<br/>
|
||||
Теперь запустим несколько обычных обработчиков. Очередь не переполнена постоянно, а периодически, соответственно обработчики вполне справляются с потоком сообщений и увеличение их количества позволит в принципе избавиться от переполнения
|
||||
<br/>
|
||||
![alt text](image-2.png)
|
||||
<br/>
|
||||
![alt text](image-3.png)
|
||||
|
||||
## Видеодемонстрация
|
||||
Демонстрация: (https://drive.google.com/file/d/16gJMGbMKSFZ_I5gCzuDekpAqUrhbpFRA/view?usp=sharing) Стоит обратить внимание на то, что настройки docker compose файла не гарантируют верный порядок подъема контейнеров, из-за чего некоторые контейнеры пришлось перезапустить.
|
||||
|
||||
|
29
dozorova_alena_lab_4/Receive/Program.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.QueueDeclare(queue: "hello",
|
||||
durable: false,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
Console.WriteLine(" [*] Waiting for messages.");
|
||||
|
||||
var consumer = new EventingBasicConsumer(channel);
|
||||
consumer.Received += (model, ea) =>
|
||||
{
|
||||
var body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
Console.WriteLine($" [x] Received {message}");
|
||||
};
|
||||
channel.BasicConsume(queue: "hello",
|
||||
autoAck: true,
|
||||
consumer: consumer);
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
14
dozorova_alena_lab_4/Receive/Receive.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
31
dozorova_alena_lab_4/ReceiveLogs/Program.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
|
||||
|
||||
// declare a server-named queue
|
||||
var queueName = channel.QueueDeclare().QueueName;
|
||||
channel.QueueBind(queue: queueName,
|
||||
exchange: "logs",
|
||||
routingKey: string.Empty);
|
||||
|
||||
Console.WriteLine(" [*] Waiting for logs.");
|
||||
|
||||
var consumer = new EventingBasicConsumer(channel);
|
||||
consumer.Received += (model, ea) =>
|
||||
{
|
||||
byte[] body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
Console.WriteLine($" [x] {message}");
|
||||
};
|
||||
channel.BasicConsume(queue: queueName,
|
||||
autoAck: true,
|
||||
consumer: consumer);
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
14
dozorova_alena_lab_4/ReceiveLogs/ReceiveLogs.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
24
dozorova_alena_lab_4/Send/Program.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.QueueDeclare(queue: "hello",
|
||||
durable: false,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
const string message = "Hello World!";
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
channel.BasicPublish(exchange: string.Empty,
|
||||
routingKey: "hello",
|
||||
basicProperties: null,
|
||||
body: body);
|
||||
Console.WriteLine($" [x] Sent {message}");
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
14
dozorova_alena_lab_4/Send/Send.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
39
dozorova_alena_lab_4/Worker/Program.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
var factory = new ConnectionFactory { HostName = "localhost" };
|
||||
using var connection = factory.CreateConnection();
|
||||
using var channel = connection.CreateModel();
|
||||
|
||||
channel.QueueDeclare(queue: "task_queue",
|
||||
durable: true,
|
||||
exclusive: false,
|
||||
autoDelete: false,
|
||||
arguments: null);
|
||||
|
||||
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
|
||||
|
||||
Console.WriteLine(" [*] Waiting for messages.");
|
||||
|
||||
var consumer = new EventingBasicConsumer(channel);
|
||||
consumer.Received += (model, ea) =>
|
||||
{
|
||||
byte[] body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
Console.WriteLine($" [x] Received {message}");
|
||||
|
||||
int dots = message.Split('.').Length - 1;
|
||||
Thread.Sleep(dots * 1000);
|
||||
|
||||
Console.WriteLine(" [x] Done");
|
||||
|
||||
// here channel could also be accessed as ((EventingBasicConsumer)sender).Model
|
||||
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
|
||||
};
|
||||
channel.BasicConsume(queue: "task_queue",
|
||||
autoAck: false,
|
||||
consumer: consumer);
|
||||
|
||||
Console.WriteLine(" Press [enter] to exit.");
|
||||
Console.ReadLine();
|
14
dozorova_alena_lab_4/Worker/Worker.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
59
dozorova_alena_lab_4/docker-compose.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
services:
|
||||
rabbit:
|
||||
image: rabbitmq:3.10.7-management
|
||||
restart: always
|
||||
ports:
|
||||
- 5672:5672
|
||||
- 8081:15672
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: admin
|
||||
RABBITMQ_DEFAULT_PASS: admin
|
||||
|
||||
publisher:
|
||||
build: ./Publisher/
|
||||
restart: always
|
||||
depends_on:
|
||||
- rabbit
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_USERNAME: admin
|
||||
RABBIT_PASSWORD: admin
|
||||
RABBIT_EXCHANGE: 'ReportIn'
|
||||
|
||||
concumer1:
|
||||
build: ./ConsumerSimple/
|
||||
restart: always
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_USERNAME: admin
|
||||
RABBIT_PASSWORD: admin
|
||||
RABBIT_EXCHANGE: 'ReportIn'
|
||||
RABBIT_QUEUE: 'First'
|
||||
|
||||
concumer2:
|
||||
build: ./ConsumerSimple/
|
||||
restart: always
|
||||
depends_on:
|
||||
- rabbit
|
||||
- publisher
|
||||
environment:
|
||||
RABBIT_HOST: rabbit
|
||||
RABBIT_USERNAME: admin
|
||||
RABBIT_PASSWORD: admin
|
||||
RABBIT_EXCHANGE: 'ReportIn'
|
||||
RABBIT_QUEUE: 'Second'
|
||||
# concumer2:
|
||||
# build: ./ConsumerDelay/
|
||||
# restart: always
|
||||
# depends_on:
|
||||
# - rabbit
|
||||
# - publisher
|
||||
# environment:
|
||||
# RABBIT_HOST: rabbit
|
||||
# RABBIT_USERNAME: admin
|
||||
# RABBIT_PASSWORD: admin
|
||||
# RABBIT_EXCHANGE: 'ReportIn'
|
||||
# RABBIT_QUEUE: 'Second'
|
BIN
dozorova_alena_lab_4/image-1.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
dozorova_alena_lab_4/image-2.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
dozorova_alena_lab_4/image-3.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
dozorova_alena_lab_4/image-4.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
dozorova_alena_lab_4/image-5.png
Normal file
After Width: | Height: | Size: 136 KiB |