Compare commits

..

1 Commits

Author SHA1 Message Date
f31120978d arutunyan_dmitry_lab_6 is ready 2024-01-17 22:32:28 +04:00
254 changed files with 182 additions and 6853 deletions

View File

@ -0,0 +1,106 @@
## Лабораторная работа 6. Вариант 4.
### Задание
Реализовать нахождение детерминанта квадратной матрицы.
- Создать алгоритм параллельного нахождения детерминанта квадратной матрицы,
- Предусмотреть задание потоков вручную, для работы параллельного алгоритма нахождения определителя как обычного.
### Как запустить
Для запуска программы необходимо с помощью командной строки в корневой директории файлов прокета прописать:
```
python main.py
```
Результат работы программы будет выведен в консоль.
### Используемые технологии
- Библиотека `numpy`, используемая для обработки массивов данных и вычислений.
- Библиотека `concurrent.futures`- высокоуровневый интерфейс для выполнения параллельных и асинхронных задач.
- `ThreadPoolExecutor` - класс пула потоков для выполнения задач в нескольких потоках. Он использует пул потоков, чтобы автоматически управлять созданием и выполнением потоков, что обеспечивает простой способ распараллеливания задач. Метод `submit()` позволяет отправлять задачи в пул потоков, и возвращает объект `Future`, который представляет результат выполнения задачи.
- Библиотека `time`, используемая для измерения времени работы программы.
- Библиотека `psutil`, используемая для отслеживания нагрузки на процессор и количества загруженной оперативной памяти.
### Описание работы
#### Распараллеливание задачи нахождения определителя
Воспользуемся правилом нахождения определителя с помощью миноров матрицы:
- Определитель матрицы равен сумме произведений элементов строки (столбца) на соответствующие алгебраические дополнения.
```
|a1, a2, a3| |b2, b3| |b1, b3| |b1, b2|
|b1, b2, b3| = (+)a1 x |c2, c3| + (-)a2 x |c1, c3| + (+)a3 x |c1, c2|
|c1, c2, c3|
```
Таким образом, мы можем выбрать 1ю строку матрицы и параллельно вычислить определитель каждого минора матрицы, умножив его на соответсвующий элемент.
#### Разработка программы
Для начала создадим функцию вычисления детерминанта минора матрицы с его умножением на соответсвующий элемент строки:
```python
def calculate_determinant(args):
matrix, i = args
multiplier = matrix[0][i]
if i % 2 != 0:
multiplier *= -1
matrix = np.delete(matrix, 0, axis=0)
submatrix = np.delete(matrix, i, axis=1)
return np.linalg.det(submatrix) * multiplier
```
Если элемент находится на нечётной позиции в строке, то, по правилу, знак множителя меняется на противоположный.
Теперь перейдём к алгоритму параллельного вычисления детерминанта матрицы. Создадим пул потоков с помощью класса `concurrent.futures.ThreadPoolExecutor`, в который будем передавать желаемое количество потоков:
```python
with concurrent.futures.ThreadPoolExecutor(max_workers=n) as executor:
results = []
start_time = time.time()
for i in range(n):
results.append(executor.submit(calculate_determinant, args=(matrix, i)))
result = np.sum([res.result() for res in results])
end_time = time.time()
```
В пул потоков в качестве аргументов мы передаём последовательно матрицу и номер столбца элемента-множителя, а в качестве их обработчика указываем ранее созданный метод `calculate_determinant`. Массив определителей миноров, умноженных на соответсвующие множители суммируется методом `sum` и записывается в результат.
> **Note**
>
> Поскольку определение детерминанта матрицы распараллеливается по 1му порядку миноров, задавать значение кол-ва потоков больше числа элементов строки-множителя (1й строки матрицы) не имеет значения. По сути, самым оптимальным решением будет являться состояние, когда каждый поток ищет определитель определённого минора, составленного по определённому элементу строки матрицы.
Создадим тестовый метод и проверим работу калькулятора вычисления определителя на грамотность:
```python
mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
return parallel_determinant(mx, parallel)
```
Результат вычислений: `0`
Алгоритм работает верно.
#### Замеры параметров
Бенчмарки в данной лабораторной работе задаются аналогично предыдущей. Прогоним все бенчмарки и сравним измеряемые показатели при максимальной многопоточности (кол-во потоков = кол-ву эл-тов 1й строки матрицы) и монопоточности.
Результаты (параллельный - слева, обычный - справа):
```
50 * 50
_________________________________________________________________
Время выполнения: 0.0129.. сек. | Время выполнения: 0.0010.. сек.
Загрузка ЦП: 5.4% | Загрузка ЦП: 4.8%
Использование ОЗУ: 71.6% | Использование ОЗУ: 71.7%
75 * 75
_________________________________________________________________
Время выполнения: 0.0220.. сек. | Время выполнения: 0.0.. сек.
Загрузка ЦП: 5.7% | Загрузка ЦП: 1.2%
Использование ОЗУ: 71.1% | Использование ОЗУ: 71.0%
125 * 125
_________________________________________________________________
Время выполнения: 0.5048.. сек. | Время выполнения: 0.0070.. сек.
Загрузка ЦП: 5.6% | Загрузка ЦП: 2.5%
Использование ОЗУ: 71.2% | Использование ОЗУ: 71.7%
```
### Вывод
По результатам замеров видно, что при любых размерностях матрицы, монопоточное вычисление определителя происходит эффективнее как по времени, так и по ресурс-затратности. По загруженности ЦП можно убедиться, что многопоточное определение детерминанта матрицы работает корректно, тк загрузка процессора при использовании нескольких потоков всегда выше, чем при использовании монопоточности.
Таким образом, применение парралельных вычислений при решении данной задачи показали себя не учшим образом. Связанно это может быть с оптимальностью алгоритма вычисления определителя функции `det` библиотеки `numpy`, с недостаточной оптимальностью алгоритма распараллеливания вычисления (находится минор только последующего порядка, а не рассчитывается до 1го в связи с рекурсивностью выбранного метода определения детерминанта) или с недостаточно большой размерностью матрицы (оперативной памяти данной машины хватает на вычисления определителя матрицы, максимальной размерностью 125х125)
### Видео
https://youtu.be/ayDhflcf7PM

View File

@ -0,0 +1,76 @@
import time
import numpy as np
import concurrent.futures
import psutil
def calculate_determinant(args):
matrix, i = args
multiplier = matrix[0][i]
if i % 2 != 0:
multiplier *= -1
matrix = np.delete(matrix, 0, axis=0)
submatrix = np.delete(matrix, i, axis=1)
return np.linalg.det(submatrix) * multiplier
def parallel_determinant(matrix, parallel):
memory = psutil.virtual_memory()
n = matrix.shape[0]
if parallel:
with concurrent.futures.ThreadPoolExecutor(max_workers=n) as executor:
results = []
start_time = time.time()
for i in range(n):
results.append(executor.submit(calculate_determinant, args=(matrix, i)))
result = np.sum([res.result() for res in results])
end_time = time.time()
else:
start_time = time.time()
result = np.linalg.det(matrix)
end_time = time.time()
execution_time = end_time - start_time
return result, execution_time, psutil.cpu_percent(interval=1), memory.percent
def test(parallel):
mx = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench50x50(parallel):
mx = np.random.randint(0, 100, size=(50, 50))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench75x75(parallel):
mx = np.random.randint(0, 100, size=(75, 75))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
def bench125x125(parallel):
mx = np.random.randint(0, 100, size=(125, 125))
result = parallel_determinant(mx, parallel)
print(f"Определитель матрицы: {result[0]}")
print(f"Время выполнения: {result[1]} сек.")
print(f"Загрузка ЦП: {result[2]} %")
print(f"Использование ОЗУ: {result[3]} %")
if __name__ == '__main__':
# test(parallel=False)
# bench50x50(parallel=False)
# bench75x75(parallel=False)
bench125x125(parallel=False)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

View File

@ -1,72 +0,0 @@
# Лабораторная работа 1.
### Задание
**Цель**: изучение современных технологий контейнеризации.
**Задачи**:
- Установить средство контейнеризации docker.
- Изучить применение и принципы docker.
- Изучить утилиту docker-compose и структуру файла docker-compose.yml.
- Развернуть не менее 3х различных сервисов при помощи docker-compose.
### Как запустить лабораторную работу
В директории с файлом характеристик docker-compose.yaml выполнить команду:
```
docker-compose -f docker-compose.yaml up
```
### Разворачивание сервисов
Выбранные сервисы:
- mediawiki
- wordpress
- gitea
- redmine
#### Разворачивание mediawiki
Конфигурации сервиса в `docker-compose.yaml`:
```yaml
mediawiki: # название сервиса
image: mediawiki # образ, который скачиывается с DockerHub
volumes:
- ./mediawiki_data:/var/data # создание volumes, который будет использоваться для хранения данных MediaWiki
ports:
- 8081:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 81 внутри контейнера.
```
#### Разворачивание wordpress
```yaml
wordpress: # название сервиса
image: wordpress # образ, который скачиывается с DockerHub
volumes:
- ./wordpress_data:/var/data # создание volumes, который будет использоваться для хранения данных wordpress
ports:
- 8082:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 82 внутри контейнера.
```
#### Разворачивание gitea
```yaml
gitea: # название сервиса
image: gitea/gitea # образ, который скачиывается с DockerHub
ports:
- 8083:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 83 внутри контейнера.
volumes:
- ./gitea_data:/var/data # создание volumes, который будет использоваться для хранения данных gitea
```
### Запуск сервисов
![Сборка docker-compose](LaunchingServices.jpg)
![Разворачивание mediawiki](mediawiki.jpg)
![Разворачивание wordpress](wordpress.jpg)
![Разворачивание gitea](gitea.jpg)
### Видео
https://disk.yandex.ru/i/-5BiuM51iqj83A

View File

@ -1,20 +0,0 @@
version: '3'
services:
mediawiki: # название сервиса
image: mediawiki # образ, который скачиывается с DockerHub
volumes:
- ./mediawiki_data:/var/data # создание volumes, который будет использоваться для хранения данных MediaWiki
ports:
- 8081:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 81 внутри контейнера.
wordpress: # название сервиса
image: wordpress # образ, который скачиывается с DockerHub
volumes:
- ./wordpress_data:/var/data # создание volumes, который будет использоваться для хранения данных wordpress
ports:
- 8082:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 82 внутри контейнера.
gitea: # название сервиса
image: gitea/gitea # образ, который скачиывается с DockerHub
ports:
- 8083:80 # открывает порт 8080 на хостовой машине, который будет проксирован на порт 83 внутри контейнера.
volumes:
- ./gitea_data:/var/data # создание volumes, который будет использоваться для хранения данных gitea

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

View File

@ -1,194 +0,0 @@
# Лабораторная работа 2.
### Задание
**Цель**: изучение техники создания простого распределённого приложения.
**Задачи**:
- Разработать два приложения такие, что результат первого является исходными данными для второго.
- Изучить файлы сборки образов docker и разработать их для созданных приложений.
- Собрать файл docker-compose.yml для запуска приложений.
- Разобраться с монтированием каталогов из хост-системы.
**Вариант**:
- Ищет в каталоге /var/data файл с самым коротким названием и перекладывает его в /var/result/data.txt.
- Сохраняет произведение первого и последнего числа из файла /var/data/data.txt в /var/result/result.txt.
### Как запустить лабораторную работу
В директории с файлом характеристик docker-compose.yaml выполнить команду:
```
docker-compose -f docker-compose.yaml up
```
### Описание лабораторной работы
#### Входные данный
В качестве входных данных представлены следующие файлы с соответствующим содержанием:
```
deadlock - file1: 9 5 0 1
diamond - file2: 3 5 8
himera - file3: 4 6 6
kjjuriositi - file4: 3 5 3
nitric-oxide - file5: 3 1 7
V3001TH - file6: 7 2 6
za - file7: 2 4 4
```
#### Первое приложение
Первое приложение выполняет задачу: Ищет в каталоге /var/data файл с самым коротким названием и перекладывает его в /var/result/data.txt.
Для этого напишем следующий код:
```java
File dataDir = new File("/var/data");
File resultDir = new File("/var/result");
// Получаем все файлы в каталоге /var/data
File[] files = dataDir.listFiles();
if (files != null && files.length > 0) {
// Ищем файл с самым коротким названием
File shortestFileName = files[0];
for (File file : files) {
if (file.getName().length() < shortestFileName.getName().length()) {
shortestFileName = file;
}
}
// Перекладываем файл в /var/result/data.txt
File destination = new File(resultDir, "data.txt");
try {
Files.move(shortestFileName.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("Файл успешно перемещен.");
} catch (IOException e) {
System.out.println("Ошибка при перемещении файла: " + e.getMessage());
}
} else {
System.out.println("В каталоге /var/data нет файлов.");
}
```
Также создадим *Dockerfile*, который используется для создания образа контейнера данного приложения.
```dockerfile
# Используем образ Java для компиляции и запуска кода
FROM openjdk:11
# Создание директорий для файлов
RUN ["mkdir", "/var/data"]
RUN ["mkdir", "/var/result"]
# Устанавливаем рабочую директорию внутри контейнера
WORKDIR /app
# Копируем исходный код в контейнер
COPY src/Main.java .
# Компилируем исходный код
RUN javac Main.java
# Устанавливаем команду запуска приложения
CMD ["java", "Main"]
```
#### Второе приложение
Второе приложение выполняет задачу: Сохраняет произведение первого и последнего числа из файла /var/data/data.txt в /var/result/result.txt.
Для этого напишем следующий код:
```java
String inputFile = "/var/result/data.txt";
String outputFile = "/var/result/result.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
String[] numbers = line.split("\\s+");
if (numbers.length > 0) {
int firstNumber = Integer.parseInt(numbers[0]);
int lastNumber = Integer.parseInt(numbers[numbers.length - 1]);
int result = firstNumber * lastNumber;
writer.write(String.valueOf(result));
writer.newLine();
}
}
} catch (IOException e) {
e.printStackTrace();
}
```
Также создадим *Dockerfile*, который используется для создания образа контейнера данного приложения.
```dockerfile
# Используем образ Java для компиляции и запуска кода
FROM openjdk:11
# Создание директорий для файлов
RUN ["mkdir", "/var/data"]
RUN ["mkdir", "/var/result"]
# Устанавливаем рабочую директорию внутри контейнера
WORKDIR /app
# Копируем исходный код в контейнер
COPY src/Main.java .
# Компилируем исходный код
RUN javac Main.java
# Устанавливаем команду запуска приложения
CMD ["java", "Main"]
```
### Разворачивание сервисов
Создадим файл docker-compose.yml, который определяет настройки для запуска нескольких сервисов в Docker с использованием Docker Compose.
Первый блок будет определять первый сервис worker-1. Он будет создан на основе контекста сборки /worker-1 и файла Dockerfile. Затем, указанные тома (volumes) будут примонтированы в контейнере. В данном случае, директория .\data на хосте будет примонтирована в /var/data внутри контейнера, а директория .\result на хосте будет примонтирована в /var/result внутри контейнера.
```dockerfile
worker-1:
build:
context: /worker-1
dockerfile: Dockerfile
volumes:
- .\data:/var/data
- .\result:/var/result
```
Второй блок будет определять второй сервис worker-2. Он зависит от сервиса worker-1, поэтому worker-1 будет запущен перед worker-2. Затем, он также будет создан на основе контекста сборки /worker-2 и файла Dockerfile. Аналогично, указанные тома будут примонтированы в контейнере.
```dockerfile
worker-2:
depends_on:
- worker-1
build:
context: /worker-2
dockerfile: Dockerfile
volumes:
- .\result:/var/data
- .\result:/var/result
```
### Запуск
![Сборка docker-compose](LaunchingServices.jpg)
![Первое приложение](FirstApp.jpg)
![Второе приложение](SecondApp.jpg)
Файлы директории data:
```
deadlock - file1: 9 5 0 1
diamond - file2: 3 5 8
himera - file3: 4 6 6
kjjuriositi - file4: 3 5 3
nitric-oxide - file5: 3 1 7
V3001TH - file6: 7 2 6
za - file7: 2 4 4
```
Файлы директории result:
```
data: 2 4 4
result: 8
```
### Видео
https://disk.yandex.ru/i/dFIB-vOqlAip9g

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1 +0,0 @@
7 2 6

View File

@ -1 +0,0 @@
9 5 0 1

View File

@ -1 +0,0 @@
3 5 8

View File

@ -1,19 +0,0 @@
version: '3'
services:
worker-1:
build:
context: /worker-1
dockerfile: Dockerfile
volumes:
- .\data:/var/data
- .\result:/var/result
worker-2:
depends_on:
- worker-1
build:
context: /worker-2
dockerfile: Dockerfile
volumes:
- .\result:/var/data
- .\result:/var/result

View File

@ -1 +0,0 @@
4 6 6

View File

@ -1 +0,0 @@
24

View File

@ -1,29 +0,0 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -1,13 +0,0 @@
# Используем образ Java для компиляции и запуска кода
FROM openjdk:11
# Создание директорий для файлов
RUN ["mkdir", "/var/data"]
RUN ["mkdir", "/var/result"]
# Устанавливаем рабочую директорию внутри контейнера
WORKDIR /app
# Копируем исходный код в контейнер
COPY src/Main.java .
# Компилируем исходный код
RUN javac Main.java
# Устанавливаем команду запуска приложения
CMD ["java", "Main"]

View File

@ -1,32 +0,0 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class Main {
public static void main(String[] args) {
File dataDir = new File("/var/data");
File resultDir = new File("/var/result");
// Получаем все файлы в каталоге
File[] files = dataDir.listFiles();
if (files != null && files.length > 0) {
// Ищем файл с самым коротким названием
File shortestFileName = files[0];
for (File file : files) {
if (file.getName().length() < shortestFileName.getName().length()) {
shortestFileName = file;
}
}
// Перекладываем файл
File destination = new File(resultDir, "data.txt");
try {
Files.move(shortestFileName.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.out.println("Приложение 1: Файл успешно перемещен.");
} catch (IOException e) {
System.out.println("Приложение 1: Ошибка при перемещении файла: " + e.getMessage());
}
} else {
System.out.println("Приложение 1: В каталоге /var/data нет файлов.");
}
}
}

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,29 +0,0 @@
### IntelliJ IDEA ###
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -1,13 +0,0 @@
# Используем образ Java для компиляции и запуска кода
FROM openjdk:11
# Создание директорий для файлов
RUN ["mkdir", "/var/data"]
RUN ["mkdir", "/var/result"]
# Устанавливаем рабочую директорию внутри контейнера
WORKDIR /app
# Копируем исходный код в контейнер
COPY src/Main.java .
# Компилируем исходный код
RUN javac Main.java
# Устанавливаем команду запуска приложения
CMD ["java", "Main"]

View File

@ -1,32 +0,0 @@
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
String inputFile = "/var/result/data.txt";
String outputFile = "/var/result/result.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
String[] numbers = line.split("\\s+");
if (numbers.length > 0) {
int firstNumber = Integer.parseInt(numbers[0]);
int lastNumber = Integer.parseInt(numbers[numbers.length - 1]);
int result = firstNumber * lastNumber;
System.out.println("Приложение 2: Результат умножения первого и последнего числа: " + result);
writer.write(String.valueOf(result));
writer.newLine();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,280 +0,0 @@
# Лабораторная работа 3.
### Задание
**Цель**: изучение современных технологий контейнеризации.
**Задачи**:
- Установить средство контейнеризации docker.
- Изучить применение и принципы docker.
- Изучить утилиту docker-compose и структуру файла docker-compose.yml.
- Развернуть не менее 3х различных сервисов при помощи docker-compose.
- Оформить отчёт в формате Markdown и создать Pull Request в git-репозитории.
### Как запустить лабораторную работу
В директории с файлом характеристик docker-compose.yaml выполнить команду:
```
docker-compose -f docker-compose.yaml up
```
### Описание лабораторной работы
#### Создание базы данных
Каждый сервис реализует CRUD-операции, поэтому были выбраны следующие сущности, соответствующие теме диплома: упражнение и тренировка. Эти сущности связаны отношением один ко многим. Созданные таблицы базы данных:
```sql
-- Создание таблицы тренировок
CREATE TABLE t_training (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL
);
-- Создание таблицы упражнений
CREATE TABLE t_exercise (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL,
id_training INTEGER,
FOREIGN KEY (id_training) REFERENCES t_training(id)
);
```
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
```dockerfile
#database
postgresql:
#configuration
image: postgres:latest
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: admin
POSTGRES_USER: admin
POSTGRES_DB: traininarium
volumes:
- ./database.sql:/docker-entrypoint-initdb.d/database.sql
restart: always
networks:
- mynetwork
```
- `image: postgres:latest` указывает, что мы хотим использовать последнюю версию образа `PostgreSQL`.
- `ports: - 5432:5432` пробрасывает порт 5432 контейнера (стандартный порт PostgreSQL) на порт 5432 хоста, чтобы можно было подключаться к базе данных с внешнего устройства.
- `environment` определяет переменные окружения, которые будут использоваться контейнером. В данном случае, устанавливаются значения для переменных `POSTGRES_PASSWORD`, `POSTGRES_USER` и `POSTGRES_DB`.
- `volumes` указывает путь к файлу `database.sql` в текущей директории, который будет использоваться для инициализации базы данных при запуске контейнера.
- `restart: always` гарантирует, что контейнер будет перезапущен автоматически, если он остановится или перезагрузится.
- `networks` определяет сеть, к которой будет присоединен контейнер. В данном случае, контейнер будет присоединен к сети с именем `mynetwork`.
В итоге будет создан контейнер с базой данных `PostgreSQL`, настроенной с указанными переменными окружения, проброшенным портом и файлом `database.sql` для инициализации базы данных.
#### Создание микросервиса *Упражнения*
После реализации необходимых элементов микросервиса, таких как: controller, model, modelDTO, repository и service, создадим конфигурационный файл Dockerfile, в котором пропишем следующее:
```dockerfile
FROM openjdk:17-jdk
WORKDIR /app
COPY ./exercise-app/build/libs/exercise-app-0.0.1-SNAPSHOT.jar /app/exercise-app-0.0.1-SNAPSHOT.jar
EXPOSE 8081
CMD ["java", "-jar", "exercise-app-0.0.1-SNAPSHOT.jar"]
```
В данном файле мы указываем базовый образ, который будет использован для создания контейнера (*OpenJDK* версии 17 с установленным *JDK*); устанавливаем рабочую директорию внутри контейнера, где будут размещены файлы приложения; указываем, что приложение внутри контейнера будет слушать на порту 8081 и определяем команду, которая будет выполнена при запуске контейнера, а именно запуск приложения *Java* из файла `exercise-app-0.0.1-SNAPSHOT.jar` внутри контейнера.
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
```dockerfile
exercise-service:
build:
context: .
dockerfile: ./exercise-app/Dockerfile
ports:
- 8081:8081
environment:
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
DATASOURCE_USERNAME: admin
DATASOURCE_PASSWORD: admin
restart: always
#wait build database
depends_on:
- postgresql
networks:
- mynetwork
```
- `exercise-service` является именем сервиса, который будет создан и запущен в контейнере.
- `context: .` указывает, что контекстом для сборки Docker-образа является текущая директория.
- `dockerfile: ./exercise-app/Dockerfile` указывает путь к `Dockerfile`, который будет использоваться для сборки образа.
- `ports` определяет проброс портов между хостом и контейнером.
`8081:8081` указывает, что порт 8081 на хосте будет проброшен на порт 8081 внутри контейнера.
- `environment` определяет переменные окружения, которые будут доступны внутри контейнера.
`DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium` определяет URL для подключения к базе данных `PostgreSQL`.
`DATASOURCE_USERNAME: admin` определяет имя пользователя для подключения к базе данных.
`DATASOURCE_PASSWORD: admin` определяет пароль для подключения к базе данных.
- `restart: always` указывает, что контейнер будет автоматически перезапущен в случае его остановки.
depends_on определяет зависимость данного сервиса от другого сервиса.
- `postgresql` указывает, что данный сервис зависит от сервиса с именем `postgresql`.
- `mynetwork` указывает, что контейнер будет присоединен к сети с именем `mynetwork`.
#### Создание микросервиса *Тренировки*
Аналогично реализуем controller, model, modelDTO, repository и service для микросервиса *Тренировки*, но дополняя кодом отправки запроса к сервису *Упражнений*. Пример запроса к сервису *Упражнений*:
```java
public List<TrainingDto> findAllTraining() throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
List<TrainingDto> trainingDtos = new ArrayList<>();
for (Training training : trainingRepository.findAll()) {
TrainingDto trainingDto = new TrainingDto();
trainingDto.setId(training.getId());
trainingDto.setName(training.getName());
trainingDto.setDescription(training.getDescription());
ResponseEntity<String> response = restTemplate.exchange(
"http://" + exercise_service_host + "/exercise/training/" + trainingDto.getId(), HttpMethod.GET, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
List<ExerciseDto> exerciseDtos;
try {
exerciseDtos = objectMapper.readValue(responseBody, ArrayList.class);
trainingDto.setExercises(exerciseDtos);
} catch (JsonProcessingException e) {
throw new Exception("Не удалось десериализовать тело запроса в объект");
}
} else {
throw new Exception("Ошибка получения объекта");
}
trainingDtos.add(trainingDto);
}
return trainingDtos;
}
```
Теперь создадим конфигурационный файл Dockerfile, в котором пропишем следующее:
```dockerfile
FROM openjdk:17-jdk
WORKDIR /app
COPY ./training-app/build/libs/training-app-0.0.1-SNAPSHOT.jar /app/training-app-0.0.1-SNAPSHOT.jar
EXPOSE 8082
CMD ["java", "-jar", "training-app-0.0.1-SNAPSHOT.jar"]
```
В данном файле мы указываем базовый образ, который будет использован для создания контейнера (*OpenJDK* версии 17 с установленным *JDK*); устанавливаем рабочую директорию внутри контейнера, где будут размещены файлы приложения; указываем, что приложение внутри контейнера будет слушать на порту 8082; копируем файл `training-app-0.0.1-SNAPSHOT.jar` из локальной директории `./training-app/build/libs/` внутрь контейнера в папку `/app` и определяем команду, которая будет выполнена при запуске контейнера, а именно запуск приложения Java из файла `training-app-0.0.1-SNAPSHOT.jar` внутри контейнера.
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
```dockerfile
training-service:
build:
context: .
dockerfile: ./training-app/Dockerfile
ports:
- 8082:8082
environment:
EXERCISE_SERVICE_HOST: exercise-service:8081
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
DATASOURCE_USERNAME: admin
DATASOURCE_PASSWORD: admin
restart: always
#wait build database
depends_on:
- postgresql
networks:
- mynetwork
```
- `training-service` является именем сервиса, который будет создан и запущен в контейнере.
- `context: .` указывает, что контекстом для сборки Docker-образа является текущая директория.
- `dockerfile: ./training-app/Dockerfile` указывает путь к `Dockerfile`, который будет использоваться для сборки образа.
- `8082:8082` указывает, что порт 8082 на хосте будет проброшен на порт 8082 внутри контейнера.
- `environment` определяет переменные окружения, которые будут доступны внутри контейнера.
`EXERCISE_SERVICE_HOST: exercise-service:8081` определяет хост и порт для подключения к сервису `exercise-service`.
`DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium` определяет URL для подключения к базе данных `PostgreSQL`.
`DATASOURCE_USERNAME: admin` определяет имя пользователя для подключения к базе данных.
`DATASOURCE_PASSWORD: admin` определяет пароль для подключения к базе данных.
- `restart: always` указывает, что контейнер будет автоматически перезапущен в случае его остановки.
- `depends_on` определяет зависимость данного сервиса от другого сервиса, данный сервис зависит от сервиса с именем `postgresql`.
- `mynetwork` указывает, что контейнер будет присоединен к сети с именем `mynetwork`.
#### Реализация gateway при помощи *nginx*
Для того, чтобы использовать *Nginx* для обработки HTTP-запросов и маршрутизации их к соответствующим сервисам, создадим файл конфигурации *Nginx*:
```
events {
worker_connections 1024;
}
http {
upstream exercise-service {
server exercise-service:8081;
}
upstream training-service {
server training-service:8082;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
location /exercise-service/ {
proxy_pass http://exercise-service/;
}
location /training-service/ {
proxy_pass http://training-service/;
}
}
}
```
- `events` определяет настройки событий для сервера *Nginx*.
- `worker_connections 1024` указывает максимальное количество одновременных соединений, которые могут быть обработаны сервером.
- `http` определяет настройки HTTP-сервера *Nginx*.
- `upstream` определяет группу серверов, которые могут обрабатывать запросы.
`exercise-service` определяет группу серверов с именем `exercise-service`, в которой находится только один сервер `exercise-service:8081`.
`training-service` определяет группу серверов с именем `training-service`, в которой находится только один сервер `training-service:8082`.
- `server` определяет настройки для конкретного виртуального сервера.
- `listen 80` указывает на порт, на котором сервер будет слушать входящие HTTP-запросы.
- `listen [::]:80` указывает на IPv6-адрес и порт, на котором сервер будет слушать входящие HTTP-запросы.
- `server_name localhost` указывает имя сервера.
- `location /exercise-service/` определяет местоположение для обработки запросов, которые начинаются с `/exercise-service/`.
`proxy_pass http://exercise-service/` указывает, что все запросы, начинающиеся с `/exercise-service/`, должны быть перенаправлены на группу серверов `exercise-service`.
- `location /training-service/` определяет местоположение для обработки запросов, которые начинаются с `/training-service/`.
`proxy_pass http://training-service/` указывает, что все запросы, начинающиеся с `/training-service/`, должны быть перенаправлены на группу серверов `training-service`.
Таким образом, при запуске сервера *Nginx* с использованием этого конфигурационного файла, сервер будет слушать входящие HTTP-запросы на порту 80 и маршрутизировать запросы, начинающиеся с `/exercise-service/`, на группу серверов `exercise-service`, а запросы, начинающиеся с `/training-service/`, на группу серверов `training-service`.
Далее в файле `docker-compose.yaml` создадим соответствующий сервис:
```dockerfile
nginx:
#configuration
image: nginx:latest
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
restart: always
depends_on:
- training-service
- exercise-service
networks:
- mynetwork
```
- `nginx` - это название сервиса, который будет запущен в контейнере.
- `image: nginx:latest` указывает на использование последней версии образа *Nginx* из *Docker Hub*.
- `80:80` пробрасывает порт 80 контейнера на порт 80 хостовой машины.
- `volumes` определяет привязку тома между контейнером и хостовой машиной.
- `./nginx.conf:/etc/nginx/nginx.conf` привязывает файл `nginx.conf` из текущего рабочего каталога хостовой машины к файлу `nginx.conf` внутри контейнера *Nginx*. Это позволяет настроить *Nginx* с помощью внешнего файла конфигурации.
- `restart: always` указывает, что контейнер должен быть автоматически перезапущен при его остановке или падении.
- `depends_on` указывает на зависимость этого сервиса от других сервисов.
- `training-service` указывает, что контейнер Nginx должен быть запущен после контейнера `training-service`.
- `exercise-service` указывает, что контейнер Nginx должен быть запущен после контейнера `exercise-service`.
- `networks` определяет сети, к которым будет присоединен контейнер.
- `mynetwork` добавляет контейнер в сеть с именем `mynetwork`.
### Видео
https://disk.yandex.ru/i/OYPw_Tzl0QrzIw

View File

@ -1,14 +0,0 @@
-- Создание таблицы тренировок
CREATE TABLE t_training (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL
);
-- Создание таблицы упражнений
CREATE TABLE t_exercise (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL,
id_training INTEGER,
FOREIGN KEY (id_training) REFERENCES t_training(id)
);

View File

@ -1,73 +0,0 @@
version: '3'
networks:
mynetwork:
driver: bridge
#all necessary containers(services)
services:
#database
postgresql:
#configuration
image: postgres:latest
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: admin
POSTGRES_USER: admin
POSTGRES_DB: traininarium
volumes:
- ./database.sql:/docker-entrypoint-initdb.d/database.sql
restart: always
networks:
- mynetwork
exercise-service:
build:
context: .
dockerfile: ./exercise-app/Dockerfile
ports:
- 8081:8081
environment:
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
DATASOURCE_USERNAME: admin
DATASOURCE_PASSWORD: admin
restart: always
#wait build database
depends_on:
- postgresql
networks:
- mynetwork
training-service:
build:
context: .
dockerfile: ./training-app/Dockerfile
ports:
- 8082:8082
environment:
EXERCISE_SERVICE_HOST: exercise-service:8081
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
DATASOURCE_USERNAME: admin
DATASOURCE_PASSWORD: admin
restart: always
#wait build database
depends_on:
- postgresql
networks:
- mynetwork
nginx:
#configuration
image: nginx:latest
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
restart: always
depends_on:
- training-service
- exercise-service
networks:
- mynetwork

View File

@ -1,37 +0,0 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -1,7 +0,0 @@
FROM openjdk:17-jdk
WORKDIR /app
COPY ./exercise-app/build/libs/exercise-app-0.0.1-SNAPSHOT.jar /app/exercise-app-0.0.1-SNAPSHOT.jar
EXPOSE 8081
CMD ["java", "-jar", "exercise-app-0.0.1-SNAPSHOT.jar"]

View File

@ -1,42 +0,0 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'com.services'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.3'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1 +0,0 @@
rootProject.name = 'exercise-app'

View File

@ -1,13 +0,0 @@
package com.services.exerciseapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ExerciseAppApplication {
public static void main(String[] args) {
SpringApplication.run(ExerciseAppApplication.class, args);
}
}

View File

@ -1,49 +0,0 @@
package com.services.exerciseapp.controller;
import com.services.exerciseapp.model.Exercise;
import com.services.exerciseapp.modelDto.ExerciseDto;
import com.services.exerciseapp.service.ExerciseService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/exercise")
public class ExerciseController {
private final ExerciseService exerciseService;
public ExerciseController(ExerciseService exerciseService) {
this.exerciseService = exerciseService;
}
@GetMapping("/")
public List<Exercise> getExercises(){
return exerciseService.findAllExercise();
}
@GetMapping("/{id}")
public Exercise getExercise(@PathVariable int id){
return exerciseService.findById(id);
}
@GetMapping("/training/{id}")
public List<Exercise> getTrainingExercises(@PathVariable int id){
return exerciseService.findTrainingExercises(id);
}
@PostMapping("/")
public Exercise createExercise(@RequestBody ExerciseDto exerciseDto){
return exerciseService.addExercise(exerciseDto);
}
@PutMapping("/")
public Exercise updateExercise(@RequestBody ExerciseDto exerciseDto){
return exerciseService.updateExercise(exerciseDto);
}
@DeleteMapping("/{id}")
public void deleteExercise(@PathVariable int id){
exerciseService.deleteExercise(id);
}
}

View File

@ -1,50 +0,0 @@
package com.services.exerciseapp.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.experimental.Accessors;
@Entity
@Table(name = "t_exercise")
@Data
@Accessors(chain = true)
public class Exercise {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
private Integer id_training;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getId_training() {
return id_training;
}
public void setId_training(Integer id_training) {
this.id_training = id_training;
}
}

View File

@ -1,43 +0,0 @@
package com.services.exerciseapp.modelDto;
import jakarta.annotation.Nullable;
public class ExerciseDto {
private int id;
private String name;
private String description;
@Nullable
private Integer id_training;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getId_training() {
return id_training;
}
public void setId_training(Integer id_training) {
this.id_training = id_training;
}
}

View File

@ -1,7 +0,0 @@
package com.services.exerciseapp.repository;
import com.services.exerciseapp.model.Exercise;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ExerciseRepository extends JpaRepository<Exercise, Integer> {
}

View File

@ -1,57 +0,0 @@
package com.services.exerciseapp.service;
import com.services.exerciseapp.model.Exercise;
import com.services.exerciseapp.modelDto.ExerciseDto;
import com.services.exerciseapp.repository.ExerciseRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ExerciseService {
private final ExerciseRepository exerciseRepository;
public ExerciseService(ExerciseRepository exerciseRepository) {
this.exerciseRepository = exerciseRepository;
}
public List<Exercise> findAllExercise(){
return exerciseRepository.findAll();
}
public Exercise findById(int id){
return exerciseRepository.findById(id).orElseThrow();
}
public List<Exercise> findTrainingExercises(int training_id){
return exerciseRepository.findAll().stream()
.filter(exercise -> exercise.getId_training() != null)
.filter(exercise -> exercise.getId_training() == training_id)
.toList();
}
public Exercise addExercise(ExerciseDto exerciseDto) {
Exercise exercise = new Exercise();
exercise.setName(exerciseDto.getName());
exercise.setDescription(exerciseDto.getDescription());
if (exerciseDto.getId_training() != null) {
exercise.setId_training(exerciseDto.getId_training());
}
return exerciseRepository.save(exercise);
}
public Exercise updateExercise(ExerciseDto exerciseDto){
Exercise exercise = exerciseRepository.findById(exerciseDto.getId()).orElseThrow();
exercise.setName(exerciseDto.getName());
exercise.setDescription(exerciseDto.getDescription());
if (exerciseDto.getId_training() != null) {
exercise.setId_training(exerciseDto.getId_training());
}
return exerciseRepository.save(exercise);
}
public void deleteExercise(int id){
exerciseRepository.delete(exerciseRepository.findById(id).orElseThrow());
}
}

View File

@ -1,7 +0,0 @@
server:
port: "8081"
spring:
datasource:
url: ${DATASOURCE_URL:jdbc:postgresql://localhost:5432/traininarium}
username: ${DATASOURCE_USERNAME:admin}
password: ${DATASOURCE_PASSWORD:admin}

View File

@ -1,13 +0,0 @@
package com.services.exerciseapp;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ExerciseAppApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -1,28 +0,0 @@
events {
worker_connections 1024;
}
http {
upstream exercise-service {
server exercise-service:8081;
}
upstream training-service {
server training-service:8082;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
location /exercise-service/ {
proxy_pass http://exercise-service/;
}
location /training-service/ {
proxy_pass http://training-service/;
}
}
}

View File

@ -1,37 +0,0 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -1,7 +0,0 @@
FROM openjdk:17-jdk
WORKDIR /app
COPY ./training-app/build/libs/training-app-0.0.1-SNAPSHOT.jar /app/training-app-0.0.1-SNAPSHOT.jar
EXPOSE 8082
CMD ["java", "-jar", "training-app-0.0.1-SNAPSHOT.jar"]

View File

@ -1,42 +0,0 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'com.services'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.3'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1 +0,0 @@
rootProject.name = 'training-app'

View File

@ -1,13 +0,0 @@
package com.services.trainingapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TrainingAppApplication {
public static void main(String[] args) {
SpringApplication.run(TrainingAppApplication.class, args);
}
}

View File

@ -1,49 +0,0 @@
package com.services.trainingapp.controller;
import com.services.trainingapp.model.Training;
import com.services.trainingapp.modelDto.TrainingDto;
import com.services.trainingapp.service.TrainingService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/training")
public class TrainingController {
private final TrainingService trainingService;
public TrainingController(TrainingService trainingService) {
this.trainingService = trainingService;
}
@GetMapping("/")
public List<TrainingDto> getTrainings() throws Exception {
return trainingService.findAllTraining();
}
@GetMapping("/{id}")
public TrainingDto getTraining(@PathVariable int id) throws Exception {
return trainingService.findById(id);
}
@PostMapping("/")
public Training createTraining(@RequestBody TrainingDto trainingDto){
return trainingService.addTraining(trainingDto);
}
@PutMapping("/")
public Training updateTraining(@RequestBody TrainingDto trainingDto){
return trainingService.updateTraining(trainingDto);
}
@DeleteMapping("/{id}")
public void deleteTraining(@PathVariable int id){
trainingService.deleteTraining(id);
}
@PatchMapping("/{id_exercise}/{id_training}")
public void addExerciseToTraining(@PathVariable int id_exercise,
@PathVariable int id_training) throws Exception {
trainingService.addExerciseToTraining(id_exercise, id_training);
}
}

View File

@ -1,41 +0,0 @@
package com.services.trainingapp.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.experimental.Accessors;
@Entity
@Table(name = "t_training")
@Data
@Accessors(chain = true)
public class Training {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -1,44 +0,0 @@
package com.services.trainingapp.modelDto;
import jakarta.annotation.Nullable;
public class ExerciseDto {
private int id;
private String name;
private String description;
@Nullable
private Integer id_training;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Nullable
public Integer getId_training() {
return id_training;
}
public void setId_training(@Nullable Integer id_training) {
this.id_training = id_training;
}
}

View File

@ -1,42 +0,0 @@
package com.services.trainingapp.modelDto;
import java.util.List;
public class TrainingDto {
private int id;
private String name;
private String description;
private List<ExerciseDto> exercises;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<ExerciseDto> getExercises() {
return exercises;
}
public void setExercises(List<ExerciseDto> exercises) {
this.exercises = exercises;
}
}

View File

@ -1,7 +0,0 @@
package com.services.trainingapp.repository;
import com.services.trainingapp.model.Training;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TrainingRepository extends JpaRepository<Training, Integer> {
}

View File

@ -1,130 +0,0 @@
package com.services.trainingapp.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.services.trainingapp.model.Training;
import com.services.trainingapp.modelDto.ExerciseDto;
import com.services.trainingapp.modelDto.TrainingDto;
import com.services.trainingapp.repository.TrainingRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
@Service
public class TrainingService {
private final TrainingRepository trainingRepository;
@Value("${exercise-service.host}")
private String exercise_service_host;
public TrainingService(TrainingRepository trainingRepository) {
this.trainingRepository = trainingRepository;
}
public List<TrainingDto> findAllTraining() throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
List<TrainingDto> trainingDtos = new ArrayList<>();
for (Training training : trainingRepository.findAll()) {
TrainingDto trainingDto = new TrainingDto();
trainingDto.setId(training.getId());
trainingDto.setName(training.getName());
trainingDto.setDescription(training.getDescription());
ResponseEntity<String> response = restTemplate.exchange(
"http://" + exercise_service_host + "/exercise/training/" + trainingDto.getId(), HttpMethod.GET, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
List<ExerciseDto> exerciseDtos;
try {
exerciseDtos = objectMapper.readValue(responseBody, ArrayList.class);
trainingDto.setExercises(exerciseDtos);
} catch (JsonProcessingException e) {
throw new Exception("Не удалось десериализовать тело запроса в объект");
}
} else {
throw new Exception("Ошибка получения объекта");
}
trainingDtos.add(trainingDto);
}
return trainingDtos;
}
public TrainingDto findById(int id) throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
TrainingDto trainingDto = new TrainingDto();
Training training = trainingRepository.findById(id).orElseThrow();
trainingDto.setId(training.getId());
trainingDto.setName(training.getName());
trainingDto.setDescription(training.getDescription());
ResponseEntity<String> response = restTemplate.exchange(
"http://" + exercise_service_host + "/exercise/training/" + trainingDto.getId(), HttpMethod.GET, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
List<ExerciseDto> exerciseDtos;
try {
exerciseDtos = objectMapper.readValue(responseBody, ArrayList.class);
trainingDto.setExercises(exerciseDtos);
} catch (JsonProcessingException e) {
throw new Exception("Не удалось десериализовать тело запроса в объект");
}
} else {
throw new Exception("Ошибка получения объекта");
}
return trainingDto;
}
public Training addTraining(TrainingDto trainingDto) {
Training training = new Training();
training.setName(trainingDto.getName());
training.setDescription(trainingDto.getDescription());
return trainingRepository.save(training);
}
public Training updateTraining(TrainingDto trainingDto){
Training training = trainingRepository.findById(trainingDto.getId()).orElseThrow();
training.setName(trainingDto.getName());
training.setDescription(trainingDto.getDescription());
return trainingRepository.save(training);
}
public void deleteTraining(int id){
trainingRepository.delete(trainingRepository.findById(id).orElseThrow());
}
public void addExerciseToTraining(int exercise_id, int training_id) throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
ResponseEntity<String> response = restTemplate.exchange(
"http://" + exercise_service_host + "/exercise/" + exercise_id, HttpMethod.GET, entity, String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
ExerciseDto exerciseDto;
try {
exerciseDto = objectMapper.readValue(responseBody, ExerciseDto.class);
} catch (JsonProcessingException e) {
throw new Exception("Не удалось десериализовать тело запроса в объект");
}
exerciseDto.setId_training(training_id);
restTemplate.exchange("http://" + exercise_service_host + "/exercise/", HttpMethod.PUT, new HttpEntity<>(exerciseDto), String.class);
} else {
throw new Exception("Ошибка получения объекта");
}
}
}

View File

@ -1,9 +0,0 @@
exercise-service:
host: ${EXERCISE_SERVICE_HOST:localhost:8080}
server:
port: "8082"
spring:
datasource:
url: ${DATASOURCE_URL:jdbc:postgresql://localhost:5432/traininarium}
username: ${DATASOURCE_USERNAME:admin}
password: ${DATASOURCE_PASSWORD:admin}

View File

@ -1,13 +0,0 @@
package com.services.trainingapp;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TrainingAppApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -1,190 +0,0 @@
# Лабораторная работа 4.
### Задание
**Цель**: изучение проектирования приложений при помощи брокера сообщений.
**Задачи**:
- Установить брокер сообщений RabbitMQ.
- Пройти уроки 1, 2 и 3 из RabbitMQ Tutorials на любом языке программирования.
- Продемонстрировать работу брокера сообщений.
### Как запустить лабораторную работу
В директории с файлом характеристик docker-compose.yaml выполнить команду:
```
docker-compose -f docker-compose.yaml up
```
### Описание лабораторной работы
#### Прохождение tutorial
**1. *"Hello world"***
Результат работы producer:
![Сборка docker-compose](hello-world-send.jpg)
Результат работы consumer:
![Сборка docker-compose](hello-world-receive.jpg)
**2. *"Work Queues"***
Результат работы producer:
![Сборка docker-compose](work-queues-new-task.jpg)
Результат работы consumer:
![Сборка docker-compose](work-queues-worker.jpg)
**3. *"Publish/Subscribe"***
Результат работы producer:
![Сборка docker-compose](publish-subscribe-emit-log.jpg)
Результат работы consumer1:
![Сборка docker-compose](publish-subscribe-receive-logs1.jpg)
Результат работы consumer2:
![Сборка docker-compose](publish-subscribe-receive-logs2.jpg)
#### Разработка демонстрационных приложений
Для разработки демонстрационных приложений была выбрана предметная область прошлой лабораторной работы, состоящая из тренировок и упражнений.
Согласно заданию необходимо создать:
1. Publisher, которая создаёт один exchange с типом fanout.
Для этого создадим список logs, который хранит различные сообщения логов, которые могут быть отправлены.
```python
logs = ["started exercise", "finished exercise", "started training", "finished training"]
```
Установим соединение с локальным *RabbitMQ* сервером на `localhost` и создадим канал связи с *RabbitMQ* для отправки и получения сообщений.
```python
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
```
Далее определим имя обмена в *RabbitMQ*, в данном случае logs и объявем обмен с указанным именем и типом fanout, что означает, что все сообщения, отправленные в этот обмен, будут отправлены всем подписчикам, которые присоединены к нему.
```python
exchange_name = 'logs'
channel.exchange_declare(exchange=exchange_name, exchange_type='fanout')
```
Для того, чтобы программа раз в секунду генерировала сообщения в журнал событий создадим цикл, в котором будет выбираться случайное сообщение из списка `logs`, публиковаться в обмен `logs` без указания ключа маршрутизации (сообщение будет доставлено всем подписчикам этого обмена). И также укажем задержку в 1 секунду между каждой публикацией сообщения.
```python
while 1:
log = random.choice(logs)
channel.basic_publish(exchange=exchange_name, routing_key='', body=log)
print(f" [x] Published: {log}")
time.sleep(1)
```
2. Consumer 1, которая создаёт под себя отдельную не анонимную очередь, создаёт binding на exchange и начинает принимать сообщения. Программа должна обрабатывать сообщения 2-3 секунды.
Создадим функцию `message_manager`, которая принимает три аргумента: `channel` (канал связи с RabbitMQ), `queue_name` (имя очереди) и `exchange_name` (имя обмена).
Затем объявляем очеред с указанным именем, связываем очередь с обменом, чтобы сообщения из этого обмена были доставлены в эту очередь.
Также создаем функцию `callback`, которая будет вызываться при получении нового сообщения из очереди. В данной функции происходит декодирование полученного сообщения из байтового формата в строку, вывод результата в консоль и имитация обработки сообщения с помощью задержки сообщения на 2-3 секунды. В зависимости от полученного значения декодированого сообщения, происходит вывод сообщение о статусе обработки, а также происходит подтверждение успешной обработки сообщения.
```python
def callback(ch, method, properties, body):
task = body.decode()
print(f" [x] Received : {task}")
time.sleep(random.randint(2, 3))
if task == "started exercise":
print(" [x] Started timer")
elif task == "finished exercise":
print(" [x] Stopped timer, started another exercise")
elif task == "started training":
print(" [x] Started manage exercise")
else:
print(" [x] Result of training given")
ch.basic_ack(delivery_tag=method.delivery_tag)
```
Далее устанавливаем обработчик callback для сообщений из указанной очереди и запускаем бесконечный цикл получения и обработки сообщений из очереди.
Таким образом, функция `message_manager` выглядет следующим образом:
```python
def message_manager(channel, queue_name, exchange_name):
channel.queue_declare(queue=queue_name)
channel.queue_bind(exchange=exchange_name, queue=queue_name)
def callback(ch, method, properties, body):
task = body.decode()
print(f" [x] Received : {task}")
time.sleep(random.randint(2, 3))
if task == "started exercise":
print(" [x] Started timer")
elif task == "finished exercise":
print(" [x] Stopped timer, started another exercise")
elif task == "started training":
print(" [x] Started manage exercise")
else:
print(" [x] Result of training given")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue=queue_name, on_message_callback=callback)
print("[*] Waiting for messages. To exit press CTRL+C")
channel.start_consuming()
```
Далее создаем соединение с *RabbitMQ*, создаем канал и определяем имя обмена и имя очереди. Затем, создаем поток `consumer_thread`, который вызывает функцию `message_manager` с передачей канала, имени очереди и имени обмена в качестве аргументов. После этого, поток запускается и ожидает его завершения с помощью `consumer_thread.join()`, чтобы основной поток программы не завершился до завершения работы обработчика сообщений.
```python
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
exchange_name = 'logs'
queue_name = 'slow-queue'
consumer_thread = threading.Thread(target=message_manager, args=(channel, queue_name, exchange_name))
consumer_thread.start()
consumer_thread.join()
```
3. Consumer 2. Аналогично Consumer 1, только сообщения необходимо обрабатывать моментально и имя очереди должно отличаться от Consumer 1.
Аналогично создаем подключение к серверу с помощью `pika.BlockingConnection`,создаем канал для обмена сообщениями с помощью `connection.channel()`. Определяем имя обменника *"logs"* и имя очереди *"fast-queue"*. Далее объявляем саму очередь с данным именем.
```python
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
exchange_name = 'logs'
queue_name = 'fast-queue'
channel.queue_declare(queue=queue_name)
channel.queue_bind(exchange=exchange_name, queue=queue_name)
```
После чего создаем функцию `callback`, которая будет вызываться при получении сообщений из очереди. Функция принимает параметры `ch` (канал), `method` (метод доставки), `properties` (свойства сообщения) и `body` (тело сообщения). В данном случае, функция аналогично декодирует тело сообщения в строку и выводит соответствующее сообщение в зависимости от значения `task`.
```python
def callback(ch, method, properties, body):
task = body.decode()
print(f" [x] Received : {task}")
if task == "started exercise":
print(" [x] Started timer")
elif task == "finished exercise":
print(" [x] Stopped timer, started another exercise")
elif task == "started training":
print(" [x] Started manage exercise")
else:
print(" [x] Result of training given")
ch.basic_ack(delivery_tag=method.delivery_tag)
```
Код будет ожидать и обрабатывать сообщения, пока не будет прерван.
Результат работы быстрой очереди:
![Сборка docker-compose](fast-queue.jpg)
Результат работы медленной очереди:
![Сборка docker-compose](slow-queue.jpg)
Таким образом, можно сделать вывод о том, что в очереди fast-queue сообщения обрабатываются мгновенно, а в очереди slow-queue они обрабатываются с задержкой и в следствии этого сообщения накапливаются в очереди.
### Видео
https://disk.yandex.ru/i/Qei-5DvhovOBJA

View File

@ -1,37 +0,0 @@
import pika
import random
import threading
import time
def message_manager(channel, queue_name, exchange_name):
channel.queue_declare(queue=queue_name)
channel.queue_bind(exchange=exchange_name, queue=queue_name)
def callback(ch, method, properties, body):
task = body.decode()
print(f" [x] Received : {task}")
time.sleep(random.randint(2, 3))
if task == "started exercise":
print(" [x] Started timer")
elif task == "finished exercise":
print(" [x] Stopped timer, started another exercise")
elif task == "started training":
print(" [x] Started manage exercise")
else:
print(" [x] Result of training given")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue=queue_name, on_message_callback=callback)
print("[*] Waiting for messages. To exit press CTRL+C")
channel.start_consuming()
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
exchange_name = 'logs'
queue_name = 'slow-queue'
consumer_thread = threading.Thread(target=message_manager, args=(channel, queue_name, exchange_name))
consumer_thread.start()
consumer_thread.join()

View File

@ -1,28 +0,0 @@
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
exchange_name = 'logs'
queue_name = 'fast-queue'
channel.queue_declare(queue=queue_name)
channel.queue_bind(exchange=exchange_name, queue=queue_name)
def callback(ch, method, properties, body):
task = body.decode()
print(f" [x] Received : {task}")
if task == "started exercise":
print(" [x] Started timer")
elif task == "finished exercise":
print(" [x] Stopped timer, started another exercise")
elif task == "started training":
print(" [x] Started manage exercise")
else:
print(" [x] Result of training given")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(queue=queue_name, on_message_callback=callback)
print("[*] Waiting for messages. To exit press CTRL+C")
channel.start_consuming()

View File

@ -1,17 +0,0 @@
import pika
import random
import time
logs = ["started exercise", "finished exercise", "started training", "finished training"]
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
exchange_name = 'logs'
channel.exchange_declare(exchange=exchange_name, exchange_type='fanout')
print(' [*] Started. To exit press CTRL+C')
while 1:
log = random.choice(logs)
channel.basic_publish(exchange=exchange_name, routing_key='', body=log)
print(f" [x] Published: {log}")
time.sleep(1)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -1,30 +0,0 @@
import os
import pika
import sys
def main():
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(f" [x] Received {body}")
channel.basic_consume(queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Interrupted')
try:
sys.exit(0)
except SystemExit:
os._exit(0)

View File

@ -1,11 +0,0 @@
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@ -1,13 +0,0 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs', routing_key='', body=message)
print(f" [x] Sent {message}")
connection.close()

View File

@ -1,22 +0,0 @@
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', exchange_type='fanout')
result = channel.queue_declare(queue='', exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='logs', queue=queue_name)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(f" [x] {body}")
channel.basic_consume(
queue=queue_name, on_message_callback=callback, auto_ack=True)
channel.start_consuming()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1,20 +0,0 @@
import pika
import sys
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(
exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode=pika.DeliveryMode.Persistent
))
print(f" [x] Sent {message}")
connection.close()

View File

@ -1,23 +0,0 @@
import pika
import time
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(f" [x] Received {body.decode()}")
time.sleep(body.count(b'.'))
print(" [x] Done")
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()

View File

@ -1,103 +0,0 @@
# Лабораторная работа 5.
### Задание
**Задачи**:
Реализовать умножение двух больших квадратных матриц.
Сделать два алгоритма: обычный и параллельный (задание со * - реализовать это в рамках одного алгоритма). В параллельном алгоритме предусмотреть ручное задание количества потоков (число потоков = 1 как раз и реализует задание со *), каждый из которых будет выполнять умножение элементов матрицы в рамках своей зоны ответственности.
### Как запустить лабораторную работу
В директории с файлом характеристик docker-compose.yaml выполнить команду:
```
docker-compose -f docker-compose.yaml up
```
### Описание лабораторной работы
Для реализации параллельного умножения матриц с использованием многопоточности создадим несколько функций:
1. Функция `multiplication_rows(row, matrix_b)`
Данная функция используется для перемножения входной строки row на матрицу matrix_b и возвращает результат умножения.
```python
def multiplication_rows(row, matrix_b):
return np.dot(row, matrix_b)
```
2. Функция `parallel_matrix_multiplication(matrix_a, matrix_b, num_threads)`
Данная функция Принимает две матрицы `matrix_a` и `matrix_b`, а также количество потоков `num_threads`, которое ровно количеству строк в первой матрице. Также осуществляется проверка на то, что размеры матрицы совместимы.
Затем создается пул потоков с использованием `concurrent.futures.ThreadPoolExecutor` и устанавливает максимальное количество потоков равным `num_threads`. Запускается таймер для измерения времени выполнения умножения матриц. Создается список `results` и
для каждой строки матрицы `matrix_a` запускает функцию `multiplication_rows` в отдельном потоке с помощью `executor.submit()`. Результаты сортируются по индексу строки и объединяются в матрицу в правильном порядке с помощью `np.vstack()`. Завершается таймер и происходит замер времени, затраченного на выполнение задачи.
```python
def parallel_matrix_multiplication(matrix_a, matrix_b, num_threads):
num_rows_a, num_cols_a = matrix_a.shape
num_rows_b, num_cols_b = matrix_b.shape
assert num_cols_a == num_rows_b, "Размеры матриц несовместимы"
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
start_time = time.time()
results = []
for i in range(num_rows_a):
result = executor.submit(multiplication_rows, matrix_a[i], matrix_b)
results.append((i, result))
sorted_results = sorted(results, key=lambda x: x[0])
result_matrix = np.vstack(
[result.result() for _, result in sorted_results])
end_time = time.time()
execution_time = end_time - start_time
return result_matrix, execution_time
```
3. Функция `test(parallel)`
Данная функция создает две матрицы с фиксированными значениями. И происходит разбиение на два алгоритма вычисления умножения матриц: обычный и параллельный.Если `parallel` равно True, вызывает `parallel_matrix_multiplication()` с `num_threads=2`, иначе с `num_threads=1`.
4. Функции `matrix100x100(parallel)`, `matrix300x300(parallel)` и `matrix500x500(parallel)`
В данных функция создается пара случайных матриц размером *100x100*, *300x300* и *500x500* со значениями от 0 до 100.
Если `parallel` равно `True`, вызывают `parallel_matrix_multiplication()` с `num_threads` равным размеру матрицы, иначе с `num_threads=1`.
Выводят результат умножения и время выполнения.
Пример функции для матрицы размером *100x100*:
```python
def matrix100x100(parallel):
a = np.random.randint(0, 100, size=(100, 100))
b = np.random.randint(0, 100, size=(100, 100))
if parallel:
result = parallel_matrix_multiplication(a, b, num_threads=100)
else:
result = parallel_matrix_multiplication(a, b, num_threads=1)
print("Результат умножения:")
print(result[0])
print("Время выполнения: " + str(result[1]) + " с")
```
#### Результаты выполнения последовательного и параллельного алгоритма на умножение двух матриц размером 100x100, 300x300, 500x500 элементов.
Результат перемножения матриц 100х100:
![Результат перемножения матриц 100х100](matrix100x100.jpg)
Результат перемножения матриц 300х300:
![Результат перемножения матриц 300х300](matrix300x300.jpg)
езультат перемножения матриц 500х500:
![Результат перемножения матриц 500х500](matrix500x500.jpg)
Таким образом, можно сделать вывод о том, что умножение матрицы параллельным способом значительно ускоряет процесс выполнения по сравнению с обычным способом на больших объемах данных. В случае матрицы размером 500х500, время выполнения параллельного умножения составляет всего 0.03999614715576172 с, в то время как обычный способ занимает 0.10399985313415527 с. Однако случае матрицы размером 100х100, время выполнения параллельного умножения составляет 0.008999347686767578 с, в то время как обычный способ занимает 0.006066799163818359 с, и перемножение обычным алгоритмом является более выгодным по временнным затратам, чем паралелльным.
Параллельное умножение матрицы может быть полезным в случаях, когда требуется обработать большие объемы данных и ускорить процесс вычислений.
### Видео
https://disk.yandex.ru/i/uJzemvtUlgmR2g

View File

@ -1,98 +0,0 @@
import numpy as np
import concurrent.futures
import time
def multiplication_rows(row, matrix_b):
return np.dot(row, matrix_b)
def parallel_matrix_multiplication(matrix_a, matrix_b, num_threads):
num_rows_a, num_cols_a = matrix_a.shape
num_rows_b, num_cols_b = matrix_b.shape
assert num_cols_a == num_rows_b, "Размеры матриц несовместимы"
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
start_time = time.time()
results = []
for i in range(num_rows_a):
result = executor.submit(multiplication_rows, matrix_a[i], matrix_b)
results.append((i, result))
sorted_results = sorted(results, key=lambda x: x[0])
result_matrix = np.vstack(
[result.result() for _, result in sorted_results])
end_time = time.time()
execution_time = end_time - start_time
return result_matrix, execution_time
def test(parallel):
a = np.array([[12, 42, 64],
[38, 4, 21]])
b = np.array([[35, 2],
[64, 41],
[5, 33]])
if parallel:
result = parallel_matrix_multiplication(a, b, num_threads=2)
else:
result = parallel_matrix_multiplication(a, b, num_threads=1)
print("Результат умножения:")
print(result[0])
print("Время выполнения: " + str(result[1]) + " с")
def matrix100x100(parallel):
a = np.random.randint(0, 100, size=(100, 100))
b = np.random.randint(0, 100, size=(100, 100))
if parallel:
result = parallel_matrix_multiplication(a, b, num_threads=100)
else:
result = parallel_matrix_multiplication(a, b, num_threads=1)
print("Результат умножения:")
print(result[0])
print("Время выполнения: " + str(result[1]) + " с")
def matrix300x300(parallel):
a = np.random.randint(0, 100, size=(300, 300))
b = np.random.randint(0, 100, size=(300, 300))
if parallel:
result = parallel_matrix_multiplication(a, b, num_threads=300)
else:
result = parallel_matrix_multiplication(a, b, num_threads=1)
print("Результат умножения:")
print(result[0])
print("Время выполнения: " + str(result[1]) + " с")
def matrix500x500(parallel):
a = np.random.randint(0, 100, size=(500, 500))
b = np.random.randint(0, 100, size=(500, 500))
if parallel:
result = parallel_matrix_multiplication(a, b, num_threads=500)
else:
result = parallel_matrix_multiplication(a, b, num_threads=1)
print("Результат умножения:")
print(result[0])
print("Время выполнения: " + str(result[1]) + " с")
if __name__ == '__main__':
# test(parallel=True)
# print("Матрица 100x100:")
# print("Результат умножения параллельно:")
# matrix100x100(parallel=True)
# print("Результат умножения обычным способом:")
# matrix100x100(parallel=False)
# print("Матрица 300x300:")
# print("Результат умножения параллельно:")
# matrix300x300(parallel=True)
# print("Результат умножения обычным способом:")
# matrix300x300(parallel=False)
print("Матрица 500x500:")
print("Результат умножения параллельно:")
matrix500x500(parallel=True)
print("Результат умножения обычным способом:")
matrix500x500(parallel=False)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="fb3a6329-81a8-41fd-8008-b8bea3f6c964" name="Changes" comment="" />
<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="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2ad3GS5xSIilTTmSvnMeDhv4jNu" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "C:/Users/kutyg/Downloads/pibd-22-internet-programming-Lab5 (1)/kutygin_andrey_lab_2/worker_2"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_2" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_2\src" />
<recent name="C:\Users\kutyg\Downloads\pibd-22-internet-programming-Lab5 (1)\kutygin_andrey_lab_2\worker_1\src" />
</key>
</component>
<component name="RunManager" selected="Docker.worker_2/Dockerfile">
<configuration name="docker-compose.yml: Compose Deployment" type="docker-deploy" factoryName="docker-compose.yml" temporary="true" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="sourceFilePath" value="docker-compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
<configuration name="worker_1/Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="sourceFilePath" value="worker_1/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
<configuration name="worker_2/Dockerfile" type="docker-deploy" factoryName="dockerfile" temporary="true" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="sourceFilePath" value="worker_2/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Docker.worker_2/Dockerfile" />
<item itemvalue="Docker.worker_1/Dockerfile" />
<item itemvalue="Docker.docker-compose.yml: Compose Deployment" />
</list>
</recent_temporary>
</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="fb3a6329-81a8-41fd-8008-b8bea3f6c964" name="Changes" comment="" />
<created>1704634533824</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1704634533824</updated>
</task>
<servers />
</component>
</project>

View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="renamerInitialized" value="true" />
</component>
</project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lab3.iml" filepath="$PROJECT_DIR$/.idea/lab3.iml" />
</modules>
</component>
</project>

View File

@ -1,110 +0,0 @@
## Задание
Цель: изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API.
Задачи:
- Создать 2 микросервиса, реализующих CRUD на связанных сущностях.
- Реализовать механизм синхронного обмена сообщениями между микросервисами.
- Реализовать шлюз на основе прозрачного прокси-сервера nginx.
## Ход работы
### Разворачивание сервисов:
Были разработаны два приложения на Java с использованием средства автоматизации сборки проектов Gradle и с использованием библиотеки spring-boot:
- categoryService - работа с дисциплинами (crud)
- productService - работа с продуктами (crud). При создании продукта выбирается id категории, и через nginx и rest template происходит получение данных о категории с этим id
### Обмен сообщениями
При создании плана обучения выбирается id категории, и через nginx и rest template происходит получение данных о категории с этим id
### Dockerfile
Идентичные докерфайлы для приложений:
```
FROM openjdk:17
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
RUN ./gradlew clean build
EXPOSE 8089
ENTRYPOINT ["java","-jar","build/libs/lab3-0.0.1-SNAPSHOT.jar"]
```
### docker-compose.yml
Файл, соединяющий сервисы (содержащий настройку Docker):
```
version: "3" #формат конфигурации Docker Compose версии 3
services: #определение сервисов
category:
build:
context: /categoryService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8089:8089" #проброс портов
networks:
- netwrk #сеть
product:
build:
context: /productService #путь к контексту сборки
dockerfile: Dockerfile #имя докерфайла
ports:
- "8090:8090" #проброс портов
networks:
- netwrk #сеть
nginx:
image: nginx:latest #образ для контейнера
ports:
- "8091:80" #проброс портов
networks:
- netwrk #сеть
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf #монтирует локальный файл конфигурации
depends_on: #зависимость от сервисов
- category
- product
networks:
netwrk:
driver: bridge #изолированная сеть
```
### nginx.conf
Настройка nginx:
```
http {
server {
listen 80;
listen [::]:80;
server_name localhost;
location /categoryService/ {
proxy_pass http://category:8089/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /categoryService;
}
location /productService/ {
proxy_pass http://product:8090/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Prefix /productService;
}
}
}
events {
worker_connections 1024;
}
```
## Результат
Видео: https://disk.yandex.ru/d/8Lcvb0H9LPNSKw

View File

@ -1,42 +0,0 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -1,7 +0,0 @@
FROM openjdk:17
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
COPY . /usr/src/app/
RUN ./gradlew clean build
EXPOSE 8089
ENTRYPOINT ["java","-jar","build/libs/lab3-0.0.1-SNAPSHOT.jar"]

View File

@ -1,34 +0,0 @@
plugins {
id 'org.springframework.boot' version '2.6.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'categoryApp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
}
tasks.named('test') {
useJUnitPlatform()
}

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