本地JVM之间的通信

26

我的问题:在本地运行的两个或多个JVM实例之间进行通信,我应该采取什么方法?

问题描述:
我正在为一个需要将某些任务完全隔离的项目开发系统,需要使用单独的JVM实例。

在运行中,“父”JVM将创建“子”JVM,预计它将执行并返回结果(以相对简单的POJO类或结构化XML数据的格式)。这些结果不应使用SysErr / SysOut / SysIn管道传输,因为子进程可能已经将其作为其运行的一部分使用。

如果子JVM在一定时间内没有响应结果,则父JVM应能够向子JVM发出信号以停止处理或杀死子进程。否则,在完成其任务后,子JVM应正常退出。

研究工作:
我知道有许多技术可以使用,例如...

  • 使用Java的RMI库
  • 使用套接字传输对象
  • 使用分布式库,如Cajo、Hessian

...但我想听听其他人在追求这些选项或其他任何选项之前可能考虑的方法。

感谢您提供的任何帮助或建议!

编辑:
要传输的数据量-相对较小,它将主要是一些包含字符串的POJO,表示子进程执行结果。如果任何解决方案在较大数据量上效率低下,则这在我的系统中不太可能成为问题。要传输的数量应该是固定的,因此这不需要具有可扩展性。

传输延迟-在这种情况下不是关键问题,尽管如果需要“轮询”结果,则应该能够相当频繁地进行,而没有显着的开销,因此我可以在以后维护一个响应式图形界面(例如进度条)。


也许您可以谈谈需要传输多少数据以及延迟有多重要?除非您有重大的性能限制,否则无论您认为最简单的解决方案可能是最好的。 - Peter Lawrey
@Peter Lawrey,我已经为您添加了一些额外的细节,谢谢! - obfuscation
11个回答

6

这不是你问题的直接答案,而是一个替代方案的建议。你考虑过 OSGI 吗?

它允许你在同一个jvm中完全隔离地运行Java项目,彼此之间没有任何交流。它的美妙之处在于,通过服务进行项目之间的通信非常容易(请参见Core Specifications PDF第123页)。这样,数据和调用都在同一个jvm中,不需要进行任何形式的序列化。

此外,你所有对服务质量(响应时间等)的要求都消失了 – 你只需要担心在想要使用服务的时候它是否正常运行。为此,你有一个真正好用的规范来帮助你完成这个任务,叫做声明式服务(请参见Enterprise Spec PDF第141页)。

很抱歉回答有些跑题,但我认为其他人可能会考虑这个替代方案。

更新

回答您关于安全性的问题,我从未考虑过这种情况。我不认为有一种方法可以在OSGI内强制执行“内存”使用。
然而,在不同的OSGI运行时之间有一种在JVM之外进行通信的方式。它被称为远程服务(请参见Enterprise Spec PDF第7页)。他们在那里还讨论了在执行此类操作时要考虑的因素(请参见13.1谬论)。
Apache Felix的人们(OSGI的实现)我想已经使用iPOJO实现了这个,称为Distributed Services with iPOJO(他们的包装器使使用服务更加容易)。我从未使用过这个 - 如果我错了,请忽略我。

不需要道歉,这是一个有趣的建议。我面临的问题之一是,由子JVM执行的代码部分是不受信任的,因此必须进行限制(例如通过安全管理器)。作为其中的一部分,我需要限制内存使用,以确保父JVM有足够的资源可用。然而,从我所了解的情况来看,我无法在JVM内限制内存使用,以防止内存不足异常。OSGI是否提供任何保护/调节措施? - obfuscation

5
我会建议使用KryoNet和本地套接字,因为它专门处理序列化并且非常轻量级(同时还可以获得远程方法调用!我现在正在使用它),但要禁用套接字断开连接的超时时间。
RMI基本上是基于这样的原则工作的:您有一个远程类型,该远程类型实现了一个接口。这个接口是共享的。在您的本地机器上,通过RMI库将接口绑定到从RMI库中“注入”到内存中的代码,结果是您拥有了满足接口但能够与远程对象通信的内容。

一些问题与网络类的注册有关,以及与初始注册有关的竞争条件。但是,通过确保方法调用按正确顺序进行,可以克服这些问题。此外,我发现在连接时它有时会饱和连接(我正在运行网络模拟器接口),并且它给出的一些错误令人困惑,很难确定实际原因。 - Chris Dennett
另外一件事:很难获得调用方法的连接,但我正在修改kryonet并添加该功能。如果我让客户端随意伪造他们来自哪个连接,那么可能会引发混乱。 - Chris Dennett
另外一件事:它不支持一对多的RMI调用(因此,如果您调用远程方法,则会在100个远程对象上调用相同的方法)。 - Chris Dennett
一个好处是Kryo是一个序列化库,因此非常灵活。您可以为类编写自定义序列化器等。 - Chris Dennett
正是我所想做的。幸运的是,尽管我不能信任客户端运行的某些代码(因此必须保护我的主进程免受似乎难以处理的问题,如OutOfMemory错误),但我相信我将能够将其与与父级的通信分离 :) - obfuscation
显示剩余4条评论

5

4
如果无法使用标准输入/输出,我会选择使用套接字。您需要在套接字之上添加某种序列化层(就像使用标准输入/输出一样),而RMI是一种非常易于使用且相当有效的这种层。
如果您使用了RMI并发现性能不够好,我建议切换到更高效的序列化器 - 有很多选项可供选择。
我不会接近Web服务或XML。那似乎是完全浪费时间,可能比RMI花费更多的精力,并提供更少的性能。

谢谢,我相信这是我倾向于的方向。由于传输的数据量相对较小,我认为不应该遇到任何真正的性能问题,但如果这成为一个问题,我会记下您提供的链接。 - obfuscation

3

现在似乎很少有人喜欢使用RMI了。

选项:

  1. Web服务。例如:http://cxf.apache.org
  2. JMX。现在,这实际上是一种在幕后使用RMI的方法,但它可以工作。
  3. 其他IPC协议;您引用了Hessian。
  4. 使用套接字自己编写,甚至使用共享内存。(在父进程中打开映射文件,在子进程中再次打开。您仍需要某些同步机制。)

值得注意的例子包括Apache ant(为各种目的派生所有JVM)、Apache maven和Tanukisoft守护进程化工具的开源变体。

就个人而言,我非常擅长使用Web服务,因此我倾向于将事情变成钉子的锤子。使用CXF,典型的JAX-WS+JAX-B或JAX-RS+JAX-B服务只需很少的代码,并为我处理所有数据序列化和反序列化。


出于兴趣,你更倾向于哪个方案?我明白仅凭我上面简短的项目描述可能有些难以回答。 - obfuscation

3
上面提到过,但我想稍微扩展一下JMX建议。从您的各种评论中可以看出,我们实际上正在做与您计划做的几乎完全相同的事情。我们选择使用JMX有很多原因,其中几个我将在此处提到。首先,JMX非常注重管理,因此通常非常适合您想要做的事情(特别是如果您已经计划为其他管理任务使用JMX服务)。您投入到JMX接口中的任何努力都将兼作您可以使用Java管理工具(如jvisualvm)调用的API。这引出了我下一个观点,它最相关于您想要的。 JDK 6及以上版本中的新Attach API非常好。它使您能够动态发现和与运行的JVM进行通信。例如,这允许您的“控制器”进程崩溃、重新启动并重新查找所有现有的工作进程。这就是一个非常强大的系统的基础。上面提到了JMX基本上是RMI底层,但是,与直接使用RMI不同,您不需要管理所有连接细节(例如处理唯一端口、可发现性等)。Attach API是JDK中一个有点隐藏的宝石,因为它的文档不是很完善。当我最初查看此内容时,我不知道API的名称,因此弄清楚jvisualvm和jconsole中的“魔术”是非常困难的。最后,我发现了像这样的文章,其中显示了如何在自己的程序中动态使用attach api。

Attach API仅适用于子进程ID。一旦您生成了子进程,您需要获取该进程的ID。 - Dead Programmer
@SureshSankar - 你不需要知道pid来附加到进程,但这确实有帮助。 - jtahlborn
如果您不知道子进程的ID,那么如何控制或调用子进程中的方法?请告诉我如何在没有进程ID的情况下连接到子进程。 - Dead Programmer
@SureshSankar - 正如我在答案中所说,attach API 允许您动态发现其他 Java 进程。如果您有一种识别子进程的方法,您可以在不知道它们的 PID 的情况下找到它们(这就是我们产品的工作原理)。 - jtahlborn

2
虽然它是为 JVM 之间的远程通信设计的,但我认为你会发现 Netty 在本地 JVM 实例之间也非常有效。它可能是 Java 中同类库中性能最好、最稳健、最广泛支持的一个。

Netty似乎非常受人推崇,但我认为它的专长在于稍微不同的问题。您是否尝试过以这种方式使用Netty?此外,从您的经验来看,它可能对我的需求来说有点过重了-学习/使用需要很大的投入吗? - obfuscation
它确实需要一些设置 - 但一旦你让它工作起来,它就非常稳定。如果你真的在寻找更轻量级的东西,我可能会推荐KryoNet。 - mikera

2
很多内容已经讨论过了。无论是sockets、rmi还是jms,都需要进行大量的琐碎工作。我更愿意推荐akka。它是一个基于Actor模型的框架,可以使用消息相互通信。美妙的是,这些actors可以在同一个JVM上或另一个JVM上(只需少量配置),akka会为您处理其余的部分。我没有看到比这更清晰的方式了 :)

1

如果要传输的数据不是很大,可以尝试使用jGroups


我已经将这个书签保存下来,这样我就可以更仔细地查看它了,谢谢。你有使用过这个吗?如果有,你遇到了什么实际困难呢? - obfuscation
目前经验不多,我也在评估这个项目。您可以通过邮件列表联系他们。此外,它被jBoss用于集群成员身份。 - zengr
如果您的应用程序不需要具备强大的功能并且不会承受重负载,那么使用JGroups可能是合适的选择。我在两个工作中都使用过JGroups - 在这两种情况下,它都是现状,但我们最终都迁移到了其他解决方案。我发现它会占用大量线程和内存,并且其无限的可配置性使得它难以合理地进行配置。 - Ron
@Ron 你能否详细阐述一下你的答案。另外,你可能可以回答这个问题的一个部分:http://stackoverflow.com/questions/4980677/communication-between-2-nodes-in-a-cluster(反正我还没有好的答案) - zengr
如果:(1)垃圾回收暂停不频繁且时间极短无法测量;(2)您不介意无限配置、微不足道的 jgroups 配置;(3)您不介意 jgroups 运行时在您的 JVM 中旋转的线程、缓冲区和缓存混乱,那么 jgroups 是一个很好的选择。 - Ron

0

正如您所提到的,您显然可以通过网络发送对象,但这是一件代价高昂的事情,更不用说启动一个单独的JVM了。

如果您只想在一个JVM内部分离不同的世界,另一种方法是使用不同的类加载器加载类。如果它们由CL1和CL2作为兄弟类加载器加载,则ClassA@CL1!= ClassA@CL2。

要使classA@CL1和classA@CL2之间能够通信,您可以拥有三个类加载器。

  • CL1加载process1
  • CL2加载process2(与CL1中相同的类)
  • CL3加载通信类(POJO和Service)。

现在,您让CL3成为CL1和CL2的父类加载器。

在由CL3加载的类中,您可以拥有轻量级的通信发送/接收功能(send(Pojo)/receive(Pojo)),将POJO在CL1中的类和CL2中的类之间传递。

在CL3中,您公开一个静态服务,使CL1和CL2的实现能够注册以发送和接收POJO。


他问的是JVM,而不是类加载器。 - bmargulies
感谢您的建议,但正如@bmargulies所指出的那样,由于存在许多问题,我确实需要单独的JVM。 - obfuscation

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接