bondarenko_max_lab_2 #231

Merged
Alexey merged 4 commits from bondarenko_max_lab_2 into main 2024-12-15 13:38:12 +04:00
197 changed files with 7310 additions and 0 deletions
Showing only changes of commit 201eb8f79e - Show all commits

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
main.py

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="str.__pos__" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (tukaeva_alfiya_lab_4)" project-jdk-type="Python SDK" />
</project>

View File

@ -0,0 +1,48 @@
## Отчет по Docker Compose конфигурации
### Краткое описание:
Данная конфигурация Docker Compose запускает набор сервисов, необходимых для работы WordPress и MediaWiki. Она включает в себя:
- **WordPress:** веб-сервис для блогов и CMS
- **MySQL:** база данных для хранения данных WordPress
- **RabbitMQ:** брокер сообщений для потенциального использования в будущем
- **MediaWiki:** вики-движок для создания и редактирования вики-страниц
### Запуск лабораторной работы:
1. Установить Docker и Docker Compose.
2. Сохранить конфигурацию в файл docker-compose.yml.
3. Запустить команду docker-compose up --build
### Используемые технологии:
- **Docker Compose:** инструмент для определения и запуска многоконтейнерных приложений.
- **Docker:** платформа для создания, развертывания и запуска контейнеров.
- **WordPress:** популярная платформа для создания блогов и CMS.
- **MySQL:** популярная система управления базами данных.
- **RabbitMQ:** брокер сообщений, используемый для асинхронного обмена сообщениями.
- **MediaWiki:** свободное программное обеспечение для создания и редактирования вики-страниц.
### Функциональность:
Конфигурация запускает следующие сервисы:
- **WordPress:** работает на порту 8080, доступен по адресу http://localhost:8080.
- **MySQL:** предоставляет базу данных для WordPress и MediaWiki.
- **RabbitMQ:** работает на порту 5672, доступен по адресу http://localhost:15672 для управления.
- **MediaWiki:** работает на порту 8081, доступен по адресу http://localhost:8081.
### Дополнительные сведения
- **Volumes**: используются для хранения данных сервисов, чтобы они не терялись при перезапуске контейнеров.
- **Depends_on**: указывает на зависимость между сервисами, например, WordPress зависит от MySQL.
- **Restart policy**: определяет, как сервисы будут перезапускаться после сбоя.
### Видео
https://vk.com/video/@artamonovat?z=video212084908_456239356%2Fpl_212084908_-2
### Заключение:
Данная конфигурация Docker Compose обеспечивает простой и удобный способ запуска и управления несколькими сервисами, связанными с WordPress и MediaWiki. Она позволяет разработчикам легко развертывать и управлять приложениями в изолированной среде.

View File

@ -0,0 +1,61 @@
version: '3.7'
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
volumes:
- wordpress_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: password
depends_on:
- db
restart: unless-stopped
db:
image: mysql:latest
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: dbpassword
MYSQL_ROOT_PASSWORD: rootpassword
restart: unless-stopped
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: password
restart: unless-stopped
mediawiki:
image: mediawiki:latest
ports:
- "8081:80"
volumes:
- mediawiki_data:/var/www/html
environment:
MW_DB_SERVER: db
MW_DB_NAME: mediawiki
MW_DB_USER: mediawiki
MW_DB_PASSWORD: mediawiki_password
depends_on:
- db
restart: unless-stopped
volumes:
wordpress_data:
db_data:
rabbitmq_data:
mediawiki_data:

5
artamonova_tatyana_lab_2/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.pyc
__pycache__
*.egg-info
*.dist-info
.DS_Store

View File

@ -0,0 +1,22 @@
## Лабораторная работа №2
### Выполнила Артамонова Татьяна ПИбд-42
**Вариант 1: Программа 4 - Количество символов в именах файлов из каталога /var/data**
- Формирует файл /var/result/data1.txt так, что каждая строка файла - количество символов в именах файлов из каталога /var/data.
**Вариант 2: Программа 3 - Количество чисел в последовательности**
- Ищет набольшее число из файла /var/result/data1.txt и сохраняет количество таких чисел из последовательности в /var/result/data2.txt.
**Структура проекта:**
1. В папках worker-1, worker-2 лежат выполняемые файлы .py и Dockerfile-ы с необходимым набором инструкций.
2. В папке data лежат файлы, длину имен которых нужно посчитать.
3. В папке result лежат файлы с результатами выполнения программ. data1.txt - результат выполнения main1.py (worker-1), data2.txt - результат выполнения main2.py (worker-2). Данные в data2 рассчитываются из данных data1.
4. Файл .gitignore - для указания, какие файлы отслеживать, а какие - нет.
5. docker-compose.yml - для определения и управления контейнерами Docker.
**Команда для запуска** - docker-compose up --build
**Ссылка на видео:** https://vk.com/artamonovat?z=video212084908_456239357%2Fvideos212084908%2Fpl_212084908_-2

View File

@ -0,0 +1,22 @@
services:
worker-1:
build:
context: ./worker-1
volumes:
- ./worker-1:/app
- ./data:/var/data
- ./result:/var/result
depends_on:
- worker-2
worker-2:
build:
context: ./worker-2
volumes:
- ./worker-2:/app
- ./data:/var/data
- ./result:/var/result
volumes:
data:
result:

View File

@ -0,0 +1,3 @@
15
18
18

View File

@ -0,0 +1 @@
2

View File

@ -0,0 +1,14 @@
# Используем образ Python 3.10-slim как основу для нашего контейнера.
# slim-версия образа более компактная, что делает контейнер меньше.
FROM python:3.10-slim
# Устанавливаем рабочую директорию в контейнере как /app.
# Все последующие команды будут выполняться в этой директории.
WORKDIR /app
# Копируем файл main1.py из текущей директории в директорию /app в контейнере.
COPY main1.py .
# Определяем команду, которая будет выполняться при запуске контейнера.
# В данном случае запускается Python-скрипт main1.py.
CMD ["python", "main1.py"]

View File

@ -0,0 +1,21 @@
import os
import glob
# Формирует файл data1.txt так, что каждая строка файла - кол-во символов в именах файла из каталога /data
def main():
data_dir = "/var/data"
result_file = "/var/result/data1.txt"
result_dir = os.path.dirname(result_file)
if not os.path.exists(result_dir):
os.makedirs(result_dir)
files = glob.glob(os.path.join(data_dir, '*'))
with open(result_file, 'w') as f:
for file in files:
filename = os.path.basename(file)
f.write(f"{len(filename)}\n")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,14 @@
# Используем образ Python 3.10-slim как основу для нашего контейнера.
# slim-версия образа более компактная, что делает контейнер меньше.
FROM python:3.10-slim
# Устанавливаем рабочую директорию в контейнере как /app.
# Все последующие команды будут выполняться в этой директории.
WORKDIR /app
# Копируем файл main2.py из текущей директории в директорию /app в контейнере.
COPY main2.py .
# Определяем команду, которая будет выполняться при запуске контейнера.
# В данном случае запускается Python-скрипт main2.py.
CMD ["python", "main2.py"]

View File

@ -0,0 +1,26 @@
import os
# Ищет наибольшее число из файла data1.txt и сохраняет количество таких чисел из последовательности в data2.txt
def main():
data_file_path = "/var/result/data1.txt"
result_file_path = "/var/result/data2.txt"
if not os.path.exists(data_file_path):
data_dir = os.path.dirname(data_file_path)
if not os.path.exists(result_file_path):
result_dir = os.path.dirname(result_file_path)
with open(data_file_path, 'r') as f:
numbers = [int(x.strip()) for x in f.read().splitlines()]
max_number = max(numbers)
count = numbers.count(max_number)
with open(result_file_path, 'w') as f:
f.write(str(count))
print(f"Количество наибольших чисел: {count}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,34 @@
# Богданов Дмитрий ПИбд-42
# Лабораторная работа №4
## Предметная область:
Автоматизация работы теплицы
## Результаты выполнения туториалов:
- Первый туториал:
![изображение 1](./images/tut1.png)
- Второй туториал:
![изображение 2](./images/tut2.png)
- Третий туториал:
![изображение 3](./images/tut3.png)
## Данные из RabbitMQ:
![изображение 1](./images/rmq1.png)
![изображение 2](./images/rmq2.png)
![изображение 3](./images/rmq3.png)
![изображение 3](./images/rmq4.png)
### Вывод:
Из-за моментальной обработки сообщений в Consumer2, его очередь никогда не заполняется.
Consumer1 же тратит на обработку 2 секунды, из-за чего соответствующая очередь существенно заполняется при одном
запущенном экземпляре.
При нескольких запущенных экземплярах Consumer1 очередь заполняется существенно медленнее, и перестаёт заполняться совсем при определенном кол-ве запущенных экземпляров.
## [Видео](https://drive.google.com/file/d/1KWHHYWiK8OX48OfhDnEKDtMz-Umfs0uj/view?usp=sharing)

View File

@ -0,0 +1,27 @@
import pika
import time
def callback(ch, method, properties, body):
print(f'Receiver 1: получено сообщение. {body.decode()}')
time.sleep(3)
print('Receiver 1 закончил обработку')
def consume_events_1():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
channel = connection.channel()
channel.queue_declare(queue='receiver1_queue')
channel.queue_bind(exchange='greenhouse_events', queue='receiver1_queue')
channel.basic_consume(queue='receiver1_queue', on_message_callback=callback, auto_ack=True)
print('Ожидание сообщения...')
channel.start_consuming()
if __name__ == "__main__":
consume_events_1()

View File

@ -0,0 +1,24 @@
import pika
def callback(ch, method, properties, body):
print(f'Receiver 2: получено сообщение. {body.decode()}')
print('Receiver 2 закончил обработку')
def consume_events_2():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
channel = connection.channel()
channel.queue_declare(queue='receiver2_queue')
channel.queue_bind(exchange='greenhouse_events', queue='receiver2_queue')
channel.basic_consume(queue='receiver2_queue', on_message_callback=callback, auto_ack=True)
print('Ожидание сообщения...')
channel.start_consuming()
if __name__ == "__main__":
consume_events_2()

View File

@ -0,0 +1,25 @@
import pika
import time
def publish_events():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
channel = connection.channel()
channel.exchange_declare(exchange='greenhouse_events', exchange_type='fanout')
events = [
"Влажность превысила верхнюю границу",
"Влажность упала за нижнюю границу",
"Полив начат",
"Полив остановлен"
]
while True:
event = events[int(time.time()) % len(events)]
channel.basic_publish(exchange='greenhouse_events', routing_key='', body=event)
print(f'Отправлено: {event}')
time.sleep(1)
if __name__ == "__main__":
publish_events()

View File

@ -0,0 +1,25 @@
import pika, sys, os
def main():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
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)

View File

@ -0,0 +1,13 @@
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
channel = connection.channel()
channel.queue_declare('hello')
channel.basic_publish(exchange='',
routing_key='hello',
body='Hello world!')
print(" [x] Sent 'Hello world!'")
connection.close()

View File

@ -0,0 +1,19 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
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()

View File

@ -0,0 +1,22 @@
import pika
import time
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
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()

View File

@ -0,0 +1,13 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
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()

View File

@ -0,0 +1,22 @@
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost', port=5672, credentials=pika.PlainCredentials("user", "password")))
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()

View File

@ -0,0 +1,12 @@
version: '3.8'
services:
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
environment:
RABBITMQ_DEFAULT_USER: user
RABBITMQ_DEFAULT_PASS: password
ports:
- "5672:5672"
- "15672:15672"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,88 @@
import random as rnd
import threading
import time
from multiprocessing import Pool
def generateSquareMatrix(size):
return [[rnd.randint(0, 100) for i in range(size)] for j in range(size)]
def printMatrix(matrix):
for row in matrix:
print(*row, sep="\t")
# Перемножение без использования потоков
def matrixMultiplyStandard(matrix1, matrix2):
l1 = len(matrix1)
l2 = len(matrix2)
global result_matrix
result = result_matrix
for i in range(l1):
for j in range(l2):
for k in range(l2):
result[i][j] += matrix1[i][k] * matrix2[k][j]
return result
result_matrix = [[0 for i in range(500)] for j in range(500)]
# Перемножение в отдельном потоке
def matrixMultiplySingleThread(args):
matrix1, matrix2, start_i, end_i = args
global result_matrix
result = result_matrix
for i in range(start_i, end_i):
for j in range(len(matrix2[0])):
for k in range(len(matrix2)):
result[i][j] += matrix1[i - start_i][k] * matrix2[k][j]
# Параллельное перемножение, использует ф-ю выше для каждого потока
def matrixMultiplyWithThreads(matrix1, matrix2, thread_count):
l1 = len(matrix1)
l2 = len(matrix2)
# Кол-во строк на последний поток, если деление по потокам будет неточным
last_rows_count = 0
if l1 % thread_count == 0:
rows_per_thread = l1 // thread_count
else:
rows_per_thread = l1 // thread_count
last_rows_count = l1 % thread_count
for i in range(thread_count):
start_i = i * rows_per_thread
if (i - 1) == thread_count and last_rows_count > 0:
end_i = start_i + last_rows_count
else:
end_i = start_i + rows_per_thread
args = []
args.append((matrix1[start_i:end_i], matrix2, start_i, end_i))
with Pool(processes = thread_count) as pool:
pool.map(matrixMultiplySingleThread, args)
if __name__ == "__main__":
sizes = [100, 300, 500]
num_threads = [1, 5, 8, 12]
for size in sizes:
matrix1 = generateSquareMatrix(size)
matrix2 = generateSquareMatrix(size)
start_time = time.time()
matrixMultiplyStandard(matrix1, matrix2)
end_time = time.time()
print(f"Standard size {size}: {end_time - start_time}s")
for threads in num_threads:
start_time = time.time()
matrixMultiplyWithThreads(matrix1, matrix2, threads)
end_time = time.time()
print(f"Parallel size {size}, {threads} thread(s): {end_time - start_time}s")
print("-" * 100)

View File

@ -0,0 +1,18 @@
# Богданов Дмитрий ПИбд-42
# Лабораторная работа №5
## Функционал:
- Были созданы методы генерации и отображения матриц заданного размера
- Былы созданы методы для параллельного умножения матриц с использованием Pool
- Был написан код для бенчмаркинга стандартного и параллельного перемножений
## Результаты выполнения:
![изображение 1](./images/Screenshot_1.png)
### Вывод:
Использование нескольких потоков приносит значительный выигрыш только на крупных матрицах, в то время как на матрицах меньшего размера больше времени уходит на менеджмент потоков. Это особенно заметно при сравнении результатов выполнения вычислений на матрице размером 100х100.
## [Видео](https://drive.google.com/file/d/1iPfLjzLiWwmszPH_KJ40vFCX-iWDLm1S/view?usp=sharing)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -0,0 +1,37 @@
# Отчет. Лабораторная работа 7
## Балансировка нагрузки в распределённых системах при помощи открытых технологий на примерах
### Какие алгоритмы и методы используются для балансировки нагрузки?
В распределенных системах балансировка нагрузки может осуществляться на разных уровнях:
- балансировка нагрузки на сетевом уровне
- балансировка нагрузки на транспортном уровне
- балансировка нагрузки на прикладном уровне
На сетевом уровне балансировка может реализовываться с помощью таких алгоритмов, как Round Robin, Weighted Round Robin или Least Connections.
Алгоритм Round Robin основывается на принципе выделения одному доменному имени несколько IP, которые выбираются
при поступлении запроса по очереди.
Weighted Round Robin - усовершенствованный алгоритм Round Robin, который подразумевает указание весов каждому серверу
в зависимости от доступных мощностей.
Алгоритм Least Connections вносит в предыдущий алгоритм еще одно условие выбора сервера - количество активных подключений к нему,
избегая перегруженности имеющихся узлов.
### Какие открытые технологии существуют для балансировки нагрузки?
Среди самых популярных открытых технологий можно выделить Nginx и Kubernetes. Nginx позволяет управлять нагрузкой на
компоненты системы с помощью различных алгоритмов и перенаправляет запросы от клиента к сервисам, выстпая в качестве реверс-прокси.
Kubernetes представляет собой платформу для управления контейнерами, которая включает встроенные механизмы для балансировки
нагрузки между подами.
### Как осуществляется балансировка нагрузки на базах данных?
Можно уменьшить нагрузку на базу данных путем использования механизма кэширования на уровне приложения
или с помощью нереляционных БД (Redis и др.). Или с помощью шардирования данных, размещенных в базе.
Для балансировки нагрузки на базах данных на прикладном уровне, например, существует утилита pgpool —
прокси между клиентом и сервером СУБД PostgreSQL, с помощью которого задаются правила перенаправления
запросов к БД в зависимости от их содержания (чтение/запись).
### Реверс-прокси как один из элементов балансировки нагрузки.
Реверс-прокси перенаправляет входящие запросы на соотвествующие сервисы, может выступать при этом и в качестве балансировщика нагрузки, реализуя один из ранее озвученных алгоритмов.
Как уже было сказано, примером такого компонента РС может выступать Nginx, в котором с помощью настройки конфигурационного файла можно указать как настройки проксирования,
так и добавить логику балансировки.

View File

@ -0,0 +1,30 @@
# Отчет. Лабораторная работа 8
### Популярность распределенных систем (РС)
Распределенные системы обладают рядом преимуществ, которые побуждают разработчиков адаптировать уже существующие решения к такой архитектуре (и переписывать монолит на микросервисы).
Данный подход позволяет сделать систему более отказоустойчивой, так как её компоненты становятся независимыми друг от друга.
Особенно это важно для сложных систем, для которых появляется возможность масштабировать отдельные узлы РС.
Также при разработке распределенных систем удобно распределяются задачи между командами разработки и есть возможность
использовать свой стек технологий для каждого сервиса.
### Системы оркестрации
Системы оркестрации автоматизируют развертывание, масштабирование и управление контейнерами, что значительно упрощает
разработку и сопровождение. Однако вместе с тем они накладывают определенные требования к участникам команды разработки,
которым необходимо иметь хотя бы базовые знания об используемой технологии.
### Очереди сообщений
Очереди обработки сообщений позволяют организовать асинхронное взаимодействие между компонентами системы.
Такой подход важен, если процесс при отправке сообщения не должен блокироваться, а ответ необязательно должен быть
получен и обработан мгновенно. Сообщения, которые передаются по очередям, могут быть некоторыми событиями или командами,
на которые могут отреагировать сразу несколько сервисов, подписанных на очередь.
### Преимущества и недостатки РС
К преимуществам РС, как уже было сказано выше, можно отнести масштабируемость, отказоустойчивость, гибкость внедрения новых функциональностей,
а к недостаткам - сложности отладки возникающих проблем и тестирования,
возникающие сетевые задержки при прохождении запроса через несколько сервисов.
### Параллелизм: за и против
Параллельные вычисления нужно применять там, где это действительно нужно. Например, при обработке больших данных, при возможности
разбиения задачи на несколько независимых подзадач.
Однако в случаях, где важна последовательность операций,
параллелизм может привести к усложнению логики приложения и ошибкам, а при малом объеме данных для обработки параллелизм может только ухудшить производительность.

12
dozorova_alena_lab_6/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
/dozorova_alena_lab_2/.vs
/dozorova_alena_lab_2/ConsoleApp1/.vs
/dozorova_alena_lab_2/ConsoleApp1/bin
/dozorova_alena_lab_2/ConsoleApp1/obj
/dozorova_alena_lab_2/ConsoleApp1/Properties/PublishProfiles
/dozorova_alena_lab_2/ConsoleApp2/.vs
/dozorova_alena_lab_2/ConsoleApp2/bin
/dozorova_alena_lab_2/ConsoleApp2/obj
/dozorova_alena_lab_6/ConsoleApp1/.vs
/dozorova_alena_lab_6/ConsoleApp1/bin
/dozorova_alena_lab_6/ConsoleApp1/obj

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View 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}") = "ConsoleApp1", "ConsoleApp1.csproj", "{29269567-7466-4C99-BEEF-F5766BDDFB24}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{29269567-7466-4C99-BEEF-F5766BDDFB24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29269567-7466-4C99-BEEF-F5766BDDFB24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29269567-7466-4C99-BEEF-F5766BDDFB24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29269567-7466-4C99-BEEF-F5766BDDFB24}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EDED6E1D-0A86-43F9-94EA-6ADCC1FA1B42}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public static class Extention
{
public static int[,] CreateMatrixWithoutColumn(this int[,] matrix, int column)
{
var result = new int[matrix.GetLength(0), matrix.GetLength(1) - 1];
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1) - 1; j++)
{
result[i, j] = j < column ? matrix[i, j] : matrix[i, j + 1];
}
}
return result;
}
public static int[,] CreateMatrixWithoutRow(this int[,] matrix, int row)
{
var result = new int[matrix.GetLength(0) - 1, matrix.GetLength(1)];
for (int i = 0; i < matrix.GetLength(0) - 1; i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
result[i, j] = i < row ? matrix[i, j] : matrix[i + 1, j];
}
}
return result;
}
public static double CalculateDeterminant(this int[,] matrix)
{
if (matrix.GetLength(0) == 2)
{
return matrix[0, 0] * matrix[1, 1] - matrix[0, 1] * matrix[1, 0];
}
double result = 0;
for (var j = 0; j < matrix.GetLength(0); j++)
{
result += (j % 2 == 1 ? 1 : -1) * matrix[1, j] *
matrix.CreateMatrixWithoutColumn(j).CreateMatrixWithoutRow(1).CalculateDeterminant();
}
//Console.WriteLine("Ко мне пришли с размером " + matrix.GetLength(0));
return result;
}
}
}

View File

@ -0,0 +1,87 @@

using ConsoleApp1;
using System.Data.Common;
using System.Diagnostics;
internal class Program
{
private static void Main(string[] args)
{
var value = new int[3] {100, 300, 500 };
foreach(var i in value)
{
var a = CreateMatrix(i, i);
var b = CreateMatrix(i, i);
List<long> times = new() {};
Console.WriteLine("Для пяти потоков: ");
for (int j = 1; j <= 5; j++)
{
var sw = new Stopwatch();
sw.Start();
Calculate(a, j);
sw.Stop();
times.Add(sw.ElapsedTicks);
}
Console.WriteLine("Количество тиков для вычисления матрицы стороной "+i+": "+string.Join("\t", times));
Console.WriteLine("Для десяти потоков: ");
for (int j = 1; j <= 10; j++)
{
var sw = new Stopwatch();
sw.Start();
Calculate(a, j);
sw.Stop();
times.Add(sw.ElapsedTicks);
}
Console.WriteLine("Количество тиков для вычисления матрицы стороной " + i + ": " + string.Join("\t", times));
}
}
private static int[,] CreateMatrix(int x, int y)
{
var rnd = new Random();
var res = new int[y, x];
for (int i = 0; i < y; i++)
{
for (int j = 0; j < x; j++)
{
res[i, j] = rnd.Next(0, 100);
}
}
return res;
}
private static double Calculate(int[,] matrix, int maxTask)
{
double res = 0;
var semaphore = new SemaphoreSlim(maxTask, maxTask);
for (var j = 0; j < matrix.GetLength(0) - 1; j++)
{
_ = Task.Run(() =>
{
try
{
semaphore.Wait();
res += (j % 2 == 1 ? 1 : -1) * matrix[1, j] *
matrix.CreateMatrixWithoutColumn(j).
CreateMatrixWithoutRow(1).CalculateDeterminant();
}
finally { semaphore.Release(); }
});
}
semaphore.Wait(maxTask);
return res;
}
}

View File

@ -0,0 +1,18 @@
# Лабораторная работа 6
В рамках данной работы мы изучаем выигрыш при распаралелливании процесса вычисления определителя матрицы
## Описание
Для вычисления определителя мы используем следующую формулу:
![alt text](image.png)
где ![alt text](image-1.png) - определитель матрицы, полученной из исходной вырезанием 1 строки и j столбца.
## Запуск
По опыту прошлой лабораторной работы, в консольном приложении был реализован алгоритм вычисление детерминанта и запущено сравнение затраченного времени (в тиках) для 5 и 10 потоков.
## Результаты
Результаты:
<br/>
![Результат](image-2.png)
<br/>
Как мы видим, подтверждаются выводы прошлой лабораторной работы: для небольших матриц выигрыш несущественнен из-за затраты времени на работу с потоком, а для больших эта разница в скорости уже существенна
## Видеодемонстрация
Видеодемонстрация по [адресу](https://drive.google.com/file/d/1dOMaRcTRiPwhn2E4ok1WUOeh_dD9NyDQ/view?usp=sharing)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,17 @@
**Балансировка нагрузки** предполагает равномерную нагрузку вычислительных узлов (процессора многопроцессорной ЭВМ или компьютера в сети)
Следует различать _статическую_ и _динамическую_ балансировки, где первая выполняется до начала выполнения распределенного приложения, а вторая в процессе.
Приведем примеры алгоритмов балансировки:
* *Круговой алгоритм*: - распределение входящих запросов между несколькими серверами в порядке циклической очередности. Модификации:
- *Взвешенный циклический перебор*, учитывающий мощность серверов
- *Динамический круговой алгоритм*, который учитывает текущую нагрузку на серверы при распределении запросов.
* *Наименьшее количество соединений* - направление входящих запросов на сервер с наименьшим количеством активных соединений в данный момент времени. Модификации:
- *Взвешенное наименьшее количество соединений* направляет запросы на сервер с наименьшим соотношением активных соединений к его назначенному весу.
* *Наименьшее время отклика* - направление запросов на сервер, который демонстрируют наилучшую производительность в данный момент. Он учитывает два ключевых фактора: время отклика сервера и количество активных соединений.
* *Наименьший объем трафика* динамический алгоритм балансировки нагрузки, который направляет входящие запросы на сервер, передающий наименьший объем данных в текущий момент.
Существует достаточно много различных технологий как для программной, так и аппаратной балансировки. Наиболее популярные решения это Nginx и OpenELB, MetalLB для Kubernetes.
Для обеспечения балансировка нагрузки на базах данных используется механизм **репликации**. Балансировка нагрузки заключается в распределении запросов от пользователей по разным копиям (репликам) базы данных.
**Реверс-прокси** — это сервер, который находится перед веб-серверами и пересылает запросы от клиента на эти веб-серверы. Он не является в полной мере балансировщиком, но может выполнять его функцию при большой нагрузке на систему.

View File

@ -0,0 +1,12 @@
Распределенные системы все больше набирают популярность в наше время. Этому способствуют такие ее качества как:
* *Устойчивость и масштабируемость* возможность репликации позволяет поддерживать в работоспособном состоянии всю систему при отказе одного из ее компонентов
* *Принцип единой ответственности* реализация каждого элемента системы в виде сервиса, ответственного за что-то одно, упрощает поддержку и дальнейшее развитие системы.
* *Увеличение производительности* использование одновременно несколько распределенных сервисов позволяет повысить скорость обработки данных
**Оркестраторы** призваны обеспечивать мониторинг за состоянием системы и выполнять функции по ее оптимизации: организация быстрого развертывания, балансировка нагрузки. Оркестраторы так же позволяют уведомлять пользователей о возникших проблемах и выполнять сбор, агрегацию и анализ логов. Подобная обработка данных позволяет выявить узкие места системы. Но оркестратор так же требует ресурсы на собственную работу.
Под **сообщениями** подразумеваются данные, которыми обмениваются сервисы внутри системы. **Очереди сообщений** обеспечивают асинхронный обмен информацией. Сервис, отправляющий сообщение, взаимодействует не с другим сервисом, а с очередью и ждет ответа, не блокируя собственные процессы. Брокеры сообщений повышают отказоустойчивость такого обмена они гарантируют доставку сообщения до сервиса-приемника.
На мой взгляд основными *преимуществами* распределенных приложений являются их масштабируемость и отказоустойчивость. Но в то же время они имеют и достаточно весомые *недостатки* сложность их поддержки и реализации, а также сложность обработки и трассировки транзакций.
**Параллельные вычисления** в подобных системах следует внедрять только в том случае, когда *выигрыш от скорости вычислений значительно повышает затраты на выделение ресурсов*. Вычислить математические характеристики матрицы, к примеру, может быть оправдано, а вот разнесение высокоуровневой логики может быть неоправданно

View File

@ -0,0 +1,27 @@
import java.util.concurrent.ExecutionException;
public class Benchmark {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] sizes = {5, 7, 10};
for (int size : sizes) {
int[][] matrix = MatrixGenerator.generateMatrix(size);
// Последовательное вычисление
long startSequential = System.nanoTime();
double detSequential = DeterminantCalculator.calculateDeterminant(matrix, 1);
long endSequential = System.nanoTime();
System.out.println("Последовательно (" + size + "x" + size + "): " + (endSequential - startSequential) / 1_000_000 + " ms, детерминант: " + detSequential);
// Параллельное вычисление
for (int numThreads : new int[]{2, 4, 8}) {
long startParallel = System.nanoTime();
double detParallel = DeterminantCalculator.calculateDeterminant(matrix, numThreads);
long endParallel = System.nanoTime();
System.out.println("Параллельно (" + size + "x" + size + ", " + numThreads + " потоков): " +
(endParallel - startParallel) / 1_000_000 + " ms, детерминант: " + detParallel);
}
System.out.println("--------------------------------------------------");
}
}
}

View File

@ -0,0 +1,80 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class DeterminantCalculator {
public static int calculateDeterminant(int[][] matrix, int numThreads) throws InterruptedException, ExecutionException {
int size = matrix.length;
// Если размер матрицы 1x1, возвращаем единственный элемент.
if (size == 1) {
return matrix[0][0];
}
// Если количество потоков равно 1, выполняем последовательный алгоритм.
if (numThreads == 1) {
return sequentialDeterminant(matrix);
}
// Иначе выполняем параллельный алгоритм.
return parallelDeterminant(matrix, numThreads);
}
private static int sequentialDeterminant(int[][] matrix) {
int size = matrix.length;
if (size == 1) {
return matrix[0][0];
}
if (size == 2) {
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
}
int determinant = 0;
for (int col = 0; col < size; col++) {
determinant += (int) (Math.pow(-1, col) * matrix[0][col] * sequentialDeterminant(getMinor(matrix, 0, col)));
}
return determinant;
}
private static int parallelDeterminant(int[][] matrix, int numThreads) throws InterruptedException, ExecutionException {
int size = matrix.length;
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
List<Future<Double>> futures = new ArrayList<>();
for (int col = 0; col < size; col++) {
int finalCol = col;
futures.add(executor.submit(() -> {
double minorDet = sequentialDeterminant(getMinor(matrix, 0, finalCol));
return Math.pow(-1, finalCol) * matrix[0][finalCol] * minorDet;
}));
}
int determinant = 0;
for (Future<Double> future : futures) {
determinant += future.get();
}
executor.shutdown();
return determinant;
}
private static int[][] getMinor(int[][] matrix, int row, int col) {
int size = matrix.length;
int[][] minor = new int[size - 1][size - 1];
for (int i = 0, mi = 0; i < size; i++) {
if (i == row) continue;
for (int j = 0, mj = 0; j < size; j++) {
if (j == col) continue;
minor[mi][mj] = matrix[i][j];
mj++;
}
mi++;
}
return minor;
}
}

View File

@ -0,0 +1,11 @@
public class MatrixGenerator {
public static int[][] generateMatrix(int size) {
int[][] matrix = new int[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
matrix[i][j] = (int) (Math.random() * 100);
}
}
return matrix;
}
}

View File

@ -0,0 +1,12 @@
# Поиск детерминанта
Данная работа посвящена реализации и сравнению последовательного и параллельного алгоритмов поиска детерминанта матриц на языке Java. Целью является оценка производительности при использовании разного числа потоков.
## Результаты:
![img.png](images/img.png)
Как видим, однозначно сказать нельзя, для маленьких матриц многопоточность особо не повлияла. Начиная с матрицы размерностью 10, есть прибавка в производительности. Посмотрим на сложность алгоритма, и она будет что-то около O(n!), так как алгоритм рекурсивный.
Для больших матриц, типа 100 на 100, сложность будет огромной, и не думаю что такое получится посчитать.
Ссылка на видео: https://drive.google.com/file/d/1eCNcSLLLfWGlOk5Z0y0CfOfkbxeASrdE/view?usp=sharing

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="ece3d3db-34eb-42c6-9dfd-726739d49f9f" name="Changes" comment="feature: completed lab 7" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$/.." value="emelyanov_artem_lab_7" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 4
}]]></component>
<component name="ProjectId" id="2nes6IK0Z3YgtIz3E1743Ralffw" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "emelyanov__artem__lab__6",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "/home/forever/УлГТУ/Распределенные вычисления и приложения/DAS_2024_1/emelyanov_artem_lab_7",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.keymap",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-28b599e66164-intellij.indexing.shared.core-IU-242.22855.74" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-IU-242.22855.74" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="ece3d3db-34eb-42c6-9dfd-726739d49f9f" name="Changes" comment="" />
<created>1729344698203</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1729344698203</updated>
<workItem from="1729344699237" duration="1655000" />
</task>
<task id="LOCAL-00001" summary="feature: completed lab 7">
<option name="closed" value="true" />
<created>1729346451585</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1729346451585</updated>
</task>
<option name="localTasksCounter" value="2" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="feature: completed lab 7" />
<option name="LAST_COMMIT_MESSAGE" value="feature: completed lab 7" />
</component>
</project>

View File

@ -0,0 +1,24 @@
# Балансировка нагрузки в распределённых системах
>Балансировка нагрузки - распределение сетевых или вычислительных нагрузок между несколькими серверами или ресурсами для оптимизации производительности, надежности и времени отклика.
Существуют такие **алгоритмы** для балансировки нагрузки, как:
- **Round Robin** — запросы идут по очереди на серверы.
- **Least Connections** — запрос направляется на сервер с наименьшей загрузкой.
- **Weighted Round Robin** — учитывает мощность серверов.
Ну и одними из популярных **технологий**, являются:
- **NGINX** и **HAProxy** — балансировщики на уровне HTTP/TCP.
- **Kubernetes Ingress** — управляет балансировкой в контейнерных средах.
Также можно осуществить балансировку нагрузки на **базах данных**, с помощью:
- **Чтение/Запись с репликами** — чтение с реплик, запись в мастер.
- **Шардинг** — деление данных по узлам.
- **PgBouncer** — управление пулами соединений.
Для балансировки нагрузки также можно использовать **реверс-прокси**.
>Реверс-прокси - это сервер, который принимает клиентские запросы и перенаправляет их на соответствующие внутренние сервера. Также он может быть использован для балансировки, кэширования и безопасности.

View File

@ -0,0 +1,33 @@
# Устройство распределенных систем
> Сложные системы такие как "ВКонтакте" пишутся в **распределенном** стиле, так как это даёт такие преимущества, как:
- **Масштабируемость**: Разделение на микросервисы позволяет масштабировать только те части системы, которые испытывают высокую нагрузку, не затрагивая другие компоненты.
- **Упрощение разработки и поддержки**: Микросервисная архитектура позволяет выделить разные команды для разработки и поддержки каждого отдельного сервиса, что ускоряет разработку и упрощает управление кодовой базой.
- **Устойчивость и отказоустойчивость**: Отказ одного микросервиса не ведет к отказу всей системы, что повышает её надежность.
- **Гибкость в выборе технологий**: Разные сервисы могут использовать разные языки программирования, базы данных и фреймворки, которые лучше подходят для конкретной задачи.
> Но у распределённых систем можно выявить и недостатки, такие как:
- **Сложность разработки и управления**
- **Сложность тестирования**
- **Задержки в сетевом взаимодействии**
> Для управления контейнерами в распределенных системах, были созданы специальные системы автоматизации для управления развертыванием. Называется **орекстратор**, и его преимущества заключаются в:
- **Автоматическое масштабирование**
- **Управление отказами**
- **Облегчение развертывания и обновления**
- **Управление конфигурациями и секретами**
> Для асинхронного взаимодействия между сервисами придуманы **очереди сообщений**, такие как, RabbitMQ или Kafka:
- **Сообщения** — это единицы данных, которые сервисы обмениваются друг с другом.
**Очереди** обеспечивают:
- **Буферизацию нагрузки**
- **Асинхронность**
- **Устойчивость к сбоям**
> Для внедрения параллельных вычислений в распределенную систему, должны быть весомы поводы, такие как, обработка большого кол-ва данных. Но если задачи простые или важна скорость обмена между сервисами, это может быть уже не так полезно.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,173 @@
# Лабораторная работа №1 - Знакомство с docker и docker-compose
**Цель**: изучение современных технологий контейнеризации.
**Задачи**:
1. Установить средство контейнеризации docker.
2. Изучить применение и принципы docker.
3. Изучить утилиту docker-compose и структуру файла docker-compose.yml.
4. Развернуть не менее 3х различных сервисов при помощи docker-compose.
5. Оформить отчёт в формате Markdown и создать Pull Request в git-репозитории.
## Разворачивание сервисов
Предлагается развернуть не менее 3х сервисов через docker-compose из списка ниже:
1. **mediawiki**
Движок вики.
2. **redmine**
Система учёта багов, т.е. баг-трекер.
3. **wordpress**
Популярная система управления контентом.
4. **drupal**
Ещё одна популярная система управления контентом.
5. **moodle**
Система для обучения, на которой построена <https://lms.ulstu.ru>.
6. **gitea**
Сервис для хранения репозиториев git, на котором работает этот репозиторий.
Требования и docker-compose:
* Несколько контейнеров.
* Хотя бы один volume.
* Хотя бы один порт, проброшенный на хост.
При этом разворачивание системы должно пройти до конца.
Например, должен быть создан администратор и система должна корректно функционировать.
Это необходимо будет предоставить в отчёте, поэтому не забывайте делать скриншоты.
## Ход работы
1. Установил Docker dekstop на свою ОС (Windows)
2. Открыл терминал и с помощью команды "docker --version" проверил корректность установки
3. Запустил первый тестовый контейнер с помощью команды "docker run hello-world". (Скрин Image_1)
4. Попробовал несколько базовых команд.(Скрин Image_2)
5. C помощью команды "docker-compose --version" проверил версию Docker-Compose. (Скрин Image_3)
6. Развернул сервисы (Описал ниже)
## Объяснение работы кода:
## WordPress:
1. Эта строка отвечает за образ сборки:
image: wordpress:latest
Используем последний официальный образ WordPress.
2. Задание имени контейнера:
container_name: wordpress
Устанавливается имя контейнера как wordpress.
3. Проброс портов:
Порт 80 контейнера пробрасывается на порт 8080 хоста.
4. Тома для хранения данных:
volumes:
- wordpress_data:/var/www/html
Том wordpress_data монтируется в директорию /var/www/html контейнера для хранения данных WordPress.
5. Переменные окружения для WordPress:
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: example_user
WORDPRESS_DB_PASSWORD: example_password
WORDPRESS_DB_NAME: example_db
Здесь задаются параметры для подключения к базе данных:
WORDPRESS_DB_HOST: адрес базы данных (контейнер с именем db).
WORDPRESS_DB_USER: имя пользователя базы данных.
WORDPRESS_DB_PASSWORD: пароль пользователя базы данных.
WORDPRESS_DB_NAME: имя базы данных.
6. Зависимость от базы данных:
depends_on: db
Указываем, что WordPress зависит от контейнера с базой данных (db), который должен запуститься первым.
## Redmine:
1. Эта строка отвечает за образ сборки:
image: redmine:latest
Используем последний официальный образ Redmine.
2. Задание имени контейнера:
container_name: redmine
Устанавливается имя контейнера как redmine.
3. Проброс портов:
ports:"8081:3000"
Порт 3000 контейнера пробрасывается на порт 8081 хоста
4. Переменные окружения для Redmine:
environment:
REDMINE_DB_MYSQL: redmine_db
REDMINE_DB_USERNAME: redmine_user
REDMINE_DB_PASSWORD: redmine_password
Задаются параметры для подключения к базе данных:
REDMINE_DB_MYSQL: имя контейнера базы данных.
REDMINE_DB_USERNAME: имя пользователя базы данных.
REDMINE_DB_PASSWORD: пароль пользователя базы данных.
5. Зависимость от базы данных:
depends_on:
- redmine_db
Redmine зависит от контейнера с базой данных redmine_db.
## Gitea:
1. Эта строка отвечает за образ сборки:
image: gitea/gitea:latest
Используем последний официальный образ Gitea.
2. Задание имени контейнера:
container_name: gitea
Устанавливается имя контейнера как gitea.
3. Проброс портов:
ports:"8082:3000"
Порт 3000 контейнера пробрасывается на порт 8082 хоста
4. Тома для хранения данных:
volumes:
- gitea_data:/data
Том gitea_data монтируется в директорию /data контейнера для хранения данных Gitea.
5. Переменные окружения для Gitea:
environment:
- USER_UID=1000
- USER_GID=1000
## Видео по ссылку:
https://vk.com/video64471408_456239204?list=ln-0pkqwQsSiTkihNDILH

View File

@ -0,0 +1,68 @@
version: '3'
services:
wordpress:
image: wordpress:latest
container_name: wordpress
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: example_user
WORDPRESS_DB_PASSWORD: example_password
WORDPRESS_DB_NAME: example_db
volumes:
- wordpress_data:/var/www/html
depends_on:
- db
db:
image: mysql:5.7
container_name: wordpress_db
environment:
MYSQL_DATABASE: example_db
MYSQL_USER: example_user
MYSQL_PASSWORD: example_password
MYSQL_ROOT_PASSWORD: root_password
volumes:
- db_data:/var/lib/mysql
redmine:
image: redmine:latest
container_name: redmine
ports:
- "8081:3000"
environment:
REDMINE_DB_MYSQL: redmine_db
REDMINE_DB_USERNAME: redmine_user
REDMINE_DB_PASSWORD: redmine_password
depends_on:
- redmine_db
redmine_db:
image: mysql:5.7
container_name: redmine_db
environment:
MYSQL_DATABASE: redmine
MYSQL_USER: redmine_user
MYSQL_PASSWORD: redmine_password
MYSQL_ROOT_PASSWORD: root_password
volumes:
- redmine_db_data:/var/lib/mysql
gitea:
image: gitea/gitea:latest
container_name: gitea
ports:
- "8082:3000"
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- gitea_data:/data
volumes:
wordpress_data:
db_data:
redmine_db_data:
gitea_data:

10
kadyrov_aydar_lab_2/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
data/
result/
__pycache__/
*.py[cod]
*.env
*.venv
.env.local
venv/
env/
.idea/

View File

@ -0,0 +1,78 @@
# Лабораторная работа №2 - Разработка простейшего распределённого приложения
## Задание
**Цель**: Разработать два приложения, результат работы первого из которых становится входными данными для второго.
**Задачи**:
1. Разработать два приложения:
- **app_one**: Ищет в каталоге /var/data файл с наибольшим количеством строк и копирует его содержимое в /var/result/data.txt.
- **app_two**: Ищет наименьшее число из файла /var/result/data.txt и сохраняет его в третьей степени в /var/result/result.txt.
2. Разработать файлы сборки Docker для каждого приложения.
3. Собрать файл docker-compose.yml для запуска обоих приложений.
4. Настроить монтирование директорий для обмена данными между контейнерами.
5. Правильно закоммитить решение с использованием .gitignore для исключения лишних файлов.
## Варианты задания:
1. **app_one**:
- Ищет файл с наибольшим количеством строк в каталоге /var/data.
- Копирует содержимое этого файла в /var/result/data.txt.
2. **app_two**:
- Читает файл /var/result/data.txt.
- Ищет наименьшее число и сохраняет его третью степень в /var/result/result.txt.
### Требования:
1. **Docker**: Платформа для контейнеризации приложений.
2. **Docker Compose**: Инструмент для управления многоконтейнерными приложениями на основе файла docker-compose.yml.
### Сборка и запуск:
В директории, где находится файл docker-compose.yml, выполним команду для сборки и запуска всех контейнеров:
docker-compose up --build
Эта команда:
1. Собирает все Docker-образы для сервисов.
2. Запускает контейнеры.
3. Автоматически подготавливает данные и выполняет приложения последовательно.
### Результаты:
После успешного завершения работы контейнеров можно проверить результаты в папке result:
- **data.txt** — файл, полученный после выполнения первого приложения (содержит копию файла с наибольшим количеством строк из папки data).
- **result.txt** — файл, полученный после выполнения второго приложения (содержит третью степень наименьшего числа из файла data.txt).
## Описание работы
### Программы:
1. **app_one/main.py**:
- Ищет файл с наибольшим количеством строк в каталоге /var/data.
- Копирует содержимое этого файла в /var/result/data.txt.
2. **app_two/main.py**:
- Читает файл /var/result/data.txt.
- Ищет наименьшее число в файле и возводит его в третью степень.
- Сохраняет результат в файл /var/result/result.txt.
### Генерация данных:
Для создания случайных данных был написан скрипт generate_data.py:
- Создает несколько файлов с целыми числами в каталоге /var/data.
- Каждый файл содержит случайные числа, которые будут использоваться первым приложением.
### Dockerfile:
Каждое приложение имеет собственный Dockerfile, где указаны шаги для сборки Python-образов и запуска программ.
## Вывод
В результате лабораторной работы было создано простейшее распределенное приложение, которое использует Docker и Docker Compose для запуска двух программ, обрабатывающих данные в контейнерах.
## Видео ВК
https://vk.com/video64471408_456239205?list=ln-nzLhpgninZdZBOzarw

View File

@ -0,0 +1,7 @@
FROM python:3.9-slim
WORKDIR /app
COPY . /app
CMD ["python", "main.py"]

View File

@ -0,0 +1,38 @@
import os
def get_file_with_most_lines(directory):
max_lines = 0
target_file = ""
for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
with open(filepath, 'r') as f:
line_count = sum(1 for _ in f)
if line_count > max_lines:
max_lines = line_count
target_file = filename
return target_file
def copy_file(src_directory, dest_directory, filename):
src_filepath = os.path.join(src_directory, filename)
dest_filepath = os.path.join(dest_directory, 'data.txt')
os.makedirs(dest_directory, exist_ok=True)
with open(src_filepath, 'r') as src_file:
with open(dest_filepath, 'w') as dest_file:
dest_file.write(src_file.read())
def main():
src_directory = '/var/data'
dest_directory = '/var/result'
target_file = get_file_with_most_lines(src_directory)
if target_file:
copy_file(src_directory, dest_directory, target_file)
print(f"File {target_file} copied to {dest_directory}/data.txt")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,7 @@
FROM python:3.9-slim
WORKDIR /app
COPY . /app
CMD ["python", "main.py"]

View File

@ -0,0 +1,25 @@
import os
def get_largest_number_from_file(filepath):
with open(filepath, 'r') as f:
numbers = [int(line.strip()) for line in f.readlines()]
return max(numbers)
def save_square_of_number(number, output_filepath):
result = number ** 2
with open(output_filepath, 'w') as f:
f.write(str(result))
def main():
input_filepath = '/var/result/data.txt'
output_filepath = '/var/result/result.txt'
if os.path.exists(input_filepath):
largest_number = get_largest_number_from_file(input_filepath)
save_square_of_number(largest_number, output_filepath)
print(f"Largest number squared: {largest_number}^2 saved to {output_filepath}")
else:
print(f"Input file {input_filepath} not found!")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,7 @@
FROM python:3.9-slim
WORKDIR /app
COPY . /app
CMD ["python", "generate_data.py"]

View File

@ -0,0 +1,29 @@
import os
import random
def generate_random_files(directory, num_files, num_lines_per_file, min_value, max_value):
os.makedirs(directory, exist_ok=True)
for i in range(num_files):
file_path = os.path.join(directory, f"file_{i + 1}.txt")
with open(file_path, 'w') as f:
for _ in range(num_lines_per_file):
random_number = random.randint(min_value, max_value)
f.write(f"{random_number}\n")
print(f"Generated file: {file_path}")
def main():
data_directory = '/var/data'
num_files = 10
num_lines_per_file = 12
min_value = 1
max_value = 100
generate_random_files(data_directory, num_files, num_lines_per_file, min_value, max_value)
print(f"Generated {num_files} files in {data_directory}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,22 @@
version: '3'
services:
data_generator:
build:
context: ./data_generator
volumes:
- ./data:/var/data
app_one:
build:
context: ./app_one
volumes:
- ./data:/var/data
- ./result:/var/result
depends_on:
- data_generator
app_two:
build:
context: ./app_two
volumes:
- ./result:/var/result
depends_on:
- app_one

View File

@ -0,0 +1,42 @@
# Лабораторная работа №3 - REST API, шлюз и синхронный обмен данными между микросервисами
## Задание
### Цель:
Изучение принципов проектирования с использованием паттерна шлюза, организации синхронной передачи данных между микросервисами и применения архитектурного стиля RESTful API.
### Задачи:
1. Создание двух микросервисов, которые реализуют операции CRUD для связанных сущностей.
2. Реализация механизма синхронного обмена данными между микросервисами.
3. Настройка шлюза на базе Nginx в качестве прозрачного прокси-сервера.
### Микросервисы:
1. **hero_service** — сервис, который управляет информацией о героях.
2. **item_service** — сервис, который обрабатывает данные о предметах, принадлежащих героям.
### Связь между микросервисами:
- Один документ (hero) может иметь множество связанных предметов (items) (соотношение 1 ко многим).
## Как запустить проект:
Для запуска приложения необходимо выполнить команду:
```bash
docker-compose up
```
## Описание работы:
Для разработки микросервисов был выбран язык программирования Python.
### Синхронный обмен данными
Сервис `hero_service` отправляет HTTP-запросы к `item_service` при выполнении определенных операций CRUD. Это позволяет получать актуальную информацию о предметах, связанных с конкретными героями.
### Docker Compose
Конфигурационный файл `docker-compose.yml` представляет собой многоконтейнерное приложение, которое включает в себя три сервиса: `hero_service`, `item_service` и `nginx`. Функция маршрутизации возложена на сервер Nginx, который обрабатывает запросы и перенаправляет их на соответствующие микросервисы.
### Nginx
Конфигурационный файл Nginx определяет настройки веб-сервера и обратного прокси, который управляет входящими запросами и направляет их на соответствующие сервисы.
### ВК ВИДЕО
https://vk.com/video64471408_456239206?list=ln-eG1UX8zXWbZkc651DD

View File

@ -0,0 +1,26 @@
version: '3.8'
services:
hero_service:
build:
context: ./hero_service
dockerfile: Dockerfile
ports:
- "5000:5000"
item_service:
build:
context: ./item_service
dockerfile: Dockerfile
ports:
- "5001:5001"
nginx:
image: nginx:latest
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- hero_service
- item_service

View File

@ -0,0 +1,10 @@
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

View File

@ -0,0 +1,51 @@
from flask import Flask, jsonify, request
import uuid
app = Flask(__name__)
heroes = {}
@app.route('/heroes', methods=['GET'])
def get_heroes():
return jsonify(list(heroes.values()))
@app.route('/heroes/<uuid:hero_uuid>', methods=['GET'])
def get_hero(hero_uuid):
hero = heroes.get(str(hero_uuid))
if hero:
return jsonify(hero)
return jsonify({'error': 'Not found'}), 404
@app.route('/heroes', methods=['POST'])
def create_hero():
data = request.get_json()
hero_uuid = str(uuid.uuid4())
hero = {
'uuid': hero_uuid,
'name': data['name'],
'role': data['role'],
'strength': data['strength']
}
heroes[hero_uuid] = hero
return jsonify(hero), 201
@app.route('/heroes/<uuid:hero_uuid>', methods=['PUT'])
def update_hero(hero_uuid):
hero = heroes.get(str(hero_uuid))
if not hero:
return jsonify({'error': 'Not found'}), 404
data = request.get_json()
hero['name'] = data['name']
hero['role'] = data['role']
hero['strength'] = data['strength']
return jsonify(hero)
@app.route('/heroes/<uuid:hero_uuid>', methods=['DELETE'])
def delete_hero(hero_uuid):
if str(hero_uuid) in heroes:
del heroes[str(hero_uuid)]
return '', 204
return jsonify({'error': 'Not found'}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

View File

@ -0,0 +1 @@
Flask

View File

@ -0,0 +1,10 @@
FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

View File

@ -0,0 +1,51 @@
from flask import Flask, jsonify, request
import uuid
app = Flask(__name__)
items = {}
@app.route('/items', methods=['GET'])
def get_items():
return jsonify(list(items.values()))
@app.route('/items/<uuid:item_uuid>', methods=['GET'])
def get_item(item_uuid):
item = items.get(str(item_uuid))
if item:
return jsonify(item)
return jsonify({'error': 'Not found'}), 404
@app.route('/items', methods=['POST'])
def create_item():
data = request.json
item_uuid = str(uuid.uuid4())
item = {
'uuid': item_uuid,
'name': data['name'],
'type': data['type'],
'hero_uuid': data['hero_uuid']
}
items[item_uuid] = item
return jsonify(item), 201
@app.route('/items/<uuid:item_uuid>', methods=['PUT'])
def update_item(item_uuid):
item = items.get(str(item_uuid))
if not item:
return jsonify({'error': 'Not found'}), 404
data = request.json
item['name'] = data['name']
item['type'] = data['type']
item['hero_uuid'] = data['hero_uuid']
return jsonify(item)
@app.route('/items/<uuid:item_uuid>', methods=['DELETE'])
def delete_item(item_uuid):
if str(item_uuid) in items:
del items[str(item_uuid)]
return '', 204
return jsonify({'error': 'Not found'}), 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)

View File

@ -0,0 +1 @@
Flask

View File

@ -0,0 +1,11 @@
server {
listen 80;
location /heroes {
proxy_pass http://hero_service:5000;
}
location /items {
proxy_pass http://item_service:5001;
}
}

View File

@ -0,0 +1,13 @@
# Балансировка нагрузки в распределённых системах
*Балансировка нагрузки в распределенных системах* это метод распределения сетевого трафика по пулу ресурсов приложения. Для этого существует достаточно большое число инструментов.
## Алгоритмы балансировки
Всё множество алгоритмов для балансировки можно условно разделить на 2 большие группы: рассчитанные на сохранение состояния и не рассчитанные.
- Первые, по большому счёту, балансируют подключения пользователей. Но это никак не гарантирует равномерного распределения их запросов.
- Вторые балансируют запросы между ресурсами. Это позволяет распределять нагрузку чуть более равномерно, но при этом необходимо задумываться о том, что бы система могла работать в режиме без сохранения состояния.
Сами же алгоритмы, по большому счёту, отличаются только механизмом выбора экземпляра ресурса, к которому перенаправляется запрос. Это может быть равномерное распределение (алгоритм **Round Robin**) или вариации взвешенного (алгоритм **Weighted Round Robin**).
На сегодняшний день существует множество открытых технологий балансировки нагрузки. Например, всем известный **Nginx** предоставляет инструменты для настройки подобной функции. Так же есть ряд проектов, полностью заточенных под балансировку нагрузки в Kubernetes (**OpenELB**, **MetalLB**).
## Балансировка нагрузки на БД
Балансировка нагрузки на БД более сложный и комплексный вопрос. Он предполагает, что у СУБД должен быть механизм репликации данных, или шардинга. Например, такое умеет **PostgreSQL** и **MongoDB**
## Реверс-прокси
Реверс-прокси в данном случае напрямую не является инструментом балансировки нагрузки. Скорее это инструмент, эту балансировку и распределённость в целом, скрывающий за единым фасадом. Самым известным представителем этого семейства можно назвать уже упомянутый ранее **Nginx**

View File

@ -0,0 +1,19 @@
# Устройство распределенных систем
Сегодня всё больше и больше сложным систем начинают переводить, или проектировать в распределённом формате.
## Причины популярности РС
На то есть много причин, перечислим основные из них:
- **Размеры.** На данный момент заметна тенденция создания не просто отдельных приложений для выполнения конкретных задач, а формирования некоторой экосистемы, где пользователь может делать много всего через единую точку входа. Это повышает сложность проектирования системы: она больше физически не может поместиться в голове у разработчиков. Поэтому переход на структуру, когда всё разбито на мелкие, взаимосвязанные элементы вполне оправдан.
- **Масштабируемость.** Нагрузка на системы растёт постоянно. И нет оснований полагать, что дальше будет не так. Монолит конечно может держать нагрузку, но он лишён возможности точечного горизонтального масштабирования (упёрлись в то, что у нас очень долго работает механизм отправки сообщений о готовности увеличили количество таких сервисов узкое место ушло)
- **Переиспользуемость.** Написанные один раз сервис авторизации, можно с минимальными доработками перенести и в другие продукты. Это сокращает затраты на разработку системы.
## Оркестраторы
Так же, с приходом распределённых систем важную роль стали играть оркестраторы. Фактически, они смогли собрать в себе сразу ряд полезных функций: мониторинг состояния системы, оповещения пользователей о том, что что-то не так, организация процесса быстрого развёртывания, Возможность сбора, агрегации и анализа логов, для поиска узких мест системы и их дальнейшего устранения. Но, как и всегда, оркестратор имеет и минусы: он требует ресурсы (и порой не малые) для своей работы, порог входа у него не всегда низкий.
## Очереди сообщений
В распределённой системе, помимо прочего, появляется возможность активно использовать системы асинхронного обмена сообщениями через очереди. Этот механизм значительно экономит ресурсы процессора, так как позволяет вместо нахождения в бесконечном цикле ожидания (пока запрос обработается) делать что-то полезное. К тому же брокеры сообщений, которые часто используются в асинхронной обработке, могут повышать надёжность системы: если в момент обработки сообщения упадёт сервис-приёмник, то информация о подтверждении не доедет до очереди и сообщения будет доставлено через другой экземпляр, а не потеряется, так как очередь будет считать его недоставленным, и повторять отправку.
## Параллельные вычисления
Что же касается параллельных вычислений это в первую очередь вопрос производительности. Использование нескольких потоков может ускорить работу кратно, но не всегда это применимо, так как порой перестройка алгоритма может потребовать ресурсов многократно больших, чем будет выигрыш, полученный от ускорения. Если речь о чистой математике(умножить матрицы) это и правда полезно, а вот попытка развести высокоуровневую бизнес-логику по потокам может быть изначально плохой идеей.
## Недостатки РС
Стоит так же отметить, что распределённые системы, несмотря на достоинства, имеют и недостатки. В основном их источник отсутствие большого опыта создания таких систем, а так же простое правило, что вероятность ошибки в системе тем больше, чем из большего количества элементов она состоит. Отлаживать монолит и искать в нём уязвимости проще, чем задумываться над тем, что будет, если в момент REST запроса один из сервисов откажет, или сеть упадёт.

1
lazarev_andrey_lab_3/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
venv/

View File

@ -0,0 +1,76 @@
# Лабораторная работа №2
## Описание проекта
Проект разворачивает 3 программы в отдельных контейнерах с использованием Docker Compose:
1. **author_service** - сервис, с CRUD операциями для авторов;
2. **publication_service** - сервис, с CRUD операциями для публикаций;
3. **nginx** - веб-сервер и прокси-сервер, является маршрутизатором.
Между первыми двумя сервисами имеется связь один(`Автор`) ко многим(`Публикация`).
## Струкутура проекта
### Проект состоит из:
- 2 папки(author_service, publication_service)
- Каждая папка содержит в себе файл с расширением `.py` с кодом программы;
- Кадлая папка сожержит в себе файл `Dockerfile` с инструкцией по созданию Docker образа.
- Файл `.gitignore` для исключения временных файлов директории `venv/`;
- Файл `docker-compose.yml` с конфигурацией Docker Compose;
- Файл `nginx.conf` конфигурации для веб-сервера NGINX с параметрами работы сервера;
- Файл `requirements.txt` с перечислением всех необходимых библиотек для запуска.
Комментарии в файлах.
## Запуск
1. Скачать и установить Docker и Docker Compose;
2. Перейти в директорию с файлом docker-compose.yml;
3. Открыть консоль и запустить сервисы командой
```bash
docker-compose up --build -d
```
4. Дождаться запуска всех сервисов
```bash
[+] Running 3/3
✔ Container lazarev_andrey_lab_2-generate-files-1 Started 0.5s
✔ Container lazarev_andrey_lab_2-first-1 Started 1.3s
✔ Container lazarev_andrey_lab_2-second-1 Started 2.0s
```
5. Остановка всех сервисов
Для завершения работы с сервисами необходимо выполнить команду:
```bash
docker-compose down
```
Дождаться завершения работы:
```bash
[+] Running 4/4
✔ Container lazarev_andrey_lab_2-second-1 Removed 0.0s
✔ Container lazarev_andrey_lab_2-first-1 Removed 0.0s
✔ Container lazarev_andrey_lab_2-generate-files-1 Removed 0.0s
✔ Network lazarev_andrey_lab_2_default Removed 0.4s
```
## Cписок команд
- Author_service
- `http://localhost:8000/author_service/author` - список авторов
- `http://localhost:8000/author_service/author/{id автора}` - конкретный автор
- `http://localhost:8000/author_service/author/full/{id автора}` - автор и полный список его публикаций
- `http://localhost:8000/author_service/author?name={имя}&second_name={фамилия}&age={возраст}` - добавление нового автора
- `http://localhost:8000/author_service/author/{id автора}?name={новое имя}` - изменение имени автора
- Publication_service
- `http://localhost:8000/publication_service/publication` - список публикаций
- `http://localhost:8000/publication_service/publication/{id публикации}` - конкретная публикация
- `http://localhost:8000/publication_service/publication/full/{id публикации}` - публикация и полная информация об авторе
- `http://localhost:8000/publication_service/publication?name={название}&public_year={год выпуска}&author_id={id автора}` - добавление новой публикации
- `http://localhost:8000/publication_service/publication/{id публикации}?name={новое название}` - изменение названия публикации
## Видеодемонстрация работоспособности
[Демонстрация работы сервисов](https://files.ulstu.ru/s/5D2i6gbLn6r2jsA)

View File

@ -0,0 +1,17 @@
# Использует базовый образ Python 3.9 на основе slim-версии
FROM python:3.10-slim
# Устанавливаю рабочую директорию внутри контейнера
WORKDIR /app
# Копирую файл requirements.txt в контейнер
COPY requirements.txt /app/
# Устанавливаю зависимости
RUN pip install --no-cache-dir -r requirements.txt
#аналогично копирую
COPY author_service/main.py .
# Задает команду для запуска контейнера
CMD ["python", "main.py"]

View File

@ -0,0 +1,110 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
import requests
#Инициализация веб-приложения
app = FastAPI(title="Author service")
#Строка подключения к публикациям
publication_url='http://publication_service:8009'
#Сущность автор с полями имя, фамилия, возраст.
class Author(BaseModel):
uuid_: UUID = Field(default_factory=uuid4)
name: str | None
second_name: str | None
age: int | None
#Заранее заполненный список авторов, в некоторых есть uuid они пригодятся при создании публикаций
data: list[Author] = [
Author(uuid_="92e78b56-0026-4561-b9f6-ba628110c900", name="Андрей", second_name="Лазарев", age=21),
Author(uuid_="3203b355-d844-4d5a-ad91-a9e0135cd9d9",name="Павел", second_name="Сорокин", age=20),
Author(name="Дарья", second_name="Балберова", age=21),
Author(name="Дмитрий", second_name="Курило", age=24),
Author(name="Александр", second_name="Дырночкин", age=24)
]
#Получить список всех авторов
@app.get("/author", tags=["Author"])
def all_authors():
return data
#Получить одного автора по uuid
@app.get("/author/{author_id}", tags=["Author"])
def get_author(author_id: UUID):
author = next((x for x in data if x.uuid_ == author_id), None)
if not author:
return HTTPException(status_code=404, detail="Автор не найден")
return author
#Получить одного автора по uuid с списком его публикаций
@app.get("/author/full/{author_id}")
def get_publications_by_author(author_id: UUID):
author = get_author(author_id)
if not author:
return HTTPException(status_code=404, detail="Автор не найден")
publications = requests.get(f"{publication_url}/publication/author/{author_id}")
if not publications:
return HTTPException(status_code=404, detail="Публикации не найдены")
result = author.model_dump()
result['publications'] = publications.json()
return result
#Добавление нового автора, все поля обязательные
@app.post("/author", tags=["Author"])
def add_author(name: str, second_name: str, age: int):
author = next((x for x in data if x.name == name and x.second_name == second_name and x.age == age), None)
if author:
return HTTPException(status_code=404, detail="Такой автор уже существует")
try:
data.append(Author(name=name, second_name=second_name, age=age))
return JSONResponse(content={"message": "Автор успешно добавлен"}, status_code=200)
except Exception as e:
return HTTPException(status_code=404, detail={"Автор не был добавлен с ошибкой": str(e)})
#Изменение автора по uuid
@app.put("/author/{author_id}", tags=["Author"])
def update_author(author_id: UUID, name: str = None , second_name: str = None, age: int = None):
author = get_author(author_id)
if author:
index = data.index(author)
if name:
data[index].name = name
if second_name:
data[index].second_name = second_name
if age:
data[index].age = age
return JSONResponse(content={"message": "Автор успешно изменен"}, status_code=200)
else:
return HTTPException(status_code=404, detail={"Автор не найден": {author}})
#Удаление автора по uuid
@app.delete("/author/{author_id}", tags=["Author"])
def delete_author(author_id: UUID):
author = get_author(author_id)
if author:
index = data.index(author)
del data[index]
return JSONResponse(content={"message": "Автор успешно удален"}, status_code=200)
else:
return HTTPException(status_code=404, detail={"Автор не найден": {author}})
#Запуск
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8008)

View File

@ -0,0 +1,26 @@
version: '3.8'
services:
author_service:
build:
context: . #Контекст сборки — текущая директория (корневая папка проекта).
dockerfile: ./author_service/Dockerfile # Путь до Dockerfile для сборки контейнера.
expose: # Указывает, какой порт будет открыт внутри контейнера.
- 8008
publication_service:
build:
context: .
dockerfile: ./publication_service/Dockerfile
expose:
- 8009
nginx: # Третий сервис, называемый "nginx".
image: nginx # Используется готовый образ NGINX из Docker Hub.
ports: # Публикует порты для доступа к NGINX.
- 8000:8000 # Проброс порта: внешний порт 8000 связан с внутренним портом 8000.
volumes: # Монтирует локальные файлы/директории в контейнер.
- ./nginx.conf:/etc/nginx/nginx.conf # Локальный файл nginx.conf будет монтирован в контейнер по пути /etc/nginx/nginx.conf.
depends_on: # Зависимости. NGINX будет запускаться после запуска указанных сервисов.
- author_service # NGINX зависит от запуска author_service.
- publication_service # NGINX зависит от запуска publication_service.

View File

@ -0,0 +1,27 @@
events {
worker_connections 1024;
}
http {
server {
listen 8000;
listen [::]:8000;
server_name localhost;
location /author_service/ {
proxy_pass http://author_service:8008/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Prefix $scheme;
}
location /publication_service/ {
proxy_pass http://publication_service:8009/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Prefix $scheme;
}
}
}

View File

@ -0,0 +1,12 @@
#описание в Dockerfile author_service
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY publication_service/main.py .
CMD ["python", "main.py"]

View File

@ -0,0 +1,113 @@
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
import requests
#Инициализация веб-приложения
app = FastAPI(title="Publication service")
#Строка подключения к авторам
author_url='http://author_service:8008'
#Сущность публикации с полями название, год публикации, ид автора.
class Publication(BaseModel):
uuid_: UUID = Field(default_factory=uuid4)
name: str | None
public_year: int | None
author_id: UUID | None
#Заранее заполненный список публикаций, в некоторых есть uuid они пригодятся при создании публикаций
data: list[Publication] = [
Publication(uuid_="92e78b56-0026-4561-b9f6-ba628110c901", name="книга 1", public_year=2024, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
Publication(name="книга 2", public_year=2022, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
Publication(name="книга 3", public_year=2003, author_id="3203b355-d844-4d5a-ad91-a9e0135cd9d9"),
Publication(name="книга 4", public_year=2020, author_id="92e78b56-0026-4561-b9f6-ba628110c900"),
Publication(name="книга 5", public_year=2019, author_id="3203b355-d844-4d5a-ad91-a9e0135cd9d9")
]
#Получить список всех публикаций
@app.get("/publication", tags=["Publication"])
def all_publications():
return data
#Получить одной публикации по uuid
@app.get("/publication/{publication_id}", tags=["Publication"])
def get_publication(publication_id: UUID):
publication = next((x for x in data if x.uuid_ == publication_id), None)
if not publication:
return HTTPException(status_code=404, detail="Публикация не найдена")
return publication
#Получить одной публикации по uuid с информацие об ее авторе
@app.get("/publication/full/{publication_id}")
def get_full_publication(publication_id: UUID):
publication = get_publication(publication_id)
if not publication:
return HTTPException(status_code=404, detail="Публикаций не найдена")
author = requests.get(f"{author_url}/author/{publication.author_id}")
if not author:
return HTTPException(status_code=404, detail="Автор не найден")
result = publication.model_dump()
result['author_info'] = author.json()
return result
#Добавление новой публикации, все поля обязательные
@app.post("/publication", tags=["Publication"])
def add_publication(name: str, public_year: int, author_id: UUID):
author = next((x for x in data if x.name == name and x.public_year == public_year and x.author_id == author_id), None)
if author:
return HTTPException(status_code=404, detail="Такая публикация уже существует")
try:
data.append(Publication(name=name, public_year=public_year, author_id=author_id))
return JSONResponse(content={"message": "Публикация успешно добавлена"}, status_code=200)
except Exception as e:
return HTTPException(status_code=404, detail={"Публикация не была добавлена с ошибкой": str(e)})
#Изменение публикации по uuid
@app.put("/publication/{publication_id}", tags=["Publication"])
def update_publication(publication_id: UUID, name: str = None, public_year: int = None, author_id: UUID = None):
publication = get_publication(publication_id)
if publication:
index = data.index(publication)
if name:
data[index].name = name
if public_year:
data[index].public_year = public_year
if author_id:
data[index].author_id = author_id
return JSONResponse(content={"message": "Публикация успешно изменена"}, status_code=200)
else:
return HTTPException(status_code=404, detail={"Публикация не найдена": {publication}})
#Удаление публикации по uuid
@app.delete("/publication/{publication_id}", tags=["Publication"])
def delete_publication(publication_id: UUID):
publication = get_publication(publication_id)
if publication:
index = data.index(publication)
del data[index]
return JSONResponse(content={"message": "Публикация успешно удалена"}, status_code=200)
else:
return HTTPException(status_code=404, detail={"Публикация не найдена": {publication}})
#Запуск
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8009)

View File

@ -0,0 +1,5 @@
fastapi==0.115.0
uvicorn==0.31.0
pydantic==2.9.2
pydantic_core==2.23.4
requests==2.32.3

5
rogashova_ekaterina_lab_2/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__/
*.pyc
*.pyo
*.log
.DS_Store

View File

@ -0,0 +1 @@
1 67 11 45

View File

@ -0,0 +1 @@
1 2 3 4 5

View File

@ -0,0 +1,17 @@
version: '3.8'
services:
findfile:
build:
context: ./worker-1
volumes:
- ./data:/var/data
- ./result:/var/result
findnumber:
build:
context: ./worker-2
volumes:
- ./result:/var/result
depends_on:
- findfile

Some files were not shown because too many files have changed in this diff Show More