Matlab Java 互操作性

Matlab Java Interoperability

我们的网络应用程序充当集成层,允许用户 运行 编译为 Java 的 Matlab 代码(Matlab 是一种科学编程语言),通过浏览器打包为 jar 文件(选择的如上图,除了remote_proxy-1.0.0.jar不是,它用于RMI)。

问题在于,包含在 javabuilder-1.0.0.jar 文件中的 Matlab Java 运行time 具有进程范围的阻塞机制,这意味着如果第一个用户发送 HTTP 请求执行 cdf_read-1.0.0.jar 或任何 Matlab-compiled-to-Java jar,然后后续请求将阻塞,直到第一个请求完成,并且由于 JNI 用于调用,因此需要不少于 5 秒本机 Matlab 代码,并且因为应用程序服务器只是生成新线程来服务每个请求,但是再一次,由于 Matlab Java 运行time 的进程范围锁定机制,这些新生成的线程只会阻止等待第一个请求被满足,因此我们的应用程序在技术上可以一次为一个用户服务。

所以为了解决这个问题,对于每个这样的请求,我们启动一个新的 JVM 进程,将请求发送到这个新进程到 运行 使用 RMI 的作业,然后 return 结果回到应用服务器的进程,然后销毁生成的进程。所以我们已经解决了阻塞问题,但这在内存使用方面并不是很好,这是一个小众应用程序,所以用户数量在数千个范围内。下面是用于启动一个新进程到 运行 BootStrap class 的代码,它启动一个新的 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());
        }
    }
}

这个class用于启动一个新的RMI注册表,这样每个执行Matlab代码的HTTP请求都可以运行在一个单独的进程中,我们这样做的原因是因为每个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);
        }
    }
}

此 class 允许提交执行 Matlab 代码的请求,returns 结果,并终止新产生的进程。

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");
        }
    }
}

问题:

  1. 您不希望 JVM 启动时间成为感知事务时间的一部分。我会提前启动大量 RMI JVM,这取决于预期的并发请求数,可能是数百甚至数千。
  2. 您只需要一个注册表:rmiregistry.exe。在其默认端口上启动它并使用适当的 CLASSPATH,以便它可以找到它们所依赖的所有存根和应用程序 类。
  3. 将每个远程对象绑定到具有 sequentially-increasing 一般形式 runner%d 名称的注册表中。
  4. 让您的 RMI 客户端从已知范围 1-N 中随机选择一个 'runner',其中 N 是跑步者的数量。您可能需要一种比单纯的随机性更复杂的机制来确保跑步者当时是空闲的。

您不需要多个注册表端口甚至多个注册表。