Статьи

Оптимизация ресурсов: быстрый старт с Drools Planner

Учебник по балансировке облаков

1. Постановка проблемы

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

Жесткие ограничения, которые должны быть выполнены:

  • Каждый компьютер должен быть в состоянии справиться с минимальными требованиями к оборудованию суммы своих процессов:

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

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

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

Мягкие ограничения, которые должны быть оптимизированы:

  • Каждый компьютер, которому назначен один или несколько процессов, несет затраты на обслуживание (которые фиксированы для каждого компьютера).

    • Минимизируйте общую стоимость обслуживания.

Как бы Вы это сделали? Эта проблема является формой упаковки бункера . Вот упрощенный пример, в котором мы назначаем 4 процесса двум компьютерам с двумя ограничениями (ЦП и ОЗУ) с помощью простого алгоритма:

Вариант использования Cloud Balancing

Простым алгоритмом, используемым здесь, является алгоритм уменьшения первой аппроксимации , который сначала назначает более крупные процессы и назначает меньшие процессы оставшемуся пространству. Как видите, он не оптимален, поскольку не оставляет достаточно места для назначения желтого процесса D.

Drools Planner быстро находит более оптимальное решение, используя дополнительные, более умные алгоритмы. И это также масштабируется: как в данных (больше процессов, больше компьютеров), так и в ограничениях (больше требований к оборудованию, другие ограничения). Итак, давайте посмотрим, как мы можем использовать Planner для этого.

2. Схема доменной модели

Давайте начнем с рассмотрения модели предметной области. Это довольно просто:

  • Компьютер: представляет собой компьютер с определенным аппаратным обеспечением (мощность процессора, оперативная память, пропускная способность сети) и затратами на обслуживание.

  • Процесс: представляет собой процесс со спросом. Должен быть назначен на компьютер Drools Planner.

  • CloudBalance: представляет проблему. Содержит каждый компьютер и процесс для определенного набора данных.

Диаграмма классов облачного баланса

На приведенной выше диаграмме классов UML концепции Planner уже аннотированы:

  • Планируемый объект: класс (или классы), который изменяется во время планирования. В этом примере это класс Process.

  • Переменная планирования: свойство (или свойства) класса объекта планирования, которое изменяется во время планирования. В этих примерах это свойство компьютера в классе Process.

  • Решение: класс, представляющий набор данных и содержащий все объекты планирования. В этом примере это класс CloudBalance.

3. Основной метод

Попробуй сам. Загрузите и настройте примеры в вашей любимой IDE. Запустите org.drools.planner.examples.cloudbalancing.app.CloudBalancingHelloWorld. По умолчанию он настроен на работу в течение 120 секунд. Он выполнит этот код:

CloudBalancingHelloWorld.java

public class CloudBalancingHelloWorld {

    public static void main(String[] args) {
        // Build the Solver
        SolverFactory solverFactory = new XmlSolverFactory(
                "/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
        Solver solver = solverFactory.buildSolver();

        // Load a problem with 400 computers and 1200 processes
        CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(400, 1200);

        // Solve the problem
        solver.setPlanningProblem(unsolvedCloudBalance);
        solver.solve();
        CloudBalance solvedCloudBalance = (CloudBalance) solver.getBestSolution();

        // Display the result
        System.out.println("\nSolved cloudBalance with 400 computers and 1200 processes:\n"
                + toDisplayString(solvedCloudBalance));
    }

    ...

}

Код выше делает это:

  • Постройте Солвер на основе конфигурации Солвера (в данном случае это файл XML).

            SolverFactory solverFactory = new XmlSolverFactory(
                    "/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
            Solver solver = solverFactory.buildSolver();
  • Загрузите проблему. CloudBalancingGenerator создает случайную проблему: вы замените ее классом, который загружает реальную проблему, например, из базы данных.

            CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(400, 1200);
  • Решать проблему.

            solver.setPlanningProblem(unsolvedCloudBalance);
            solver.solve();
            CloudBalance solvedCloudBalance = (CloudBalance) solver.getBestSolution();
  • Показать результат.

            System.out.println("\nSolved cloudBalance with 400 computers and 1200 processes:\n"
                    + toDisplayString(solvedCloudBalance));

Единственная неочевидная часть — это построение Солвера. Давайте рассмотрим это.

4. Конфигурация решателя

Посмотрите на конфигурацию решателя:

cloudBalancingSolverConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<solver>
  <!--<environmentMode>DEBUG</environmentMode>-->

  <!-- Domain model configuration -->
  <solutionClass>org.drools.planner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
  <planningEntityClass>org.drools.planner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>

  <!-- Score configuration -->
  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.drools.planner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
    <!--<scoreDrl>/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
  </scoreDirectorFactory>
  
  <!-- Optimization algorithms configuration -->
  <termination>
    <maximumSecondsSpend>120</maximumSecondsSpend>
  </termination>
  <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
    <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType>
  </constructionHeuristic>
  <localSearch>
    <selector>
      <selector>
        <moveFactoryClass>org.drools.planner.core.move.generic.GenericChangeMoveFactory</moveFactoryClass>
      </selector>
      <selector>
        <moveFactoryClass>org.drools.planner.core.move.generic.GenericSwapMoveFactory</moveFactoryClass>
      </selector>
    </selector>
    <acceptor>
      <planningEntityTabuSize>7</planningEntityTabuSize>
    </acceptor>
    <forager>
      <minimalAcceptedSelection>1000</minimalAcceptedSelection>
    </forager>
  </localSearch>
</solver>

Эта конфигурация решателя состоит из 3 частей:

  • Конфигурация модели домена : что может изменить Planner? Нам нужно, чтобы Planner знал о наших классах домена:

      <solutionClass>org.drools.planner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
      <planningEntityClass>org.drools.planner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>
  • Конфигурация баллов : Как Planner должен оптимизировать переменные планирования? Поскольку у нас есть жесткие и мягкие ограничения, мы используем HardAndSoftScore. Но мы также должны сообщить Planner, как рассчитать такую ​​оценку, в зависимости от требований нашего бизнеса. Далее мы рассмотрим 2 варианта расчета балла: с помощью простой реализации Java или с помощью Drools DRL.

      <scoreDirectorFactory>
        <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
        <simpleScoreCalculatorClass>org.drools.planner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
        <!--<scoreDrl>/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
      </scoreDirectorFactory>
  • Настройка алгоритмов оптимизации : как Planner должен оптимизировать его? Пока не беспокойтесь об этом: это хорошая конфигурация по умолчанию, которая подходит для большинства задач планирования. Он уже превзойдет людей, планирующих и большинство внутренних реализаций. Используя набор инструментов Planner, вы можете настроить его, чтобы получить еще лучшие результаты.

      <termination>
        <maximumSecondsSpend>120</maximumSecondsSpend>
      </termination>
      <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <constructionHeuristicPickEarlyType>FIRST_LAST_STEP_SCORE_EQUAL_OR_IMPROVING</constructionHeuristicPickEarlyType>
      </constructionHeuristic>
      <localSearch>
        <selector>
          <selector>
            <moveFactoryClass>org.drools.planner.core.move.generic.GenericChangeMoveFactory</moveFactoryClass>
          </selector>
          <selector>
            <moveFactoryClass>org.drools.planner.core.move.generic.GenericSwapMoveFactory</moveFactoryClass>
          </selector>
        </selector>
        <acceptor>
          <planningEntityTabuSize>7</planningEntityTabuSize>
        </acceptor>
        <forager>
          <minimalAcceptedSelection>1000</minimalAcceptedSelection>
        </forager>
      </localSearch>

Давайте рассмотрим классы модели предметной области и конфигурацию оценки.

5. Реализация доменной модели

5.1. Класс Компьютерный

Класс Computer — это POJO (обычный старый Java-объект), ничего особенного. Обычно у вас будет больше таких классов.

CloudComputer.java

public class CloudComputer ... {

    private int cpuPower;
    private int memory;
    private int networkBandwidth;
    private int cost;

    ... // getters

}

5.2. Класс Process

Класс Process немного особенный. Нам нужно сообщить Planner, что он может изменить полевой компьютер, поэтому мы аннотируем класс с помощью @PlanningEntity, а getComputer получателя — с @PlanningVariable:

CloudProcess.java

@PlanningEntity(...)
public class CloudProcess ... {

    private int requiredCpuPower;
    private int requiredMemory;
    private int requiredNetworkBandwidth;

    private CloudComputer computer;

    ... // getters

    @PlanningVariable(...)
    @ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "computerList")
    public CloudComputer getComputer() {
        return computer;
    }

    public void setComputer(CloudComputer computer) {
        computer = computer;
    }

    public CloudProcess clone() {
        CloudProcess clone = new CloudProcess();
        clone.id = id;
        clone.requiredCpuPower = requiredCpuPower;
        clone.requiredMemory = requiredMemory;
        clone.requiredNetworkBandwidth = requiredNetworkBandwidth;
        clone.computer = computer;
        return clone;
    }

    ...

}

Значения, которые Planner может выбрать для полевого компьютера, извлекаются из метода в реализации решения: CloudBalance.getComputerList (), который возвращает список всех компьютеров в текущем наборе данных. Мы сообщаем об этом Planner, используя аннотацию @ValueRange.

Метод clone () используется классом CloudBalance.

5.3. Класс CloudBalance

Класс CloudBalance реализует интерфейс решения. Он содержит список всех компьютеров и процессов. Нам нужно сообщить Planner, как получить коллекцию процесса, которую он может изменить, поэтому нам нужно аннотировать getProcessList метода get с помощью @PlanningEntityCollectionProperty.

Класс CloudBalance также имеет оценку свойства, которая является оценкой этого экземпляра решения в его текущем состоянии:

CloudBalance.java

public class CloudBalance ... implements Solution<HardAndSoftScore> {

    private List<CloudComputer> computerList;

    private List<CloudProcess> processList;

    private HardAndSoftScore score;

    public List<CloudComputer> getComputerList() {
        return computerList;
    }

    @PlanningEntityCollectionProperty
    public List<CloudProcess> getProcessList() {
        return processList;
    }

    ...

    public HardAndSoftScore getScore() {
        return score;
    }

    public void setScore(HardAndSoftScore score) {
        this.score = score;
    }

    // ************************************************************************
    // Complex methods
    // ************************************************************************

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(computerList);
        // Do not add the planning entity's (processList) because that will be done automatically
        return facts;
    }

    /**
     * Clone will only deep copy the {@link #processList}.
     */
    public CloudBalance cloneSolution() {
        CloudBalance clone = new CloudBalance();
        clone.id = id;
        clone.computerList = computerList;
        List<CloudProcess> clonedProcessList = new ArrayList<CloudProcess>(
                processList.size());
        for (CloudProcess process : processList) {
            CloudProcess clonedProcess = process.clone();
            clonedProcessList.add(clonedProcess);
        }
        clone.processList = clonedProcessList;
        clone.score = score;
        return clone;
    }

    ...

}

Метод getProblemFacts () необходим только для расчета баллов с помощью Drools. Это не нужно для других типов расчета баллов.

Требуется метод clone (). Планировщик использует его для создания клона лучшего Решения во встречах во время поиска.

6. Оценка конфигурации

Планировщик будет искать решение с наивысшим баллом. Мы используем HardAndSoftScore, что означает, что Planner будет искать решение без жестких ограничений (выполнение требований к оборудованию) и с минимальными нарушениями мягких ограничений (минимизация затрат на обслуживание).

Есть несколько способов реализовать функцию оценки:

  • Простая Java

  • Добавочная Java

  • Drools

Давайте посмотрим на 2 разных реализации:

6.1. Простая настройка партитуры Java

Одним из способов определения функции оценки является реализация интерфейса SimpleScoreCalculator в простой Java.

  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.drools.planner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
  </scoreDirectorFactory>

Просто реализуйте метод calcScore (Solution), чтобы вернуть экземпляр DefaultHardAndSoftScore.

CloudBalancingSimpleScoreCalculator.java

public class CloudBalancingSimpleScoreCalculator implements SimpleScoreCalculator<CloudBalance> {

    /**
     * A very simple implementation. The double loop can easily be removed by using Maps as shown in
     * {@link CloudBalancingMapBasedSimpleScoreCalculator#calculateScore(CloudBalance)}.
     */
    public HardAndSoftScore calculateScore(CloudBalance cloudBalance) {
        int hardScore = 0;
        int softScore = 0;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            int cpuPowerUsage = 0;
            int memoryUsage = 0;
            int networkBandwidthUsage = 0;
            boolean used = false;

            // Calculate usage
            for (CloudProcess process : cloudBalance.getProcessList()) {
                if (computer.equals(process.getComputer())) {
                    cpuPowerUsage += process.getRequiredCpuPower();
                    memoryUsage += process.getRequiredMemory();
                    networkBandwidthUsage += process.getRequiredNetworkBandwidth();
                    used = true;
                }
            }
            
            // Hard constraints
            int cpuPowerAvailable = computer.getCpuPower() - cpuPowerUsage;
            if (cpuPowerAvailable < 0) {
                hardScore += cpuPowerAvailable;
            }
            int memoryAvailable = computer.getMemory() - memoryUsage;
            if (memoryAvailable < 0) {
                hardScore += memoryAvailable;
            }
            int networkBandwidthAvailable = computer.getNetworkBandwidth() - networkBandwidthUsage;
            if (networkBandwidthAvailable < 0) {
                hardScore += networkBandwidthAvailable;
            }
            
            // Soft constraints
            if (used) {
                softScore -= computer.getCost();
            }
        }
        return DefaultHardAndSoftScore.valueOf(hardScore, softScore);
    }

}

Даже если мы оптимизируем приведенный выше код, чтобы использовать Maps для итерации по списку процессов только один раз, он все равно будет медленным, поскольку он не выполняет вычисление добавочной оценки. Чтобы исправить это, используйте функцию добавочной оценки Java или функцию оценки Drools.

6.2. Слюни настройка счета

Чтобы использовать механизм правил Drools в качестве функции оценки, просто добавьте ресурс ScoreDrl в путь к классам:

  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_AND_SOFT</scoreDefinitionType>
    <scoreDrl>/org/drools/planner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

Во-первых, мы хотим убедиться, что на всех компьютерах достаточно ресурсов ЦП, ОЗУ и пропускной способности сети для поддержки всех их процессов, поэтому мы устанавливаем следующие жесткие ограничения:

cloudBalancingScoreRules.drl — жесткие ограничения

...

import org.drools.planner.examples.cloudbalancing.domain.CloudBalance;
import org.drools.planner.examples.cloudbalancing.domain.CloudComputer;
import org.drools.planner.examples.cloudbalancing.domain.CloudProcess;

global HardAndSoftScoreHolder scoreHolder;

// ############################################################################
// Hard constraints
// ############################################################################

rule "requiredCpuPowerTotal"
    when
        $computer : CloudComputer($cpuPower : cpuPower)
        $requiredCpuPowerTotal : Number(intValue > $cpuPower) from accumulate(
            CloudProcess(
                computer == $computer,
                $requiredCpuPower : requiredCpuPower),
            sum($requiredCpuPower)
        )
    then
        insertLogical(new IntConstraintOccurrence("requiredCpuPowerTotal", ConstraintType.NEGATIVE_HARD,
                $requiredCpuPowerTotal.intValue() - $cpuPower,
                $computer));
end

rule "requiredMemoryTotal"
    ...
end

rule "requiredNetworkBandwidthTotal"
    ...
end

// ############################################################################
// Calculate hard score
// ############################################################################

// Accumulate hard constraints
rule "hardConstraintsBroken"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $hardTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_HARD, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setHardConstraintsBroken($hardTotal.intValue());
end

Далее, если эти ограничения выполнены, мы хотим минимизировать затраты на обслуживание, поэтому добавим это в качестве мягкого ограничения:

cloudBalancingScoreRules.drl — мягкие ограничения

// ############################################################################
// Soft constraints
// ############################################################################

rule "computerCost"
    when
        $computer : CloudComputer($cost : cost)
        exists CloudProcess(computer == $computer)
    then
        insertLogical(new IntConstraintOccurrence("computerCost", ConstraintType.NEGATIVE_SOFT,
                $cost,
                $computer));
end

// ############################################################################
// Calculate soft score
// ############################################################################

// Accumulate soft constraints
rule "softConstraintsBroken"
        salience -1 // Do the other rules first (optional, for performance)
    when
        $softTotal : Number() from accumulate(
            IntConstraintOccurrence(constraintType == ConstraintType.NEGATIVE_SOFT, $weight : weight),
            sum($weight)
        )
    then
        scoreHolder.setSoftConstraintsBroken($softTotal.intValue());
end

Если вы используете механизм правил Drools для подсчета очков, вы можете интегрироваться с другими технологиями Drools, такими как таблицы решений (на основе XSL или через Интернет), репозиторий правил Guvnor, …

7. Помимо этого урока

Теперь, когда этот простой пример работает, вы можете пойти дальше. Попробуйте обогатить модель предметной области и добавить дополнительные ограничения:

  • Каждый процесс принадлежит Сервису. Компьютер может зависнуть, поэтому процессы, выполняющие одну и ту же службу, должны быть назначены разным компьютерам.

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

Для получения дополнительной информации о Drools Planner обратитесь к справочному руководству .