Files

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

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

Вариант 5: Нужно определить максимальный элемент матрицы

Необходимо:

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

Используемые технологии

  1. Spring Boot - фреймворк для упрощения создание и настройку Spring-приложений. А также используется для создания RESTful сервисов
  2. Docker

Запуск лабораторной работы

  1. docker build -t matrix-container2 . - создаем образ приложения во втором контейнере
  2. docker build -t matrix-container1 . - создаем образ приложения в первом контейнере
  3. docker run -d -p 8080:8080 matrix-container2 - запуск контейнера во втором контейнере
  4. docker run -d -p 8081:8081 matrix-container1 - запуск контейнера в первом контейнере

Что делает программа

  1. Генерирация матрицы
  2. Матрица разделяется на две половины. Первая половина матрицы остается в локальном сервисе. Вторая половина матрицы отправляется во второй сервис
  3. Паралелльая обработка матрицы и поиск каждым сервисом локального максимума в каждой половине.
  4. Определение глобального максимума

Обзор работы программы

Программа реализует распределенные вычисления в два контейнера.

  1. Первый контейнер генерирует матрицу размером 1000*1000
int[][] matrix = generateRandomMatrix(1000, 1000);

private static int[][] generateRandomMatrix(int rows, int columns) {
                int[][] matrix = new int[rows][columns];
                Random random = new Random();
                for (int i = 0; i < rows; i++) {
                        for (int j = 0; j < columns; j++) {
                                matrix[i][j] = random.nextInt(100_000);
                        }
                }
                return matrix;
        }
  1. Затем в первом контейнер происходит разделение матрицы на две части
int half = matrix.length / 2;
                int[][] firstHalf = new int[half][matrix[0].length];
                int[][] secondHalf = new int[matrix.length - half][matrix[0].length];
                for (int i = 0; i < matrix.length; i++) {
                        if (i < half) {
                                firstHalf[i] = matrix[i];
                        } else {
                                secondHalf[i - half] = matrix[i];
                        }
                }

Первая половина будет обрабатываться локально, вторая половина будет обрабатывать на втором контейнере

  1. Распределенная обработка массива

Отправляем вторую половину матрицы во второй контейнер.

//создание объекта RestTemplate для выполнения HTTP-запроса
RestTemplate restTemplate = new RestTemplate();
//url адрес второго контейнера
String secondContainerUrl = "http://192.168.5.107:8080/matrix/max";
//отправка пост запроса второй половины матрицы на второй контейнер 
ResponseEntity<Integer> response = restTemplate.postForEntity(secondContainerUrl, secondHalf, Integer.class);

Затем происходит поиск максимума в каждой половине массива.

В первом контейнере обработка половины массива происходит локально

private static int findMaxElement(int[][] matrix) {
                int max = Integer.MIN_VALUE;
                for (int[] row : matrix) {
                        for (int value : row) {
                                if (value > max) {
                                max = value;
                                }
                        }
                }
                return max;
        }

Второй контейнер принимает половину матрицы, обрабатывает и отправляет обратно в первый контейнер

public int processMatrix(@RequestBody int[][] matrix) {
        System.out.println("Server start find max");
        int min = Integer.MAX_VALUE;
        for (int[] row : matrix) {
            for (int value : row) {
                if (value < min) {
                    min = value;
                }
            }
        }
        return min;
    }

Получение результата из второго контейнера и поиск глобального максимума из двух контейнеров

int localMax = findMaxElement(firstHalf);
//получение ответа из второго контейнера
int remoteMax = response.getBody();
int globalMax = Math.max(localMax, remoteMax);

Тесты

Вывод первого контейнера

root@secondcontainer:~/client# docker run  matrix-container1

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.3)

2025-03-21T15:33:03.190Z  INFO 1 --- [demo] [           main] com.example.client.Application       : Starting Application v0.0.1-SNAPSHOT using Java 17.0.2 with PID 1 (/app.jar started by root in /)
2025-03-21T15:33:03.210Z  INFO 1 --- [demo] [           main] com.example.client.Application       : No active profile set, falling back to 1 default profile: "default"
2025-03-21T15:33:07.096Z  INFO 1 --- [demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8081 (http)
2025-03-21T15:33:07.149Z  INFO 1 --- [demo] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-03-21T15:33:07.155Z  INFO 1 --- [demo] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.36]
2025-03-21T15:33:07.236Z  INFO 1 --- [demo] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-03-21T15:33:07.243Z  INFO 1 --- [demo] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 3641 ms
2025-03-21T15:33:08.559Z  INFO 1 --- [demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8081 (http) with context path '/'
2025-03-21T15:33:08.654Z  INFO 1 --- [demo] [           main] com.example.client.Application       : Started Application in 7.65 seconds (process running for 9.735)
Max: 99999
Time: 690 ms

Вывод второго контейнера

root@CT107:~/server# docker run  matrix-container2   

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.3)

2025-03-21T15:36:25.317Z  INFO 1 --- [demo] [           main] com.example.server.Application       : Starting Application v0.0.1-SNAPSHOT using Java 17.0.2 with PID 1 (/app.jar started by root in /)
2025-03-21T15:36:25.336Z  INFO 1 --- [demo] [           main] com.example.server.Application       : No active profile set, falling back to 1 default profile: "default"
2025-03-21T15:36:29.380Z  INFO 1 --- [demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-03-21T15:36:29.537Z  INFO 1 --- [demo] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-03-21T15:36:29.547Z  INFO 1 --- [demo] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.36]
2025-03-21T15:36:30.043Z  INFO 1 --- [demo] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-03-21T15:36:30.051Z  INFO 1 --- [demo] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4520 ms
2025-03-21T15:36:32.306Z  INFO 1 --- [demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-03-21T15:36:32.417Z  INFO 1 --- [demo] [           main] com.example.server.Application       : Started Application in 8.98 seconds (process running for 11.068)
Server start find max

Вывод

В даной ЛР была был разработан параллельный вариант алгоритма при использования SpringBoot.

Если сравнивать алгоритм с однопоточным, то использование сервисной архитектуры приводит к более быстрому получению результата, если не брать в расчет случаи, когда дополнительные затраты на реализацию многопоточного приложения незначительны по сравнению с эффективностью паралельной обработки

Если сравнивать алгоритм с ThreadPoolExecutor(1440 ms), то использовать распределенности более выгодно.

Если сравнивать алгоритм с ForkJoinPool(464 ms) и MPI приложением(485 ms), то у нас получается более худшие результы, так как хоть приложение и распределенное, но обратывает части он линейно, в отличие от ForkJoinPool, который сильнее делит матрицу на подзадачи.