Выполнение ресурсоемких задач на Java в виртуальной машине Windows Azure

Windows Azure позволяет использовать виртуальную машину для выполнения ресурсоемких задач. Виртуальная машина может выполнять задачи и выводить их результат на клиентские компьютеры или мобильные приложения. Это руководство посвящено созданию виртуальной машины, на которой может выполняться ресурсоемкое Java-приложение. Результаты работы этого приложения можно контролировать с помощью другого Java-приложения.

Предполагается, что вы умеете создавать консольные Java-приложения и архивы Java (JAR), а также импортировать библиотеки в Java-приложение. Опыта работы с Windows Azure не требуется.

О чем пойдет речь в этом руководстве:

  • Как создать виртуальную машину.
  • Как удаленно войти в систему на виртуальной машине.
  • Как установить на виртуальной машине JRE или JDK.
  • Как создать пространство имен шины обслуживания.
  • Как создать Java-приложение, выполняющее ресурсоемкую задачу.
  • Как создать Java-приложение, отслеживающее ход выполнения ресурсоемкой задачи.
  • Как запустить Java-приложение.
  • Как остановить Java-приложение.

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

clip_image001

Ниже приведен пример Java-приложения, отслеживающего выполнение ресурсоемкой задачи:

clip_image002

Примечание. Для выполнения действий, описываемых в этом руководстве, необходима учетная запись Windows Azure с активированной функцией Виртуальные машины Windows Azure. Создать бесплатную пробную учетную запись и активировать функции для предварительного ознакомления можно всего за несколько минут. Для получения дополнительных сведений см. Create a Windows Azure account and enable preview features (Создание учетной записи Windows Azure и включение функций для предварительного ознакомления).

Как создать виртуальную машину. Как удаленно войти в систему на виртуальной машине. Как установить на виртуальной машине JRE или JDK

Вы можете ознакомиться с этими вопросами в предыдущей статье Запуск сервера приложений Java на виртуальной машине Windows Azure.

Обратите внимание, что для корректной работы шины обслуживания необходимо установить глобальный корневой сертификат GTE CyberTrust в хранилище cacerts JRE. Этот сертификат уже входит в комплект JRE, используемый в данном руководстве. Если в хранилище JRE cacerts этот сертификат отсутствует, установите его следующим образом: скопируйте содержимое сертификата со страницы https://secure.omniroot.com/cacert/ct_root.der, сохраните файл с расширением .cer и добавьте его в хранилище cacertsс помощью keytool. Для получения подробных инструкций по добавлению сертификата в хранилище cacerts см. Adding a Certificate to the Java CA Certificate Store (Добавление сертификата в хранилище сертификатов Java CA).

Как создать пространство имен шины обслуживания

Перед тем как приступить к работе с очередями шины обслуживания Windows Azure, нужно создать пространство имен службы. Пространство имен службы представляет собой контейнер, задающий область действия ресурсов шины обслуживания в приложении.

Примечание. Здесь описывается процесс создания шины обслуживания с помощью старого портала. Вы можете проделать все действия и в новом портале по адресу https://manage.windowsazure.com.

Чтобы создать пространство имен службы, выполните следующие действия:

Войдите в систему на портале управления Windows Azure (обратите внимание: это не портал управления ознакомительной версией Windows Azure). В левой нижней панели навигации портала управления щелкните Service Bus, Access Control & Caching (Шина обслуживания, управление доступом и кэширование) . В левой верхней панели портала управления выберите Service Bus (Шина обслуживания) и нажмите кнопку New (Создать) .

clip_image003

В диалоговом окне Create a new Service Namespace (Создать новое пространство имен службы) введите название пространства имен в поле Namespace (Пространство имен) и нажмите кнопку Check Availability (Проверить доступность) , чтобы проверить его уникальность.

clip_image004

Убедившись, что название доступно, выберите страну или регион, где будет размещаться новое пространство имен; затем нажмите кнопку Create Namespace (Создать пространство имен) .

Созданное пространство имен появится на портале управления. Его активация займет несколько секунд. Подождите, пока его состояние не изменится на Active (Активно) , и переходите к следующему шагу.

Получение учетных данных по умолчанию для управления пространством имен

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

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

clip_image003[1]

В списке выберите только что созданное пространство имен.

clip_image005

В правой панели Properties (Свойства) будет отображен список свойств нового пространства имен.

clip_image006

Default Key (Ключ по умолчанию) скрыт. Нажмите кнопку View (Просмотр) , чтобы отобразить учетные данные для безопасного доступа.

clip_image007

Запомните или запишите значения полей Default Issuer (Отправитель по умолчанию) и Default Key — в дальнейшем они потребуются для работы с этим пространством имен.

Как создать приложение Java, выполняющее ресурсоемкую задачу

Скачайте Windows Azure SDK для Java на компьютер, который используется для разработки (это не обязательно должна быть только что созданная виртуальная машина). Создайте консольное Java-приложение, используя приведенный в этом разделе исходный код. В этом случае мы будем использовать TSPSolver.java в качестве имени файла Java. Вместо заполнителей your_service_bus_namespace, your_service_bus_owner и your_service_bus_key укажите параметры вашей шины обслуживания: namespace, Default Issuer и Default Key соответственно.

После кодировки экспортируйте приложение в готовый к выполнению архив Java (JAR) и добавьте в него необходимые библиотеки. В этом случае мы будем использовать TSPSolver.jar в качестве имени созданного JAR-архива.

// TSPSolver.java

import com.microsoft.windowsazure.services.core.Configuration;
import com.microsoft.windowsazure.services.core.ServiceException;
import com.microsoft.windowsazure.services.serviceBus.*;
import com.microsoft.windowsazure.services.serviceBus.models.*;
import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TSPSolver {

    // Value specifying how often to provide an update to the console.
    private static long loopCheck = 100000000;  

    private static long nTimes = 0, nLoops=0;

    private static double[][] distances;
    private static String[] cityNames;
    private static int[] bestOrder;
    private static double minDistance;
    private static ServiceBusContract service;

    private static void buildDistances(String fileLocation, int numCities) throws Exception{
        try{
            BufferedReader file = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(new File(fileLocation)))));
            double[][] cityLocs = new double[numCities][2];
            for (int i = 0; i<numCities; i++){
                String[] line = file.readLine().split(", ");
                cityNames[i] = line[0];
                cityLocs[i][0] = Double.parseDouble(line[1]);
                cityLocs[i][1] = Double.parseDouble(line[2]);              
            }
            for (int i = 0; i<numCities; i++){
                for (int j = i; j<numCities; j++){
                    distances[i][j] = Math.hypot(Math.abs(cityLocs[i][0] - cityLocs[j][0]), Math.abs(cityLocs[i][1] - cityLocs[j][1]));
                    distances[j][i] = distances[i][j];
                }
            }
        } catch (Exception e){
            throw e;
        }
    }

    private static void permutation(List<Integer> startCities, double distSoFar, List<Integer> restCities) throws Exception {

        try
        {
            nTimes++;
            if (nTimes == loopCheck)
            {
                nLoops++;
                nTimes = 0;
                DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
                Date date = new Date();
                System.out.print("Current time is " + dateFormat.format(date) + ". ");
                System.out.println(  "Completed " + nLoops + " iterations of size of " + loopCheck + ".");
            }

            if ((restCities.size() == 1) && ((minDistance == -1) || (distSoFar + distances[restCities.get(0)][startCities.get(0)] + distances[restCities.get(0)][startCities.get(startCities.size()-1)] < minDistance))){
                startCities.add(restCities.get(0));
                newBestDistance(startCities, distSoFar + distances[restCities.get(0)][startCities.get(0)] + distances[restCities.get(0)][startCities.get(startCities.size()-2)]);
                startCities.remove(startCities.size()-1);
            }
            else{
                for (int i=0; i<restCities.size(); i++){
                    startCities.add(restCities.get(0));
                    restCities.remove(0);
                    permutation(startCities, distSoFar + distances[startCities.get(startCities.size()-1)][startCities.get(startCities.size()-2)],restCities);
                    restCities.add(startCities.get(startCities.size()-1));
                    startCities.remove(startCities.size()-1);
                }
            }
        }
        catch (Exception e)
        {
            throw e;
        }
    }

    private static void newBestDistance(List<Integer> cities, double distance) throws ServiceException, Exception {
        try
        {
            minDistance = distance;
            String cityList = "Shortest distance is "+minDistance+", with route: ";
            for (int i = 0; i<bestOrder.length; i++){
                bestOrder[i] = cities.get(i);
                cityList += cityNames[bestOrder[i]];
                if (i != bestOrder.length -1)
                    cityList += ", ";
            }
            System.out.println(cityList);
            service.sendQueueMessage("TSPQueue", new BrokeredMessage(cityList));
        }
        catch (ServiceException se)
        {
            throw se;
        }
        catch (Exception e)
        {
            throw e;
        }
    }

    public static void main(String args[]){

        try {

            Configuration config = ServiceBusConfiguration.configureWithWrapAuthentication(
                    "your_service_bus_namespace", "your_service_bus_owner",
                    "your_service_bus_key",
                    ".servicebus.windows.net",
                    "-sb.accesscontrol.windows.net/WRAPv0.9");

            service = ServiceBusService.create(config);

            int numCities = 10;  // Use as the default, if no value is specified at command line.
            if (args.length != 0)
            {
                if (args[0].toLowerCase().compareTo("createqueue")==0)
                {
                    // No processing to occur other than creating the queue.
                    QueueInfo queueInfo = new QueueInfo("TSPQueue");

                    service.createQueue(queueInfo);

                    System.out.println("Queue named TSPQueue was created.");

                    System.exit(0);
                }

                if (args[0].toLowerCase().compareTo("deletequeue")==0)
                {
                    // No processing to occur other than deleting the queue.
                    service.deleteQueue("TSPQueue");

                    System.out.println("Queue named TSPQueue was deleted.");

                    System.exit(0);
                }

                // Neither creating or deleting a queue.
                // Assume the value passed in is the number of cities to solve.
                numCities = Integer.valueOf(args[0]);  
            }

            System.out.println("Running for " + numCities + " cities.");

            List<Integer> startCities = new ArrayList<Integer>();
            List<Integer> restCities = new ArrayList<Integer>();
            startCities.add(0);
            for(int i = 1; i<numCities; i++)
                restCities.add(i);
            distances = new double[numCities][numCities];
            cityNames = new String[numCities];
            buildDistances("c:\\TSP\\cities.txt", numCities);
            minDistance = -1;
            bestOrder = new int[numCities];
            permutation(startCities, 0, restCities);
            System.out.println("Final solution found!");
            service.sendQueueMessage("TSPQueue", new BrokeredMessage("Complete"));
        }
        catch (ServiceException se)
        {
            System.out.println(se.getMessage());
            se.printStackTrace();
            System.exit(-1);
        }        
        catch (Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
            System.exit(-1);
        }
    }

}

Как создать Java-приложение, отслеживающее ход выполнения ресурсоемкой задачи

На предназначенном для разработки компьютере создайте консольное Java-приложение, используя приведенный в этом разделе исходный код. В этом случае мы будем использовать TSPClient.java в качестве имени файла Java. Как и в примере выше, вместо заполнителей your_service_bus_namespace, your_service_bus_owner и your_service_bus_key укажите параметры вашей шины обслуживания: namespace, Default Issuer и Default Key соответственно.

Экспортируйте приложение в исполняемый архив Java (JAR) и добавьте в него необходимые библиотеки. В этом случае мы будем использовать TSPClient.jar в качестве имени созданного JAR-архива.

// TSPClient.java

import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import com.microsoft.windowsazure.services.serviceBus.*;
import com.microsoft.windowsazure.services.serviceBus.models.*;
import com.microsoft.windowsazure.services.core.*;

public class TSPClient
{

    public static void main(String[] args)
    {
            try
            {

                DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
                Date date = new Date();
                System.out.println("Starting at " + dateFormat.format(date) + ".");

                String namespace = "your_service_bus_namespace";
                String issuer = "your_service_bus_owner";
                String key = "your_service_bus_key";

                Configuration config;
                config = ServiceBusConfiguration.configureWithWrapAuthentication(
                        namespace, issuer, key,
                        ".servicebus.windows.net",
                        "-sb.accesscontrol.windows.net/WRAPv0.9");

                ServiceBusContract service = ServiceBusService.create(config);

                BrokeredMessage message;

                int waitMinutes = 3;  // Use as the default, if no value is specified at command line.
                if (args.length != 0)
                {
                    waitMinutes = Integer.valueOf(args[0]);  
                }

                String waitString;

                waitString = (waitMinutes == 1) ? "minute." : waitMinutes + " minutes.";

                // This queue must have previously been created.
                service.getQueue("TSPQueue");

                int numRead;

                String s = null;

                while (true)
                {

                    ReceiveQueueMessageResult resultQM = service.receiveQueueMessage("TSPQueue");
                    message = resultQM.getValue();

                    if (null != message && null != message.getMessageId())
                    {                        

                        // Display the queue message.
                        byte[] b = new byte[200];

                        System.out.print("From queue: ");

                        s = null;
                        numRead = message.getBody().read(b);
                        while (-1 != numRead)
                        {
                            s = new String(b);
                            s = s.trim();
                            System.out.print(s);
                            numRead = message.getBody().read(b);
                        }
                        System.out.println();
                        if (s.compareTo("Complete") == 0)
                        {
                            // No more processing to occur.
                            date = new Date();
                            System.out.println("Finished at " + dateFormat.format(date) + ".");
                            break;
                        }
                    }
                    else
                    {
                        // The queue is empty.
                        System.out.println("Queue is empty. Sleeping for another " + waitString);
                        Thread.sleep(60000 * waitMinutes);
                    }
                }

        }
        catch (ServiceException se)
        {
            System.out.println(se.getMessage());
            se.printStackTrace();
            System.exit(-1);
        }
        catch (Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
            System.exit(-1);
        }

    }

}

Как запустить Java-приложение

Запустите ресурсоемкое приложение — вначале для создания очереди, потом для решения задачи коммивояжера. Лучший из найденных маршрутов будет добавлен в очередь шины обслуживания. В ходе (или после завершения) ресурсоемких вычислений запустите клиент для отображения результатов из очереди шины обслуживания.

Как запустить ресурсоемкое приложение

Войдите в систему на виртуальной машине. Создайте каталог, в котором будет работать приложение. Например, c:\TSP. Скопируйте файл TSPSolver.jar в c:\TSP.

Создайте файл c:\TSP\cities.txt со следующим содержимым:

City_1, 1002.81, -1841.35
City_2, -953.55, -229.6
City_3, -1363.11, -1027.72
City_4, -1884.47, -1616.16
City_5, 1603.08, -1030.03
City_6, -1555.58, 218.58
City_7, 578.8, -12.87
City_8, 1350.76, 77.79
City_9, 293.36, -1820.01
City_10, 1883.14, 1637.28
City_11, -1271.41, -1670.5
City_12, 1475.99, 225.35
City_13, 1250.78, 379.98
City_14, 1305.77, 569.75
City_15, 230.77, 231.58
City_16, -822.63, -544.68
City_17, -817.54, -81.92
City_18, 303.99, -1823.43
City_19, 239.95, 1007.91
City_20, -1302.92, 150.39
City_21, -116.11, 1933.01
City_22, 382.64, 835.09
City_23, -580.28, 1040.04
City_24, 205.55, -264.23
City_25, -238.81, -576.48
City_26, -1722.9, -909.65
City_27, 445.22, 1427.28
City_28, 513.17, 1828.72
City_29, 1750.68, -1668.1
City_30, 1705.09, -309.35
City_31, -167.34, 1003.76
City_32, -1162.85, -1674.33
City_33, 1490.32, 821.04
City_34, 1208.32, 1523.3
City_35, 18.04, 1857.11
City_36, 1852.46, 1647.75
City_37, -167.44, -336.39
City_38, 115.4, 0.2
City_39, -66.96, 917.73
City_40, 915.96, 474.1
City_41, 140.03, 725.22
City_42, -1582.68, 1608.88
City_43, -567.51, 1253.83
City_44, 1956.36, 830.92
City_45, -233.38, 909.93
City_46, -1750.45, 1940.76
City_47, 405.81, 421.84
City_48, 363.68, 768.21
City_49, -120.3, -463.13
City_50, 588.51, 679.33

В командной строке перейдите в каталог c:\TSP. Убедитесь, что путь к папке bin для файлов JRE указан в переменной среды PATH.

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

java -jar TSPSolver.jar createqueue

Теперь, когда очередь создана, можно запускать приложение, решающее задачу коммивояжера. Например, следующая команда решает задачу для восьми городов.

java -jar TSPSolver.jar 8

Если вы не укажете число, задача будет решаться для 10 городов. Текущее оптимальное решение выводится в очередь.

Примечание. Чем больше указанное число, тем дольше будет работать приложение. Например, решение для 14 городов может занять несколько минут, а для 15 городов — несколько часов. Увеличение числа до 16 и более приведет к увеличению продолжительности работы программы до нескольких дней (недель, месяцев или лет). Дело в том, что с увеличением числа городов количество вариантов решения, проверяемых программой, растет очень быстро.

Как запустить клиентское приложение для мониторинга работы программы

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

Создайте каталог, в котором будет работать приложение. Например, c:\TSP. Скопируйте файл TSPClient.jar в c:\TSP. Убедитесь, что путь к папке bin для файлов JRE указан в переменной среды PATH.

В командной строке перейдите в каталог c:\TSP. Выполните следующую команду:

java -jar TSPClient.jar

С помощью необязательного аргумента командной строки можно указать периодичность проверки очереди в минутах. По умолчанию, если TSPClient запущен без дополнительных аргументов, клиентское приложение опрашивает очередь каждые три минуты. Если вы желаете использовать иное значение, например одну минуту, введите:

java -jar TSPClient.jar 1

Клиентское приложение будет работать, пока из очереди не будет прочитано сообщение "Complete". Обратите внимание: если вы запустили несколько экземпляров решающего приложения, не запуская соответствующие клиенты, то для полной очистки очереди может потребоваться многократный запуск клиента. Можно также удалить очередь, а затем создать ее снова. Для удаления очереди введите следующую команду для TSPSolver (не TSPClient):

java -jar TSPSolver.jar deletequeue

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

Как остановить Java-приложение

Чтобы остановить выполнение как решающего, так и клиентского приложения, нажмите клавиши CTRL+C, не дожидаясь нормального завершения работы приложения.

Это перевод оригинальной статьи How to run a compute-intensive task in Java on a virtual machine