Matlab Java Interoperability

Matlab-скомпилированный в Java код

Наше веб-приложение действует как слой интеграции, который позволяет пользователям запускать код Matlab (Matlab - научный язык программирования), который был скомпилирован в Java, упакован в виде файлов JAR через браузер (выбранные как на изображении выше, за исключением remote_proxy-1.0.0.jar это не так, он используется для RMI).

Проблема заключается в том, что Matlab Java Runtime, содержащийся внутри javabuilder-1.0.0.jar файл, имеет механизм блокировки всего процесса, который означает, что если первый пользователь отправляет HTTP-запрос на выполнение cdf_read-1.0.0.jar или любые jar-файлы Matlab-compiled-to-Java, затем последующие запросы будут блокироваться, пока не завершится первый, и это займет не менее 5 секунд, поскольку JNI используется для вызова нативного кода Matlab и поскольку сервер приложений просто порождает новые потоки для обслуживания каждого запроса, но еще раз, благодаря механизму блокировки всего процесса времени исполнения Java Matlab, эти вновь созданные потоки будут просто блокировать ожидание выполнения первого запроса, таким образом, наше приложение может технически обслуживать одного пользователя за раз,

Таким образом, чтобы обойти эту проблему, для каждого такого запроса мы запускаем новый процесс JVM, отправляем запрос этому новому процессу для запуска задания с использованием RMI, затем возвращаем результат обратно процессу сервера приложений, а затем уничтожаем порожденный процесс. Итак, мы решили проблему блокировки, но это не очень хорошо с точки зрения используемой памяти, это нишевое приложение, поэтому число пользователей находится в диапазоне громких облаков. Ниже приведен код, используемый для запуска нового процесса для запуска BootStrap Класс, который запускает новый реестр RMI и привязывает удаленный объект для запуска задания.

package rmi;

import java.io.*;
import java.nio.file.*;
import static java.util.stream.Collectors.joining;
import java.util.stream.Stream;
import javax.enterprise.concurrent.ManagedExecutorService;
import org.slf4j.LoggerFactory;
//TODO: Remove sout
public class ProcessInit {

    public static Process startRMIServer(ManagedExecutorService pool, String WEBINF, int port, String jar) {
        ProcessBuilder pb = new ProcessBuilder();
        Path wd = Paths.get(WEBINF);
        pb.directory(wd.resolve("classes").toFile());
        Path lib = wd.resolve("lib");
        String cp = Stream.of("javabuilder-1.0.0.jar", "remote_proxy-1.0.0.jar", jar)
                .map(e -> lib.resolve(e).toString())
                .collect(joining(File.pathSeparator));
        pb.command("java", "-cp", "." + File.pathSeparator + cp, "rmi.BootStrap", String.valueOf(port));
        while (true) {
            try {
                Process p = pb.start();
                pool.execute(() -> flushIStream(p.getInputStream()));
                pool.execute(() -> flushIStream(p.getErrorStream()));
                return p;
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println("Retrying....");
            }
        }
    }

    private static void flushIStream(InputStream is) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
            br.lines().forEach(System.out::println);
        } catch (IOException ex) {
            LoggerFactory.getLogger(ProcessInit.class.getName()).error(ex.getMessage());
        }
    }
}

Этот класс используется для запуска нового реестра RMI, поэтому каждый HTTP-запрос на выполнение кода Matlab может выполняться в отдельном процессе, поэтому мы делаем это потому, что каждый реестр RMI связан с процессом, поэтому нам нужен отдельный реестр для каждого Процесс JVM.

package rmi;

import java.rmi.RemoteException;
import java.rmi.registry.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.logging.*;
import remote_proxy.*;
//TODO: Remove sout
public class BootStrap {

    public static void main(String[] args) {
        int port = Integer.parseInt(args[0]);
        System.out.println("Instantiating a task runner implementation on port: "  + port );
        try {
            System.setProperty("java.rmi.server.hostname", "localhost");
            TaskRunner runner = new TaskRunnerRemoteObject();
            TaskRunner stub = (TaskRunner)UnicastRemoteObject.exportObject(runner, 0);
            Registry reg = LocateRegistry.createRegistry(port);
            reg.rebind("runner" + port, stub);
        } catch (RemoteException ex) {
            Logger.getLogger(BootStrap.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Этот класс позволяет отправить запрос на выполнение кода Matlab, вернуть результат и уничтожить вновь созданный процесс.

package rmi.tasks;

import java.rmi.*;
import java.rmi.registry.*;
import java.util.Random;
import java.util.concurrent.*;
import java.util.logging.*;
import javax.enterprise.concurrent.ManagedExecutorService;
import remote_proxy.*;
import rmi.ProcessInit;

public final class Tasks {

    /**
     * @param pool This instance should be injected using @Resource(name = "java:comp/DefaultManagedExecutorService")
     * @param task This is an implementation of the Task interface, this
     *             implementation should extend from MATLAB class and accept any necessary
     *             arguments, e.g Class1 and it must implement Serializable interface
     * @param WEBINF WEB-INF directory
     * @param jar  Name of the jar that contains this MATLAB function
     * @throws RemoteException
     * @throws NotBoundException
     */
    public static final <T> T runTask(ManagedExecutorService pool, Task<T> task, String WEBINF, String jar) throws RemoteException, NotBoundException {
        int port = new Random().nextInt(1000) + 2000;
        Future<Process> process = pool.submit(() -> ProcessInit.startRMIServer(pool, WEBINF, port, jar));
        Registry reg = LocateRegistry.getRegistry(port);
        TaskRunner generator = (TaskRunner) reg.lookup("runner" + port);
        T result = generator.runTask(task);
        destroyProcess(process);
        return result;
    }

    private static void destroyProcess(Future<Process> process) {
        try {
            System.out.println("KILLING THIS PROCESS");
            process.get().destroy();
            System.out.println("DONE KILLING THIS PROCESS");
        } catch (InterruptedException | ExecutionException ex) {
            Logger.getLogger(Tasks.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("DONE KILLING THIS PROCESS");
        }
    }
}

Вопросы:

  • Нужно ли мне запускать новый отдельный реестр RMI и привязывать к нему пульт дистанционного управления для каждого нового процесса?
  • Есть ли лучший способ достичь того же результата?

1 ответ

Решение
  1. Вы не хотите, чтобы время запуска JVM было частью предполагаемого времени транзакции. Я бы запустил большое количество JVM RMI заранее, в зависимости от ожидаемого количества одновременных запросов, которое может исчисляться сотнями или даже тысячами.
  2. Вам нужен только один реестр: rmiregistry.exe, Запустите его на порте по умолчанию и с соответствующим CLASSPATH, чтобы он мог найти все ваши заглушки и классы приложений, от которых они зависят.
  3. Свяжите каждый удаленный объект в этот реестр с последовательно увеличивающимися именами общего вида. runner%d,
  4. Пусть ваш клиент RMI выберет "бегуна" случайным образом из известного диапазона 1-N, где N - количество бегунов. Вам может понадобиться более сложный механизм, чем просто случайность, чтобы гарантировать, что бегун свободен в то время.

Вам не нужно несколько портов реестра или даже несколько реестров.

Другие вопросы по тегам