在基于对象的分布式系统中,对象在分布式实现中起着极其关键的作用。从原理上来讲,所有的模块都可以被作为对象抽象出来,客户端将以调用对象的方式来获得服务和资源。
分布式对象之所以成为重要的范型,是因为它相对比较容易地把分布的特性隐藏在对象接口后面。此外,因为对象实际上可以是任何事务,所以它也是构建系统的强大范型。
面向对象技术于20世纪80年代开始用于开发分布式系统。同样,在达到高度分布式透明性的同时,通过远程服务器宿主独立对象的理念构成了开发新一代分布式系统的稳固的基础。在本节中,我们将看到基于对象的分布式系统的常用体系结构。
软件世界中的对象和现实世界中的对象类似,对象存储状态在字段里,通过方法来暴露其行为。方法对对象的内部状态进行操作,并作为对象与对象之间通信的主要机制。隐藏对象内部状态,通过方法进行所有的交互操作,这是面向对象编程的一个基本原则——数据封装,可以通过接口来使用方法。一个对象可能实现多个接口,给定的一个接口定义可能有多个对象为其提供实现。
把接口与实现这些接口的对象进行分隔,对于分布式系统是至关重要的。严格的隔离允许我们把接口放在一台机器上,而使对象本身驻留在另外一台机器上。这种组织通常称为分布式对象,如图2-1所示。
图2-1 带有客户端代理的远程对象的常见组织
当客户绑定(bind)到一个分布式对象时,就会把这个对象的接口的实现——称为代理(proxy)——加载进客户的地址空间中。代理类似于RPC系统中的客户存根(client stub)。它所做的事是把方法调用编组进消息中,以及对应答消息进行解组,把方法调用的结果返回给客户。实际的对象驻留在服务器计算机上,在这里提供了与它在客户机上提供的相同的接口。进入的调用请求首先被传递给服务器存根,服务器存根对它们进行解码,在服务器的对象接口上进行方法的调用。服务器存根还负责对应答进行编码,并把应答消息转发给客户端代理。
服务器端存根通常被称为骨架(skeleton),因为它提供了明确的方式,允许服务器中间件访问用户定义的对象。实际上,它通常以特定于语言的类的形式包含不完整的代码,需要开发人员进一步对其进行特殊化处理。
大多数分布式对象的一个特性是它们的状态不是分布式的。状态驻留在单台机器上,在其他机器上,智能地使用被对象实现的接口,这样的对象也被称为远程对象(remote object)。分布式对象的状态本身可能物理地分布在多台机器上,但是这种分布对于对象接口背后的客户来说是透明的。
Java在最初只支持通过socket来实现分布式通信。1995年,作为Java的缔造者,Sun公司开始创建一个Java的扩展,称为Java RMI(Remote Method Invocation,远程方法调用)。Java RMI允许程序员在创建分布式应用程序时,可以从其他Java虚拟机(JVM)调用远程对象的方法。
一旦应用程序(客户端)引用了远程对象,就可以进行远程调用了。通过RMI提供的命名服务(RMI注册中心)来查找远程对象,以接收作为返回值的引用。Java RMI在概念上类似于RPC,但能在不同地址空间支持对象调用的语义。
与大多数其他诸如CORBA的RPC系统不同,RMI只支持基于Java来构建,但也正是这个原因,RMI对于语言来说更加整洁,无须做额外的数据序列化工作。Java RMI的设计目标应该是:
能够适应语言、集成到语言、易于使用;
支持无缝的远程调用对象;
支持服务器到applet的回调;
保障Java对象的安全环境;
支持分布式垃圾回收;
支持多种传输。
分布式对象模型与本地Java对象模型的相似点在于:
引用一个对象可以作为参数传递或返回的结果;
远程对象可以投递到任何使用Java语法实现的远程接口的集合上;
内置Java instanceof操作符可以用来测试远程对象是否支持远程接口。
不同点在于:
远程对象的类与远程接口进行交互,而不是与这些接口的实现类交互;
Non-remote参数对于远程方法调用来说是通过复制,而不是通过引用来传递的;
远程对象是通过引用来传递的,而不是复制实际的远程实现;
客户端必须处理额外的异常。
所有的远程接口都继承自java.rmi.Remote接口。例如:
注意: 每个方法必须在throws中声明java.rmi.RemoteException。只要客户端调用远程方法出现失败,这个异常就会抛出。
Java.rmi.server.RemoteObject类提供了远程对象实现的语义,包括hashCode、equals和toString。java.rmi.server.RemoteServer 及其子类让对象实现远程可见。java.rmi.server.UnicastRemoteObject类定义了客户机与服务器对象实例并建立一对一的连接。
Java RMI通过创建存根函数来工作。存根由rmic编译器生成。自Java 1.5以来,Java支持在运行时动态生成存根类。编译器rmic会提供各种编译选项。
引导名称服务提供了用于存储对远程对象的命名引用。一个远程对象引用可以存储使用类java.rmi.Naming提供的基于URL的方法。例如:
Java RMI的工作流程如图2-2所示。
图2-2 Java RMI的工作流程
RMI是三层架构(见图2-3),顶层是Stub/Skeleton layer(存根/骨架层)。方法调用从Stub、Remote Reference Layer(远程引用层)和Transport Layer(传输层)向下传递给主机,然后再次经过Transport Layer层,向上穿过Remote Reference Layer和Skeleton,到达服务器对象。Stub扮演着远程服务器对象的代理的角色,使该对象可被客户激活。Remote Reference Layer 处理语义、管理单一或多重对象的通信,决定调用发往一个还是多个服务器。Transport Layer 管理实际的连接,并且追踪可以接收方法调用的远程对象。服务器端的Skeleton完成对服务器对象实际的方法调用,并获取返回值。返回值向下经 Remote Reference Layer、服务器端的 Transport Layer传递回客户端,再向上经Transport Layer和Remote Reference Layer返回。最后,Stub程序获得返回值。
图2-3 Java RMI架构
要完成以上步骤需要以下几个环节:
生成一个远程接口;
实现远程对象(服务器端程序);
生成Stub和Skeleton(服务器端程序);
编写服务器程序;
编写客户程序;
注册远程对象;
启动远程对象。
根据Java虚拟机的垃圾回收机制原理,在分布式环境下,服务器进程需要知道哪些对象不再由客户端引用,从而删除对象(垃圾回收)。在JVM中,Java使用引用计数。当引用计数归零时,将进行垃圾回收。在RMI中,Java支持两种操作——dirty和clean。本地JVM定期发送一个dirty到服务器来说明该对象仍在使用。重发dirty的周期是由服务器租赁时间来决定的。当客户端不需要更多的本地引用远程对象时,它发送一个clean调用给服务器。不像DCOM,服务器不需要计算每个客户机使用的对象,只是简单地通知。如果它租赁时间到期之前没有接收到任何dirty或clean的消息,则可以安排将对象删除。