Java远程方法调用与分布式系统技术解析
1. 远程方法调用(RMI)概述
远程方法调用(RMI)是Java平台用于远程过程调用(RPC)的标准。远程过程调用在概念上与程序内的普通过程调用相同,不同之处在于调用可以通过网络在两个独立进程之间进行。
1.1 RMI核心原理
在Java中,编写客户端/服务器程序相对简单。调用对象的方法时,甚至可能不知道该对象位于远程机器上,方法调用的代码与普通本地方法调用并无区别。在Java EE中,通常需要从命名服务中查找对象实例。获取对象引用后,可能是本地引用或远程引用,但代码不变,这也是Java成为强大服务器语言的原因之一,RMI等技术的许多复杂细节已被抽象化。
不过,开发者不能完全忽略对象实例是远程还是本地的。远程对象存在一些设计权衡,因为方法调用通过网络进行,受网络可靠性和速度的限制。
1.2 调用远程对象方法的步骤
在RPC中,所有方法调用必须转换为可通过网络传输并能被远程进程理解的格式。调用远程对象的方法主要有以下三个步骤:
1.
获取远程对象的引用
:必须在远程服务器上查找远程对象。
2.
参数的编组和解组
:调用远程引用的方法时,参数必须编组为可通过网络发送的字节流。在服务器端,这些参数必须从字节流解组为原始值,然后传递给相应的方法。
3.
通过公共协议传输数据
:必须定义一个协议来传输和传递这些方法调用和返回结果。需要参数的标准格式,以及告知服务器要调用哪个对象上的哪个方法的标准。
为了使远程调用看起来像本地调用,存在一个具有相同接口的本地实现(所有RMI对象必须定义为Java接口),这个本地实现称为存根(stub),本质上是实际实现的代理。当在本地实现或存根上调用方法时,本地实现会执行必要的操作,将方法调用发送到另一台服务器上相同接口的远程实现。存根编组参数并使用公共RMI协议通过网络发送它们。反过来,服务器端实现相同接口的存根解组参数,然后以正常方法调用的方式将它们传递给实际的远程对象。返回值的过程则相反:服务器端的存根编组并发送返回值,客户端的存根解组并将其返回给原始调用者。
1.3 编组和解组
在将参数和方法调用通过网络发送之前,必须将它们扁平化为字节流,这个过程称为编组(marshalling)。相反的过程称为解组(unmarshalling),即把字节流解码为原始参数和方法调用信息。服务器解组参数和方法调用后,将方法调用分派给实际实现远程方法的对象,然后将返回值编组回客户端。
在RMI中,除了基本类型外,有两种类型的对象可以作为参数传递:实现
java.rmi.Remote
接口的对象或实现
java.io.Serializable
接口的对象。这两个接口不包含任何方法,而是标记对象具有特定属性。Java的RMI机制知道
Remote
对象可能位于另一个虚拟机上,并且会有存根。而实现
Serializable
的对象可以转换为字节流(用于保存到磁盘,或者在RMI中通过网络发送)。在RMI中,实现
Remote
的对象通过引用传递,而实现
Serializable
(但不实现
Remote
)的对象通过值传递。
例如,以下是一个服务器上被客户端远程调用的方法实现:
public void myTestMethod(A a, B b) {
a.remoteMethod();
Data d = b.getData();
...
}
在这个例子中,
A
实现了
java.rmi.Remote
,因此调用
remoteMethod()
是对客户端的远程回调。
B
实现了
Serializable
,因此
getData()
是对
b
的本地调用,
b
是从其序列化状态解组回对象后在服务器上运行的。
1.4 协议
在RPC中,所有方法调用必须转换为可通过网络发送的标准格式。RPC机制有自己的协议,有时这些协议构建在TCP/IP之上,有时它们除了RPC协议外还定义自己的传输协议,将传输层和应用层协议结合以实现最佳性能。
RMI可以支持多种底层传输协议(但显然任意两个对象之间只能使用一种协议),主要有以下两种选择:
-
Java远程方法协议(JRMP)
:是RMI的默认协议。
-
互联网InterORB协议(IIOP)
:提供与CORBA的兼容性,但由于它不是专门为Java远程过程调用设计的,因此不支持JRMP支持的一些功能,如安全性和分布式垃圾回收。使用IIOP作为RMI的底层协议可以轻松集成用其他语言编写的遗留对象。IIOP存根与JRMP存根不同,必须单独生成。
1.5 RMI注册表
在远程客户端使用对象实例之前,必须在服务器的注册表中使其可用。客户端通过查找特定名称来获取实例,例如,字符串“EmployeeData”可能引用包含特定公司员工数据的类。服务器启动时,会创建它希望可用的对象实例,并将它们注册到注册表中。由于这些对象是全局可用的,因此必须是线程安全的(因为它们的方法可以同时被不同线程调用)。
以下是一个在远程服务器上查找对象的小代码片段:
import javax.naming.InitialContext;
...
InitialContext ctx = new InitialContext();
EmployeeData data = (EmployeeData) ctx.lookup(“CompanyX\\MyEmployeeDataInstance”);
...
JNDI通过设置某些Java系统属性来配置,以告知它注册表的位置和协议。这样,对象可以透明地是远程的或本地的。如果注册表在同一JVM中本地配置,则对
data
的所有调用将是本地调用。如果
data
是远程服务器上的实例,则所有调用将通过RMI进行,使用指定的任何协议。
1.6 分布式对象
RMI允许开发者在应用程序中抽象出对象的物理位置。面向对象的应用程序可以透明地分布在多台机器上。进行大量处理或提供服务器端功能的对象,如邮件服务、事务性数据库服务或文件服务,可以位于服务器级别的机器上。典型的桌面客户端应用程序可以像访问本地对象一样访问这些对象。
位置无关的对象非常强大,因为它们可以在不同机器之间动态移动。例如,如果服务器上的邮件服务对象负载过重,可以将它们分布到多台机器上,而使用它们的客户端应用程序对此完全透明。Java的平台独立性为其位置无关的对象增加了更多价值。
1.7 中间件和Java EE
大多数情况下,将对象分布到不同机器上的主要原因是访问这些机器提供的各种服务。邮件服务、事务性数据库服务和文件服务器服务都可以由各种软件组件(在这种情况下是Java对象)封装。通过允许所有这些对象以标准的分布式方式进行通信,可以轻松开发服务器端应用程序。
位置无关的对象允许服务器应用程序进行扩展,因为当一台服务器无法为服务器应用程序提供足够的处理能力时,只需添加几台机器并分布对象即可。
中间件是各种数据源与其客户端应用程序之间的软件层。RMI分布式对象是为不同应用程序实现中间件的一种方式。中间件抽象了一个或多个数据源的细节。由于RMI具有位置和平台独立性,因此它是中间件的理想构建块。Java在服务器端应用程序和中间件中非常流行,因为它为构建稳定可靠的软件系统提供了基础。
Java企业版(Java EE)平台将RMI作为其核心技术之一。Java EE提供可靠的消息传递、坚实的事务性存储功能、远程管理和部署,以及用于生成支持Web的服务器端应用程序的框架。Java EE是开发中间件和其他服务器端服务的标准平台。RMI使Java EE能够实现位置无关和分布式。与仅使用RMI开发自己的中间件相比,基于Java EE标准编写服务器端应用程序要好得多。
2. 公共对象请求代理体系结构(CORBA)
2.1 CORBA概述
公共对象请求代理体系结构(CORBA)是对象管理组织(OMG)为语言无关的分布式对象制定的一组规范。它允许用多种不同编程语言编写的对象相互交互和通信。C++类可以与Java类通信,C#可以与C++或Java通信。一些CORBA实现支持用C编写的程序,甚至支持Python等脚本语言。
CORBA在概念上与RMI类似,但支持的语言不仅仅是Java。CORBA本身是一组规范,而不是实际的实现。一种语言要支持CORBA和其他CORBA对象,必须在其本地语言中有实现(或以某种方式绑定到实现)。例如,Java开发工具包(JDK)包含CORBA 2.3.1规范的实现,这意味着Java开箱即支持高达2.3.1规范的CORBA实现(撰写本文时,最新的CORBA规范是3.02)。
尽管业界对JDK对CORBA的支持版本有批评,但2.3.1包含了许多CORBA的现代功能,足以实现和使用大多数CORBA分布式对象。除了JDK附带的实现外,还有许多可以与Java一起使用的CORBA实现。可以在OMG的网站(www.omg.org/technology/corba/corbadownloads.htm)上找到免费的CORBA下载列表(包括商业实现的试用版或免费的开源实现)。
2.2 CORBA基础
CORBA规范有四个主要概念,定义了用不同语言编写的分布式对象如何相互通信:
-
命名服务
:与RMI类似,有一个命名服务,远程对象引用可以在其中注册,以便在某个时间点由一个或多个客户端检索。
-
互联网InterORB协议(IIOP)
:用于客户端和服务器之间的通信,负责定义远程方法调用和参数传递的编组和解组格式。
-
对象请求代理(ORB)
:负责处理所有远程方法调用,并将它们分派到客户端和服务器上的适当对象。
-
对象间通信范式
:与RMI类似,通过以下步骤实现对象间通信:
1. 使用COS命名服务获取远程对象引用。
2. 将方法调用信息和参数编组为字节流,通过互联网InterORB协议(IIOP)通过网络发送。
3. 远程服务器上的对象请求代理(ORB)接收传入请求,并将它们分派到实现被调用CORBA接口的对象。
2.3 IDL:接口定义语言
接口定义语言(IDL)是CORBA用于编写接口的规范。在CORBA中,所有分布式对象必须实现CORBA接口。这些接口类似于Java的接口概念,允许有多个实现。不过,CORBA接口可以由任何支持CORBA的语言实现。
CORBA接口中可以声明以下三种内容:
-
操作
:类似于Java方法。
-
属性
:类似于JavaBean属性,通过getXXX和setXXX方法实现。
-
异常
CORBA接口允许IDL编译器将IDL编译为现有语言的存根类。例如,JDK提供的工具可以将CORBA IDL类型映射到Java类型,并为任何给定的IDL文件生成存根类。这些存根类使Java程序员可以将CORBA对象视为Java类,并使用Java语法调用其方法,就像调用任何其他Java类一样。IDL是不同语言之间的桥梁,它提供了一个接口的描述,可以转换为具体编程语言中的相应类。
使用远程CORBA对象时,客户端程序员使用的是接口,而不是实现它的具体对象。远程机器上运行的ORB解析请求,并将其分派到正确的实现。客户端永远不知道远程对象是用哪种语言编写的,一切都是透明的。
以下是一个CORBA接口
FileNotification
的Java表示:
package book;
public interface FileNotification
{
public void fileModified (String fileName);
public void fileDeleted (String fileName);
public void fileCreated (String fileName);
}
其等效的IDL定义如下:
#include “orb.idl”
#ifndef __book_FileNotification__
#define __book_FileNotification__
module book {
interface FileNotification {
void fileModified(
in ::CORBA::WStringValue fileName );
void fileDeleted(
in ::CORBA::WStringValue fileName );
void fileCreated(
in ::CORBA::WStringValue fileName );
};
#pragma ID FileNotification “RMI:book.FileNotification:0000000000000000”
};
#endif
2.4 ORB:对象请求代理
对象请求代理负责调解传入的CORBA方法调用。ORB是任何CORBA实现的核心基础设施,为任何给定服务器的所有CORBA请求提供一个公共入口点。许多不同的CORBA对象上的方法调用都通过同一个入口点(ORB),然后ORB将请求分派到与客户端引用对应的正确CORBA对象实例。
2.5 公共对象服务(COS)命名
公共对象服务(COS)命名提供一个注册表来保存CORBA对象的引用,其概念与RMI注册表类似。当服务器希望将CORBA对象实例暴露给远程客户端时,会将每个实例注册到命名服务中,每个实例在服务器上都有一个唯一的名称。客户端使用该名称检索引用并调用其方法。
JNDI提供的
InitialContext
可以与COS命名交互,以与查找RMI对象相同的方式查找各种CORBA对象。只要IIOP的正确存根就位,设置以下系统属性(包含远程COS命名服务的正确主机名和端口的URL),客户端Java程序就可以透明地访问CORBA对象:
java.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
java.naming.provider.url=iiop://hostname:1049
设置这些属性后(例如在命令行使用 -D 选项设置),JNDI查找将正常进行。客户端程序也可以使用
org.omg.CORBA
包手动访问CORBA引用,并利用CORBA的许多细节。
2.6 IIOP:互联网InterORB协议
互联网InterORB协议是CORBA ORB用于在网络上相互通信的协议。所有方法调用和参数都通过该协议进行编组和解组,它是一种高效的二进制协议。JDK 1.5支持IIOP规范的2.3.1版本。
综上所述,RMI和CORBA都是实现分布式系统的重要技术,它们各有优缺点。在实际开发中,需要根据具体需求和场景选择合适的技术。RMI更适合基于Java平台的分布式系统开发,而CORBA则提供了跨语言的分布式对象通信能力。
3. RMI与CORBA的对比分析
3.1 功能特性对比
| 特性 | RMI | CORBA |
|---|---|---|
| 语言支持 | 主要支持Java语言 | 支持多种语言,如C++、C#、Python等 |
| 协议选择 | 支持JRMP和IIOP,默认JRMP | 主要使用IIOP |
| 特性支持 | 支持安全性和分布式垃圾回收(JRMP) | 部分特性支持不如RMI,如安全性和分布式垃圾回收(IIOP) |
| 开发难度 | 相对简单,对开发者友好 | 复杂,开发难度大 |
3.2 性能对比
- RMI :由于是Java平台的标准,与Java环境紧密集成,在纯Java环境下性能表现较好。JRMP协议专门为Java远程过程调用设计,能充分发挥Java的特性,减少不必要的开销。
- CORBA :虽然现代实现的性能有了很大提升,但由于其设计初衷是支持多种语言,需要处理更多的兼容性问题,可能会带来一定的性能损耗。不过,在跨语言场景下,CORBA的性能优势在于能够实现不同语言编写的对象之间的高效通信。
3.3 应用场景对比
- RMI :适用于基于Java平台的分布式系统开发,尤其是当系统主要使用Java语言,且对开发效率和Java特性有较高要求时。例如,开发一个企业级的Java应用,内部各个模块之间需要进行远程通信,RMI是一个不错的选择。
- CORBA :在需要集成不同语言编写的遗留系统时,CORBA的跨语言能力使其成为首选。例如,一个大型企业的信息系统,其中部分模块是用C++开发的,部分是用Java开发的,使用CORBA可以实现这些不同语言模块之间的无缝通信。
4. 实际应用案例分析
4.1 RMI应用案例
假设我们要开发一个分布式的员工信息管理系统,系统分为客户端和服务器端。服务器端负责存储和管理员工信息,客户端负责查询和修改员工信息。
步骤如下
- 定义远程接口
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface EmployeeData extends Remote {
public String getEmployeeInfo(String employeeId) throws RemoteException;
public void updateEmployeeInfo(String employeeId, String newInfo) throws RemoteException;
}
- 实现远程接口
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class EmployeeDataImpl extends UnicastRemoteObject implements EmployeeData {
protected EmployeeDataImpl() throws RemoteException {
super();
}
@Override
public String getEmployeeInfo(String employeeId) throws RemoteException {
// 模拟从数据库获取员工信息
return "Employee ID: " + employeeId + ", Info: Some information";
}
@Override
public void updateEmployeeInfo(String employeeId, String newInfo) throws RemoteException {
// 模拟更新数据库中的员工信息
System.out.println("Updating employee " + employeeId + " with info: " + newInfo);
}
}
- 服务器端注册对象
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class Server {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
EmployeeData employeeData = new EmployeeDataImpl();
Naming.rebind("EmployeeData", employeeData);
System.out.println("Server is ready.");
} catch (Exception e) {
System.out.println("Server exception: " + e.toString());
e.printStackTrace();
}
}
}
- 客户端查找对象并调用方法
import java.rmi.Naming;
public class Client {
public static void main(String[] args) {
try {
EmployeeData employeeData = (EmployeeData) Naming.lookup("rmi://localhost:1099/EmployeeData");
String info = employeeData.getEmployeeInfo("123");
System.out.println(info);
employeeData.updateEmployeeInfo("123", "New information");
} catch (Exception e) {
System.out.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
4.2 CORBA应用案例
假设我们要开发一个分布式文件系统通知系统,当文件发生修改、删除或创建时,系统能够通知相关的客户端。
步骤如下
- 定义CORBA接口(IDL)
#include “orb.idl”
#ifndef __book_FileNotification__
#define __book_FileNotification__
module book {
interface FileNotification {
void fileModified(
in ::CORBA::WStringValue fileName );
void fileDeleted(
in ::CORBA::WStringValue fileName );
void fileCreated(
in ::CORBA::WStringValue fileName );
};
#pragma ID FileNotification “RMI:book.FileNotification:0000000000000000”
};
#endif
- 生成Java存根和框架 :使用IDL编译器(如JDK提供的工具)将IDL文件转换为Java类。
- 实现CORBA接口
package book;
import org.omg.CORBA.portable.IDLEntity;
public class FileNotificationImpl extends FileNotificationPOA {
@Override
public void fileModified(String fileName) {
System.out.println("File modified: " + fileName);
}
@Override
public void fileDeleted(String fileName) {
System.out.println("File deleted: " + fileName);
}
@Override
public void fileCreated(String fileName) {
System.out.println("File created: " + fileName);
}
}
- 服务器端注册对象
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
public class Server {
public static void main(String[] args) {
try {
// 初始化ORB
ORB orb = ORB.init(args, null);
// 获取根POA
POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootPOA.the_POAManager().activate();
// 创建FileNotification对象
FileNotificationImpl fileNotificationImpl = new FileNotificationImpl();
org.omg.CORBA.Object ref = rootPOA.servant_to_reference(fileNotificationImpl);
FileNotification href = FileNotificationHelper.narrow(ref);
// 获取命名服务
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
// 绑定对象到命名服务
NameComponent path[] = ncRef.to_name("FileNotification");
ncRef.rebind(path, href);
System.out.println("Server is ready.");
// 等待请求
orb.run();
} catch (Exception e) {
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
}
}
- 客户端查找对象并调用方法
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
public class Client {
public static void main(String[] args) {
try {
// 初始化ORB
ORB orb = ORB.init(args, null);
// 获取命名服务
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
// 查找FileNotification对象
NameComponent path[] = ncRef.to_name("FileNotification");
FileNotification fileNotification = FileNotificationHelper.narrow(ncRef.resolve(path));
// 调用方法
fileNotification.fileModified("test.txt");
} catch (Exception e) {
System.err.println("ERROR: " + e);
e.printStackTrace(System.out);
}
}
}
5. 总结与展望
5.1 总结
RMI和CORBA都是实现分布式系统的重要技术,它们在不同的场景下发挥着重要作用。RMI作为Java平台的标准,在纯Java环境下具有开发简单、性能高效的优势;而CORBA则以其跨语言的能力,为集成不同语言编写的遗留系统提供了强大的支持。
5.2 展望
随着技术的不断发展,分布式系统的需求也在不断变化。未来,我们可以期待RMI和CORBA技术的进一步发展和优化。例如,RMI可能会进一步加强与新兴Java技术的集成,提高其在微服务架构中的应用能力;CORBA可能会在性能和易用性方面进行改进,使其更适合现代开发的需求。同时,新的分布式技术也在不断涌现,如基于HTTP/2的gRPC等,它们与RMI和CORBA相互竞争又相互补充,共同推动着分布式系统技术的发展。
mermaid流程图展示RMI调用流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([客户端程序]):::startend --> B(获取远程对象引用):::process
B --> C(编组参数):::process
C --> D(通过协议传输数据):::process
D --> E(服务器端解组参数):::process
E --> F(调用远程方法):::process
F --> G(编组返回值):::process
G --> H(通过协议传输返回值):::process
H --> I(客户端解组返回值):::process
I --> J([客户端继续执行]):::startend
mermaid流程图展示CORBA调用流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([客户端程序]):::startend --> B(使用COS命名服务获取远程对象引用):::process
B --> C(编组方法调用信息和参数):::process
C --> D(通过IIOP协议传输数据):::process
D --> E(服务器端ORB接收请求):::process
E --> F(ORB分派请求到正确对象):::process
F --> G(调用CORBA接口方法):::process
G --> H(编组返回值):::process
H --> I(通过IIOP协议传输返回值):::process
I --> J(客户端ORB接收返回值):::process
J --> K(解组返回值):::process
K --> L([客户端继续执行]):::startend
超级会员免费看
170万+

被折叠的 条评论
为什么被折叠?



