Данная статья является описанием моего эксперимента передать управление ресурсами JVM нейронной сети, которая будет предсказывать необходимость управления ресурсами на основе текущих данных, таких как загрузка CPU и память.

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

Применимость и потенциальные возможности

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

Что мы реализуем

Мы построим систему, которая:

  1. Считывает показатели системы (CPU, память) в реальном времени.

  2. Обучает нейронную сеть на исторических данных для предсказания вероятности перегрузки.

  3. Корректирует параметры JVM, если прогнозируемая нагрузка превышает порог.

Пример данных для обучения

Для простоты в качестве данных для обучения мы будем использовать CSV-файл jvm_usage.csv, который содержит три столбца:

  • CPU — текущая загрузка процессора,

  • Memory — текущее использование памяти,

  • HighLoad — метка перегрузки (0 или 1), обозначающая, была ли нагрузка высокой.

Пример содержимого CSV:

0.7,3000,0
0.8,4000,1
0.6,2000,0
1.0,4500,1

Настройка многослойной нейронной сети на Java

Для реализации нейронной сети мы используем библиотеку DeepLearning4j. Создадим модель с двумя слоями для обработки входных данных о CPU и памяти и одним выходным нейроном, который будет прогнозировать необходимость увеличения ресурсов JVM.

Подключение необходимых зависимостей

В build.gradle добавьте:

dependencies { 
  implementation ("org.slf4j:slf4j-simple:1.7.32")
  implementation ("org.deeplearning4j:deeplearning4j-core:1.0.0-beta7")
  implementation ("org.nd4j:nd4j-native-platform:1.0.0-beta7")
  implementation ("org.datavec:datavec-api:1.0.0-beta7")
  implementation ("org.datavec:datavec-local:1.0.0-beta7")
  }

Код для настройки и обучения модели

import org.deeplearning4j.nn.api.OptimizationAlgorithm; 
import org.deeplearning4j.nn.conf.MultiLayerConfiguration; 
import org.deeplearning4j.nn.conf.NeuralNetConfiguration; 
import org.deeplearning4j.nn.conf.layers.DenseLayer; 
import org.deeplearning4j.nn.conf.layers.OutputLayer; 
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork; 
import org.nd4j.linalg.activations.Activation; 
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator; 
import org.deeplearning4j.datasets.datavec.RecordReaderDataSetIterator;
import org.nd4j.linalg.learning.config.Nesterovs; 
import org.nd4j.linalg.lossfunctions.LossFunctions; 
import org.nd4j.linalg.api.ndarray.INDArray; 
import org.nd4j.linalg.factory.Nd4j; 
import org.datavec.api.records.reader.impl.csv.CSVRecordReader; 
import org.datavec.api.split.FileSplit;
import java.io.File

public class JVMAIManager {

    private MultiLayerNetwork model;

    public void initializeModel() {
        MultiLayerConfiguration config = new NeuralNetConfiguration.Builder()
                .seed(123)
                .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
                .updater(new Nesterovs(0.1, 0.9))
                .list()
                .layer(0, new DenseLayer.Builder().nIn(2).nOut(10)
                        .activation(Activation.RELU)
                        .build())
                .layer(1, new DenseLayer.Builder().nIn(10).nOut(10)
                        .activation(Activation.RELU)
                        .build())
                .layer(2, new OutputLayer.Builder(LossFunctions.LossFunction.XENT)
                        .activation(Activation.SIGMOID)
                        .nIn(10).nOut(1).build())
                .build();

        model = new MultiLayerNetwork(config);
        model.init();
    }

    public void trainModel(String datasetPath) throws Exception {
        int batchSize = 50;
        int labelIndex = 2;
        int numClasses = 1;

        CSVRecordReader reader = new CSVRecordReader();
        reader.initialize(new FileSplit(new File(datasetPath)));
        DataSetIterator iterator = new RecordReaderDataSetIterator(reader, batchSize, labelIndex, numClasses);

        model.fit(iterator);
    }

    public double predict(double currentCpu, double currentMemory) {
        INDArray input = Nd4j.create(new double[]{currentCpu, currentMemory}, new int[]{1, 2});
        INDArray output = model.output(input);
        return output.getDouble(0);
    }
}

Данный блок кода строит архитектуру нейронной сети, задавая её конфигурацию и порядок слоёв:

  1. Инициализация настроек сети. Мы начинаем с установки базовых параметров сети с помощью NeuralNetConfiguration.Builder(). Здесь задаётся seed (случайное начальное значение для генератора), которое помогает воспроизводить результаты, а также выбирается алгоритм оптимизации — стохастический градиентный спуск (SGD). Этот метод регулирует веса сети, минимизируя ошибку на каждом шаге обучения. Дополнительно, для улучшения и стабилизации процесса обучения, мы подключаем обновляющий алгоритм Nesterovs — метод, который «заглядывает» вперёд, избегая лишних колебаний.

  2. Создание слоёв. Мы добавляем три слоя в сеть:

    • Первый слойDenseLayer, или полносвязный слой, принимает на вход два признака (например, значения загрузки процессора и памяти) и выводит 10 нейронов. Он использует функцию активации ReLU (Rectified Linear Unit), которая сохраняет положительные значения и обнуляет отрицательные. Этот слой помогает сети находить сложные зависимости между признаками.

    • Второй слой — ещё один полносвязный слой с 10 нейронами и функцией активации ReLU. Этот слой обрабатывает и преобразует выходные данные из первого слоя, углубляя сеть для лучшего обучения.

    • Выходной слойOutputLayer, служит для получения конечного предсказания. Здесь мы используем функцию активации Sigmoid, которая выводит значение от 0 до 1. Это полезно для задач бинарной классификации, где результат нужно интерпретировать как вероятность принадлежности к одному из двух классов. Мы также указываем функцию потерь XENT (кросс-энтропия), которая вычисляет расхождение между предсказанием и истинным ответом, помогая сети улучшать точность.

  3. Инициализация модели. С помощью вызова MultiLayerNetwork создаётся нейронная сеть на основе конфигурации, и вызывается model.init(), что завершает процесс построения архитектуры сети и подготавливает её для обучения.

Мониторинг и адаптивная настройка JVM

Добавим классJVMAIAutotuner для мониторинга и динамического управления параметрами JVM:

import java.lang.management.ManagementFactory; 
import com.sun.management.OperatingSystemMXBean; 
import java.util.logging.Logger;

public class JVMAIAutotuner {

    private static final Logger logger = Logger.getLogger(JVMAIAutotuner.class.getName());
    private final OperatingSystemMXBean osBean;
    private final JVMAIManager aiManager;

    public JVMAIAutotuner(JVMAIManager aiManager) {
        this.osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        this.aiManager = aiManager;
    }

    public void monitorAndAdapt() {
        try {
            while (true) {
                double cpuLoad = osBean.getSystemCpuLoad();
                double usedMemory = osBean.getTotalPhysicalMemorySize() - osBean.getFreePhysicalMemorySize();
                double prediction = aiManager.predict(cpuLoad, usedMemory);

                logger.info(String.format("Прогноз вероятности высокой нагрузки: %.2f", prediction));

                if (prediction > 0.8) {
                    logger.info("Высокая вероятность перегрузки. Увеличиваем ресурсы JVM...");
                    increaseHeapMemory();
                }

                Thread.sleep(5000);
            }
        } catch (Exception e) {
            logger.warning("Ошибка в мониторинге и адаптации: " + e.getMessage());
        }
    }

    private void increaseHeapMemory() {
        logger.info("Перезапуск JVM с увеличенной памятью...");
        // Логика перезапуска или конфигурации контейнера
    }
}

Локальный запуск кода

В классе Main создаем экземпляры JVMAIManager и JVMAIAutotuner и запускаем:

public class Main {
    public static void main(String[] args) throws Exception {
        JVMAIManager aiManager = new JVMAIManager();
        aiManager.initializeModel();
        aiManager.trainModel("path/to/jvm_usage.csv");  // Укажите путь к CSV-файлу

        JVMAIAutotuner autotuner = new JVMAIAutotuner(aiManager);
        autotuner.monitorAndAdapt();
    }
}

Результаты и возможные доработки

Такой подход позволяет автоматически управлять ресурсами JVM, прогнозируя высокие нагрузки. Вы можете адаптировать этот код для использования в реальных условиях, например, в среде Kubernetes, добавив поддержку динамической настройки контейнеров через API и интеграцию с системой мониторинга, такой как Prometheus.

Комментарии (2)


  1. RodionGork
    06.11.2024 07:23

    Забавно, только по-моему сама "нейронная сеть" тут не особо нужна. Годится любая, так сказать, "настраиваемая функция" - просто подбираете её параметры по вашему "тренировочному файлу" - хоть популярными методами из вычислительной математики, хоть вообще случайным поиском (да, можно для маркетинга использовать его разновидность под названием "генетический алгоритм") - и вуаля :)

    В частности я думаю нечёткую логику очень легко было можно заюзать. Вы ж регулятор создаёте, на этот счёт уже мегатонны статей в теории автоматического управления написаны.

    Но конечно с "нейронной сетью" внутри что угодно гораздо лучше продаётся :)


  1. sshikov
    06.11.2024 07:23

    Мне кажется, определенная проблема данного решения состоит в том, что автор слишком упростил набор параметров JVM. Отсюда и комментарии выше, что не нужна тут никакая нейронка, а достаточно... Ну в общем да, достаточно - если остальные параметры, влияющие на производительность, проигнорировать, оставив скажем один, то нейронка не нужна. А как только мы их пробуем учесть, мы понимаем, что нейронка-то наверное будет в самый раз. И если вы поищете тут статьи про выбор параметров Apache Spark, то как раз это самое и найдете - т.е. нейронку, и обработку результатов запусков, на которых она обучается.