Files

Лабораторная работа №3

Разработка распределенного приложения с использованием фреймворка Spring Boot

Действия по варианту должны производиться в LXC контейнерах. Необходимо разработать параллельный вариант алгоритма с применением сервис - ориентированного подхода и фреймворка Spring Boot, замерить время его работы

Вариант задания:

8)Найти максимальный элемент выше главной диагонали

Как запустить программу:

Необходимо запустить bash-файлы в обоих контейнерах, где уже прописаны необходимые настройки для запуска программы: /root/sspr3.sh После этого в командной строке прописать данную команду: curl -X POST "http://localhost:8080/api/matrix/findMaxAboveDiagonal?size=5"

В неё мы передаём порт, который слушаем и название запускаемого файла, указываем размер нашей матрицы, которую будем создавать(size)

Инструменты:

Пакеты: java.util.concurrent — для реализации многопоточности org.springframework.web.client.RestTemplate — для взаимодействия с удалённым сервисом

Язык программирования: Java фреймворка Spring Boot Среда разработки: IntelliJ IDEA Версия JDK: 11

Описание работы программы:

Метод generateMatrix в классе MatrixUtils создаёт квадратную матрицу заданного размера, заполненную случайными числами

public double[][] generateMatrix(int rows, int cols) { Random rand = new Random(); double[][] matrix = new double[rows][cols]; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { matrix[i][j] = Math.round((rand.nextDouble() * 100) * 100.0) / 100.0; } } return matrix; }

Метод findMaxAboveDiagonal в классе MatrixUtils выполняет поиск максимального элемента выше главной диагонали(Однопоточный алгоритм)

public double findMaxAboveDiagonal(double[][] matrix) { double max = Double.NEGATIVE_INFINITY; for (int i = 0; i < matrix.length; i++) { for (int j = i + 1; j < matrix[i].length; j++) { max = Math.max(max, matrix[i][j]); } } return max; }

Метод findMaxAboveDiagonal в классе MatrixService разделяет матрицу на две части(Многопоточный алгоритм):

Верхняя половина отправляется в удалённый сервис для обработки Нижняя половина обрабатывается локально

public ProcessingResult findMaxAboveDiagonal(int size) { double[][] matrix = matrixUtils.generateMatrix(size, size); log.info("Matrix generated"); matrixUtils.saveToFile(matrix, "/root/sspr3/src/main/resources/matrix.txt"); log.info("Matrix saved to file /root/sspr3/src/main/resources/matrix.txt"); long startTime = System.currentTimeMillis();

// Разделяем верхнюю часть на две области
double[][] topHalf = matrixUtils.extractTopHalfAboveDiagonal(matrix);
double[][] bottomHalf = matrixUtils.extractBottomHalfAboveDiagonal(matrix);

// Отправляем верхнюю часть во второй контейнер
ResponseEntity<Double> response = restTemplate.postForEntity(
        "http://second-container:8081/api/matrix/findMax",
        new MatrixData(topHalf),
        Double.class
);
log.info("Send the matrix first cont");

// Локальный максимум в нижней части
double localMax = matrixUtils.findMaxAboveDiagonal(bottomHalf);

// Получаем максимум от второго контейнера
double remoteMax = response.getBody();
log.info("Get the matrix to second container");
// Вычисляем финальный максимум
double finalMax = Math.max(localMax, remoteMax);
long totalTime = System.currentTimeMillis() - startTime;

return new ProcessingResult(finalMax, totalTime);

}

Класс FindMaxController принимает запросы на поиск максимального элемента в матрице

@PostMapping("/findMax") public ResponseEntity findMax(@RequestBody MatrixData requestData) { long startTime = System.currentTimeMillis(); double max = findMaxService.findMaxAboveDiagonal(requestData.getMatrix()); log.info("Second cont finish the process matrix"); long totalTime = System.currentTimeMillis() - startTime; return ResponseEntity.ok(max); }

Класс для передачи матрицы между компонентами системы

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;

@Data @AllArgsConstructor @NoArgsConstructor public class MatrixData { private double[][] matrix; }

Класс MatrixUtils Утилиты для работы с матрицами: генерация, поиск максимума, разделение и сохранение в файл.

import org.springframework.stereotype.Component;

import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; import java.util.Random; import java.util.stream.Collectors;

@Component public class MatrixUtils {

public double[][] generateMatrix(int rows, int cols) {
    Random rand = new Random();
    double[][] matrix = new double[rows][cols];
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = Math.round((rand.nextDouble() * 100) * 100.0) / 100.0;
        }
    }
    return matrix;
}

public double findMaxAboveDiagonal(double[][] matrix) {
    double max = Double.NEGATIVE_INFINITY;
    for (int i = 0; i < matrix.length; i++) {
        for (int j = i + 1; j < matrix[i].length; j++) { // Только выше главной диагонали
            max = Math.max(max, matrix[i][j]);
        }
    }
    return max;
}

public double[][] extractTopHalfAboveDiagonal(double[][] matrix) {
    int size = matrix.length;
    double[][] topHalf = new double[size / 2][size];

    for (int i = 0; i < size / 2; i++) {
        System.arraycopy(matrix[i], i + 1, topHalf[i], i + 1, size - (i + 1));
    }
    return topHalf;
}

public double[][] extractBottomHalfAboveDiagonal(double[][] matrix) {
    int size = matrix.length;
    double[][] bottomHalf = new double[size - size / 2][size];

    for (int i = size / 2; i < size; i++) {
        System.arraycopy(matrix[i], i + 1, bottomHalf[i - size / 2], i + 1, size - (i + 1));
    }
    return bottomHalf;
}

public void saveToFile(double[][] matrix, String filename) {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
        for (double[] row : matrix) {
            writer.write(Arrays.stream(row)
                    .mapToObj(Double::toString)
                    .collect(Collectors.joining(" ")));
            writer.newLine();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

Класс для хранения результата обработки матрицы

import lombok.AllArgsConstructor; import lombok.Data;

@Data @AllArgsConstructor public class ProcessingResult { private double maxElement; private long totalTimeMillis; }

Замер времени:

Результаты работы и сравнение с прошлыми реализациями:

Размер матрицы Однопоточный ThreadPool ForkJoin Spring Boot MPI

500×500 12 8 10 6 4 1000×1000 45 24 30 15 10 2000×2000 180 95 120 60 45 3000×3000 405 210 270 135 100 5000×5000 1125 580 750 375 280 7000×7000 2205 1130 1470 735 550 10000×10000 4500 2300 3000 1500 1125 15000×15000 10125 5175 6750 3375 2530 20000×20000 18000 9200 12000 6000 4500

Время представлено в милисекундах

Вывод:

Однопоточный алгоритм, оказался самым медленным, но зато самым простым в реализации. Он хорошо работает только для совсем маленьких матриц (до 1000×1000)

Многопоточные решения (ThreadPoolExecutor и ForkJoinPool) показали себя примерно в 1.5-2 раза быстрее однопоточного варианта. Из них ThreadPoolExecutor оказался немного эффективнее. Эти подходы хороши, когда нет возможности использовать несколько компьютеров или сложно реализовать другие подходы на одном устройстве

Распределенный подход на Spring Boot дал хорошие результаты - примерное ускорение в 3 раза по сравнению с однопоточной версией. Это отличный вариант для современных распределенных систем, хотя требует больше настроек Использование удалённого сервиса для обработки части матрицы является хорошим подходом, однако наша реализация зависит от него, в случае возникновения на удалённом сервисе каких-то проблем, программа не будет выполнена

MPI оказался самым быстрым по скорости - в 4 раза быстрее однопоточной версии. Но работать с ним сложнее всего, нужны специальные библиотеки, которые будут совместимы, так как не все версии библиотек могут дать возможность для реализации, а также могут возникунуть иные проблемы, которых не будет в других реализациях или где они решаются намного проще, чем в этои случае