Douglas C. Schmidt Chris Cleeland
分布式对象计算是下一代通信软件的基础。对象请求代理(Object Request Broker,ORB)是分布式对象计算的心脏,它使得许多麻烦而易错的分布式编程任务得以自动化。像许多通信软件一样,传统的ORB使用静态配置设计,难以移植、优化和发展。同样地,要扩展传统的ORB,必须修改源代码,从而要求重新编译、链接和重启运行中的ORB以及与它们相关联的应用对象。
本论文对可扩展ORB中间件的研究作出的贡献有两个。首先,它给出的个案研究阐释怎样将模式语言用于开发可动态配置、并可为特定应用需求和系统特性进行定制的ORB。其次,我们对应用这种模式语言来降低复杂性和改善常见的ORB任务的可维护性(比如连接管理、数据传输、多路分离,以及并发控制)的效果进行了量化。
有四种趋势正在塑造商业软件开发的未来。首先,软件工业正在从“从零开始”的应用编程迁移到使用可复用组件来集成应用[1]。其次,分布式技术提供远地方法调用和/或面向消息的中间件来简化应用协作,软件开发者对这样的技术有着巨大的需求。第三,业界正在努力定义标准的软件基础组织构架、以允许应用在异种环境间无缝地协同工作 [2]。最后,下一代分布式应用,比如视频点播、电话会议以及航空控制系统,要求保证响应时间、带宽和可靠性的服务质量(QoS) [3]。
顺应这些趋势的一种关键软件技术是分布式对象计算(DOC)中间件。DOC中间件便利了在异种分布式环境中本地和远地应用组件的协作。它的目标是消除开发和发展分布式应用和服务的许多麻烦、易错和不可移植的方面。特别地,DOC中间件使得一些常见的网络编程任务得以自动化,比如对象定位、实现启动(也就是,服务器和对象启用)、封装不同体系结构的字节序和参数类型大小的差异(也就是,参数整编)、容错,以及安全。对象请求代理(ORB)是DOC中间件的心脏,比如CORBA[4]、DCOM[5],以及JAVA RMI[6]。
本论文描述我们怎样应用模式语言来开发和发展可动态配置、比静态配置的中间件要更容易扩展的ORB中间件。一般而言,通过将相关解决方案族应用于标准的软件开发问题,模式语言有助于减少软件概念和组件的持续的重新发送和发明[7]。例如,要为常见通信软件体系结构中的参与者之间的角色和关系编写文档,模式语言是十分有用的[8]。本论文中介绍的模式语言是在[9]中介绍的模式语言的一般化,并已被成功地用于构建灵活、高效、事件驱动和并发的通信软件,包括ORB中间件。
为使我们的讨论更集中,本论文介绍了一项个案研究,演示我们怎样将此模式语言应用于开发The ACE ORB(TAO)[10]。TAO是可自由使用、高度可扩展的ORB,它瞄准的是有实时QoS需求的应用,包括航空任务计算[11]、多媒体应用[12],以及分布式交互模拟[13]。可扩展设计是TAO的新颖之处,它的ORB在模式语言的指导下,能够进行动态定制、以满足和适应特定应用的QoS需求和网络/终端系统特性。
本论文的余下部分组织如下:3.2给出CORBA和TAO的综述;3.3说明使用动态配置的动机,并描述一种模式语言,用以应对在开发可扩展ORB时所面临的关键设计挑战; 3.3.5评价并量化模式语言对ORB中间件的贡献;3.4给出结束语。
这一部分概述CORBA参考模型,并描述TAO提供给高性能和实时应用的增强特性。
CORBA对象请求代理(ORB)[14]允许客户调用分布式对象上的操作,而不用关心以下问题:
对象位置:CORBA对象可以与客户共驻一处,或是分布在远地服务器上,而不会影响它的实现或使用。
编程语言:CORBA支持的语言包括C、C++、Java、Ada95、COBOL,以及Smalltalk,等等。
OS平台:CORBA运行在许多平台上,包括Win32、UNIX、MVS,以及实时嵌入式系统,比如VxWorks、Chorus和LynxOS。
通信协议和互连:CORBA可在以下通信协议和互连上运行:TCP/IP、IPX/SPX、FDDI、ATM、Ethernet、Fast Ethernet、嵌入式系统底板,以及共享内存。
硬件:CORBA使应用和源于硬件多样性的副作用(比如不同的存储方案和数据类型大小/范围)屏蔽开来。
图3-1演示CORBA参考模型中的组件,它们相互进行协作来提供上面概述的可移植性、互操作性和透明性。

图3-1 CORBA参考模型中的组件
CORBA参考模型中的每个组件概述如下:
客户(Client):客户是一种获取对象的引用、在其上调用操作以执行应用任务的角色(role)。对象可以在远地、或与客户共驻一处。客户可以像访问本地对象那样访问远地对象,也就是,使用object->operation(args)。图3-1显示下面描述的底层ORB组件怎样将远地操作请求从客户透明地传送给对象。
对象(Object):在CORBA中,对象是OMG接口定义语言(IDL)接口的的实例。各个对象被对象引用(object reference)所标识,对象引用与一或多个路径相关联,通过这些路径客户可以访问服务器上的对象。对象ID将对象与它的实现(称为仆人,servant)相关联,它在对象适配器(Object Adapter)的范围内是唯一的。在对象的生存期内,有一或多个仆人与其关联,以实现它的接口。
仆人(Servant):此组件实现由OMG IDL接口定义的操作。在面向对象(OO)语言(比如C++和Java)中,仆人使用一或多个类实例来实现。在非OO语言(比如C)中,仆人通常使用函数和结构来实现。客户从不与仆人直接交互,而总是通过对象引用所标识的对象来进行。以对象作为RefinedAbstraction、仆人作为ConcreteImplementor,对象和它的仆人共同构成了桥接(Bridge)模式[15]的一种实现。
ORB核心(ORB Core):当客户调用对象上的操作时,ORB核心负责将请求递送给对象;如果有任何响应,就将其返回给客户。ORB核心是作为链接进客户和服务器应用的运行时库来实现的。对于远地执行的对象、遵循CORBA的ORB核心通过General Inter-ORB Protocol(GIOP)的一种版本(比如运行在TCP传输协议之上的Internet Inter-ORB Protocol,IIOP)来进行通信。此外,还可以定义定制的Environment-Specific Inter-ORB Protocol(ESIOP)。
ORB接口(ORB Interface):ORB是可通过多种方式(例如,通过一或多个进程或一组库)实现的抽象。为使应用与实现细节去耦合,CORBA规范定义了ORB的接口。该ORB接口提供标准的操作来初始化和关闭ORB、将对象引用转换为字符串或反向转换,以及为通过动态调用接口(dynamic invocation interface,DII)发送的请求创建参数表。
OML IDL Stub和Skeleton:IDL Stub和Skeleton分别被用作客户及仆人与ORB之间的“胶水”。Stub实现代理(Proxy)模式[15],并提供强类型的静态调用接口(SII);该接口将应用参数整编(marshal)为通用的消息级表示。相反,Skeleton实现适配器(Adapter)模式[15],并将消息级表示“去整编”为对应用来说有意义的有类型参数。
IDL编译器(IDL Compiler):IDL编译器将OMG IDL定义转换为自动生成的Stub和Skeleton;其源码采用某种应用编程语言,比如C++或Java。除了提供编程语言透明性,IDL编译器还消除了常见的网络编程错误来源,并可以进行自动的编译器优化[17]。
动态调用接口(DII):DII允许用户在运行时生成请求,对于不具有所访问接口的编译时知识的应用来说这是有用的。DII还允许客户发出缓召的同步调用(deferred synchronous call),使请求与两路(two-way)操作的响应部分去耦合,从而避免客户会阻塞到仆人响应为止。CORBA SII Stub同时支持同步和异步两路操作,也就是,请求/响应操作;以及单路操作,也就是,“只请求”操作。
动态Skeleton接口(DSI):DSI是客户的DII在服务器端的对应物。DSI允许ORB将请求递送给仆人,而这些仆人不具有所实现的IDL接口的编译时知识。发出请求的客户不需要知道服务器ORB是使用静态Skeleton还是动态Skeleton。同样地,服务器也不需要知道客户是使用DII还是SII来调用请求的。
对象适配器(Object
Adapter):对象适配器是使仆人与对象相关联的合成组件,它创建对象引用、将到来的请求多路分离给仆人,并与IDL Skeleton协作,以分派仆人上适当的操作upcall。对象适配器使得ORB能够支持各种类型的具有类似需求的仆人。这样的设计产生了更小更简单的ORB,可以支持广泛的对象粒度、生存期、策略、实现风格和其他特性。
接口仓库(Interface
Repository):接口仓库提供关于IDL接口的运行时信息。使用这些信息,程序有可能遇到在程序编译时其接口还属未知的对象,并且仍然能够确定在该对象上的合法操作,并使用DII来发出调用。此外,接口仓库还提供公共区域来存储与CORBA对象的接口相关联的其他信息,比如Stub和Skeleton的类型库。
实现仓库(Implementation
Repository):实现仓库[18]包含的信息允许ORB启用服务器来对仆人进行处理。实现仓库中的大多数信息都是特定于某种ORB或OS环境的。此外,实现仓库还提供了公共区域来存储与服务器相关联的信息,比如管理控制、资源分配、安全,以及启用模式。
TAO是高性能实时ORB终端系统,其应用目标是具有确定性和可统计QoS需求、以及最优努力(best-effort)需求的应用。TAO的ORB终端系统含有图3-2中所示的网络接口、OS、通信协议,以及遵循CORBA的中间件组件和服务。TAO支持标准的CORBA参考模型[14]和实时CORBA规范[19],并且,它还具有一些增强特性,可用于确保高性能及实时应用的高效、可预测和可伸缩的QoS行为。此外,TAO也可以很好地适用于一般的分布式应用。下面,我们概述图3-2中所示的TAO组件的特性。

图3-2 TAO实时ORB终端系统中的组件
优化的IDL Stub和Skeleton:IDL Stub和Skeleton分别执行应用操作参数的整编和去整编。TAO的IDL编译器生成的Stub/Skeleton可以有选择地使用高度优化编译的和/或解释式的(去)整编[20]。这样的灵活性允许应用开发者有选择地对时间和空间需求进行权衡,这对于高性能、实时、和/或嵌入式分布系统来说是至关紧要的。
实时对象适配器(Real-time
Object Adapter):对象适配器使仆人与ORB相关联,并将到来的请求多路分离给仆人。TAO的实时对象适配器使用理想哈希[21]和主动式多路分离[22]优化来在恒定的O(1)时间内分派仆人操作,而不管IDL接口中定义的活动连接、仆人及操作的数目。
运行时调度器(Run-time
Scheduler):TAO的运行时调度器[19]将应用QoS需求(比如限定端到端响应时间以及满足定时调度最后期限)映射到ORB终端/网络资源(比如CPU、内存、网络连接,以及存储设备)。TAO的运行时调度器同时支持静态[10]和动态的[23]实时调度策略。
实时ORB核心(Real-time ORB Core):ORB核心将客户请求递送给对象适配器,并将响应(如果有的话)返回给客户。TAO的实时ORB核心[24]使用多线程、占先式、基于优先级的连接和并发体系结构[20]来提供高效和可预测的CORBA协议引擎。TAO的ORB核心允许将定制的协议插入ORB,而不会影响标准的CORBA应用编程模型[25]。
实时I/O子系统(Real-time I/O
subsystem):TAO的实时I/O(RIO)子系统[26]把对CORBA的支持扩展进OS中。RIO分配优先级给实时I/O线程,以实施应用组件和ORB终端系统资源的可调度性。在与高级硬件集成时(比如下面描述的高速网络接口),RIO可以(1)及早在优先级化的内核线程上执行I/O事件的多路分离,以避免基于线程的优先级调换,以及(2)维护清晰的优先级流,以避免基于包的优先级调换。TAO还可以高效地,并尽可能地在缺乏高级QoS特性的传统I/O子系统上可预测地运行。
高速网络接口(High-speed
network interface):“菊花链”(daisy-chained)网络接口是TAO的I/O子系统的核心,它含有一或多个ATM端口互连控制器(APIC)芯片[27]。APIC被设计用于维持2.4 Gbps的双向合计数据率,使用零复制缓冲优化来避免在终端层之间复制数据。TAO还运行在传统的实时互连上,比如VME底板和多处理器共享内存环境;以及TCP/IP上。
TAO内部情况(TAO internals):TAO的开发使用了称为ACE[28]的较低级中间件。ACE实现通信软件的核心并发和分布模式[8],并且提供可复用的C++包装外观和构架组件来支持高性能实时应用和像TAO这样的较高级中间件的QoS需求。ACE和TAO运行在广泛的OS平台上,包括Win32、大多数版本的UNIX,以及实时操作系统,比如Sun/Chorus ClassiX、LynxOS和VxWorks。
为加速实现我们的项目目标,并避免重新发明已有的组件,我们在SunSoft IIOP之上对TAO进行开发。SunSoft IIOP是可自由使用的Internet Inter-ORB Protocol(IIOP)版本1.0的C++参考实现。尽管SunSoft IIOP提供了CORBA ORB的核心特性,它还是有以下局限:
缺乏标准的ORB特性:尽管SunSoft IIOP提供了ORB核心、IIOP 1.0协议引擎,以及DII和DSI实现,它还缺乏IDL编译器、接口仓库和实现仓库,以及可移植对象适配器(Portable Object Adapter,POA)。TAO实现了所有这些遗漏的特性,并提供了更新的CORBA特性:异步方法调用[29]、实时CORBA[19]特性[30],以及容错CORBA特性[31, 32]。
缺乏IIOP优化:由于大量的整编/去整编开销、数据复制,以及很高的函数调用开销,SunSoft IIOP在高速网络上执行得很糟糕。因此,我们应用了一系列优化法则模式[22],显著地改善了它的性能[33]。指导我们的优化法则包括:(1)为常见情况进行优化,(2)消除不必要的浪费,(3)用高效的专用方法来替换通用方法,(4)如果可能,预先计算值,(5)存储冗余状态,以加速昂贵的操作,(6)在层与层间传递信息,以及(7)为处理器缓存亲缘性进行优化。
本论文并不讨论TAO怎样解决上面概述的SunSoft IIOP的局限,[10, 20]对它们进行了详细讨论。相反,我们关注TAO怎样在使用模式保留它的QoS能力的同时、实现克服了以下SunSoft IIOP局限的ORB:
缺乏可移植性:像大多数通信软件应用一样,SunSoft IIOP是直接使用低级网络和OS API(比如socket、select和POSIX Pthreads)编写的。这些API不仅麻烦而易错,它们还不能跨平台移植,例如,许多操作系统都缺乏Pthreads支持。3.3.3.1演示我们怎样使用包装外观(Wrapper Fa?ade)模式[15]来改善TAO的可移植性。
缺乏可配置性:像许多ORB和其他中间件一样,SunSoft IIOP是被静态配置的,这使得不直接修改源码、就难以对其进行扩展。这违背了TAO的一个关键设计目标:对不同应用需求和系统环境的动态适配。3.3.3.7和3.3.3.8解释我们是怎样使用抽象工厂(Abstract Factory)[15]、策略(Strategy)[15]和组件配置器(Component Configurator)[8]模式来为不同的使用情况简化TAO的配置的。
缺乏软件内聚性:像许多应用一样,SunSoft IIOP专门解决特定的问题,也就是,实现ORB核心和IIOP协议引擎。它使用紧耦合的、特定的、且将关键ORB设计决策硬编码的实现来完成这一工作。3.3.3.7和3.3.3.6阐释我们怎样使用抽象工厂和策略来减少不必要的耦合,并在将SunSoft IIOP发展为TAO时增加内聚性。
使用ORB中间件的一个关键动机是将复杂、较低级的分布式系统基础构造任务从应用开发者处转移到ORB开发者那里。ORB开发者负责实现可复用的中间件组件,由它们来处理连接管理、进程间通信、并发、传输端点多路分离、调度、分派、(去)整编,以及错误处理。这些组件通常被编译进运行时ORB库中、与使用ORB组件的应用对象链接,并在一或多个OS进程中执行。
尽管这样的事务分离可以简化应用开发,它还有可能会产生不灵活和低效的应用及中间件体系结构。其主要原因是许多传统ORB都是由ORB开发者在编译时和链接时静态配置的,而不是由应用开发者在安装时或运行时动态配置的。静态配置的ORB有以下缺点[28]:
不灵活性:静态配置的ORB将各个组件的实现与内部ORB组件的配置紧密地耦合在一起,也就是,哪些组件一起工作以及它们怎样一起工作。结果,对静态配置的ORB进行扩展需要修改已有源码。在商业的非开放源码的ORB中,应用开发者可能无法获得这些代码。
即使源码可用,扩展静态配置的ORB还需要重新编译和链接。而且,任何目前正在执行的ORB及其相关对象都必须关闭并重启。这样的静态重配置过程对于某些应用领域并不很适用,比如需要7 * 24可用性的电信呼叫处理[34]。
低效率:静态配置的ORB在空间和时间两方面都可能是低效的。如果不必要的组件总是被静态地配置进ORB中,就可能会产生空间的低效率。这可能会增加ORB的内存占用,迫使应用为它们不需要的特性付出空间代价。对于嵌入式系统,比如蜂窝电话或电信中继线卡[35],过大的内存占用是特别成问题的。
时间低效则可能来自限制ORB使用静态配置的算法或数据结构来处理关键任务,从而使得应用开发者难以定制ORB来处理新的使用情况。例如,实时航空控制系统[11]常常可以离线地实例化它们的所有仆人。这些系统可以从这样的ORB中获益:它使用理想哈希或主动式多路分离[36]来将到来的请求多路分离给仆人。因而,对于关键任务系统来说,被静态配置以使用通用的、“一劳永逸”的多路分离策略(比如动态哈希)的ORB的执行可能会很糟糕。
理论上,上面描述的静态配置的缺点是内在于ORB的,不应该直接影响应用开发者。但是实际上,应用开发者会不可避免地受到影响,因为ORB中间件的质量、可移植性、可用性,以及性能都被降低了。因此,改善ORB可扩展性的有效方式是开发可进行静态和动态配置的ORB中间件。
通过动态配置,开发者可以有选择性地集成关键ORB策略(比如连接管理、通信、并发、多路分离、调度,以及分派)的定制实现。这样的设计使得ORB开发者能够集中关注ORB组件的功能,而不用过早地使自己陷入这些组件的的特定配置。而且,动态配置还使得应用开发者和ORB开发者能够在系统生存期的后期(也就是,在安装时或运行时)改变设计决策。

图3-3 ORB可扩展性维度
图3-3演示以下ORB可扩展性的关键维度:
下面,我们沿着上面概述的每一维度描述用于增强TAO的可扩展性的模式语言。
这一部分使用TAO作为个案研究,演示通过降低组件间的耦合来帮助应用和ORB开发者构建、维护和扩展通信软件的模式语言。图3-4显示该模式语言中我们用于为TAO开发可扩展ORB体系结构的模式。详细地描述每一模式、或讨论TAO中使用的所有模式已超出了本论文的范围。相反,我们聚焦于关键模式是怎样改善实时ORB中间件的可扩展性和性能的。[9, 15]中的参考文献有对这些模式的全面描述,[8]解释这些模式是怎样被编织在一起来构成模式语言的。

图3-4 将模式语言应用于TAO
下面概述这一语言中的模式的意图和使用:
包装外观(Wrapper
Fa?ade)[8]:该模式将现有非OO API所提供的功能和数据封装在更为简洁、健壮、可移植、可维护和内聚的OO类接口中。TAO使用此模式来避开OS专有的系统调用(比如Socket API或POSIX线程)的麻烦、不可移植,以及非类型安全的编程。
反应堆(Reactor)[8]:该模式构造事件驱动的应用、特别是服务器,它们从多个客户那里并发地接收请求,但依次对它们进行处理。当I/O事件在OS中发生时,TAO使用此模式来同步地通知ORB特有的处理器。反应堆模式驱动TAO的ORB核心中的主事件循环,接受连接并接收/发送客户请求/响应。
接受器-连接器(Acceptor-Connector)[8]:该模式使连接建立及服务初始化与服务处理去耦合。TAO在服务器和客户上的ORB核心中使用此模式,以被动和主动地建立独立于底层传输机制的GIOP连接。
领导者/跟随者(Leader/Followers)[8]:该模式提供一种高效的并发模型,在其中多个线程轮流享有一组事件源,以检测、多路分离、分派和处理发生在事件源上的服务请求。TAO使用此模式来便利多种并发策略的使用;这些策略可在运行时被灵活地配置进ORB核心。
线程专有存储(Thread-Specific
Storage)[8]:该模式允许多个线程使用“逻辑上全局”的访问点来获取相对于线程来说的局部对象,而又不会为每次访问对象带来锁定开销。TAO使用此模式来使实时应用的锁竞争和优先级调换最小化。
策略(Strategy)[15]:该模式提供的抽象用于从若干候选算法中进行选择,并将其包装进对象中。在整个软件体系结构中,TAO大量地使用此模式来为并发、通信、调度和多路分离配置自定义ORB策略。
抽象工厂(Abstract
Factory)[15]:该模式提供单一组件来构造多个相关对象。TAO使用此模式来将它的成打的策略对象合并进一些可管理的抽象工厂中,这些工厂可一起被方便而一致地重配置进客户和服务器中。TAO组件使用这些工厂来访问相关策略,而不用明确地指定它们的子类名。
组件配置器(Component
Configurator)[8]:该模式允许应用在运行时链接它的组件实现,或解除其链接,而不必修改、重编译或静态重链接此应用。它还支持将组件重配置进不同的进程中,而不必关闭和重启运行中的进程。TAO使用此模式来动态地替换抽象工厂实现,以在运行时定制ORB的特性。
组成此模式语言的模式的使用并不局限于ORB或通信中间件。它们已被应用于其他许多通信软件领域中,包括电信呼叫处理和交换、航空航班控制系统、多媒体电话会议,以及分布式交互模拟,等等。
在下面的讨论中,我们概述在开发可扩展实时ORB时,关键的设计挑战所带来的压力。我们还将描述我们的模式语言中的哪些模式消除了这些压力,并解释TAO是怎样使用它们的。此外,我们还显示了在ORB中,怎样因为这些模式的缺乏而未能消除这些压力。为具体地演示后面这一点,我们将TAO和SunSoft IIOP作了比较。因为TAO发展自SunSoft IIOP,所以它提供了理想的基准来评估模式对于ORB中间件的软件质量的影响。
上下文:ORB的一种角色是将应用特有的客户和仆人与低级系统编程的细节相屏蔽。因而,ORB开发者,而不是应用开发者,负责处理麻烦的低级网络编程任务,比如多路分离事件、通过网络发送和接收GIOP消息,以及派生线程来并发地执行客户请求。图3-5演示SunSoft IIOP使用的一种常用方法,它在内部使用系统机制(比如socket、select和POSIX threads)来进行编程。

图3-5 SunSoft IIOP操作系统接口
问题:开发ORB是十分困难的。如果开发者使用低级的系统机制,开发会变得困难;这些机制使用像C这样的语言编写,常常会产生以下问题:
我们怎样才能在实现ORB时避免访问低级的系统机制呢?
解决方案?/FONT>>包装外观模式:使用包装外观模式是避免直接访问系统机制的有效方式 [8],它是外观模式[15]的一种变种。外观模式的意图是简化子系统的接口。包装外观的意图更为具体:它提供类型安全、模块化和可移植的OO接口,封装低级的、独立的系统机制,比如socket,select和POSIX线程。通常,包装外观应在现有系统级API不可移植和非类型安全时应用。
在TAO中使用包装外观模式:TAO通过ACE[28]提供的包装外观来访问所有系统机制。图3-6演示ACE
C++包装外观怎样通过使用类型安全的OO接口来封装和增强本地OS的并发、通信、内存管理、事件多路分离和动态链接机制,改善TAO的健壮性和可移植性。ACE提供的OO封装减少了TAO对直接访问弱类型系统API的需要。因而,C++编译器可以在编译时、而不是等到问题在运行时发生的时候,检测类型系统违例。

图3-6 将包装外观模式用于封装本地OS机制
ACE包装外观使用C++特性来消除额外的类型安全性和抽象层次所带来的性能开销。例如,内联被用于消除调用小方法的额外开销。同样地,静态方法被用于消除每次调用都传递C++
this指针的开销。
尽管ACE包装外观解决了若干常见的低级开发问题,它们还仅仅是迈向开发可扩展ORB的第一步。这一部分描述的其他模式在ACE 包装外观提供的封装之上构建,以致力于更有挑战性的ORB设计问题。
上下文:ORB核心负责对来自多个客户的I/O事件进行多路分离,并分派与它们相关联的事件处理器。例如,服务器端的ORB核心侦听新客户连接,并从相连的客户那里读取GIOP请求、或将GIOP响应写往相连的客户。为确保有多个客户时的响应性,ORB核心使用OS事件多路分离机制来等待连接、读取或写入事件在多个Socket句柄上发生。常见的事件多路分离机制包括select、WaitForMultipleObjects、I/O完成端口,以及线程。
图3-7演示SunSoft IIOP的典型的事件多路分离序列。在(1)中,服务器通过(2)调用对象适配器的get_request、进入它的事件循环。get_request随即(3)调用server_endpoint的静态方法block_for_connection。该方法管理服务器端连接管理的所有方面,范围从连接建立到GIOP协议处理。ORB阻塞(4)在select上,直到I/O事件(比如连接事件或请求事件)发生。当请求事件发生时,block_for_connection多路分离该请求给特定的server_endpoint,并(5)分派该事件给此端点。随后ORB核心中的GIOP引擎(6)从Socket获取数据,并将其传递给对象适配器,后者将其多路分离、整编,并(7)分派适当的方法upcall给用户提供的仆人。

图3-7 SunSoft IIOP事件循环
问题:开发ORB核心的一种方式是将其硬编码为使用某种事件多路分离机制,比如select。但是,仅依赖一种机制是不合需要的,因为没有哪种方案在所有平台上都是高效的,或能满足所有的应用需求。例如,异步I/O完成端口在Windows
NT上是异常高效的[37],而同步线程在Solaris上是高效的多路分离机制。
开发ORB核心的另一种方式是将它的事件多路分离代码与执行GIOP协议处理的代码紧密地耦合在一起。例如,SunSoft IIOP的事件多路分离逻辑不是一种自包含的组件。相反,它与后续的由对象适配器和IDL
Skeleton进行的客户请求事件的处理紧密地缠绕在一起。但是,在这种情况下,多路分离代码不能被其他通信中间件应用(比如HTTP服务器[37]或视频点播服务器)当作“黑盒子”组件来复用。而且,如果引入了新的ORB线程策略或对象适配器请求调度算法,就必须重写ORB核心的相当一部分。
那么ORB实现怎样使自身与特定的事件多路分离机制去耦合,并使它的多路分离代码与它的处理代码去耦合呢?
解决方案?/FONT>>反应堆模式:应用反应堆模式是降低耦合并增加ORB核心的可扩展性的有效方式
[8]。该模式支持同步的多路分离和多个事件处理器的分派,这些处理器由可并发到达的来自多个来源的事件触发。反应堆模式通过集成事件的多路分离和相应的事件处理器的分派,简化了事件驱动的应用。一般而言,反应堆模式应该在下面的情况下使用:应用或组件(比如ORB核心)必须处理来自多个客户的并发事件、而又不能与单一的低级机制(比如select)紧密地耦合在一起。
注意包装外观的应用并不足以解决上面概述的事件多路分离问题。Select的包装外观可以略微改善ORB核心的可移植性。但是,仅仅是这个模式并不能消除这样的需要:完全使低级的事件多路分离逻辑与ORB核心中较高级的客户请求处理去耦合。认清包装外观的局限,并随之应用反应堆模式来克服此局限,是对模式语言、而不仅是独立的模式进行应用的好处之一。
在TAO中使用反应堆模式:如图3-8所示,TAO使用反应堆模式来在它的ORB核心中驱动主事件循环。TAO服务器(1)在ORB核心的Reactor中发起事件循环,在这里它(2)阻塞在select上,直到有I/O事件发生。当GIOP请求事件发生时,Reactor将请求多路分离给适当的事件处理器;此处理器是与每个相连Socket关联的GIOP
Connection_Handler。随后Reactor(3)调用Connection_Handler::handle_input,它(4)将请求分派给TAO的对象适配器。对象适配器将请求多路分离给适当的仆人的upcall方法,并且(5)分派此upcall。

图3-8 在TAO的事件循环中使用反应堆模式
通过使TAO的ORB核心的事件处理部分与底层的OS事件多路分离机制去耦合,反应堆模式增强了TAO的可扩展性。例如,WaitForMultipleObjects事件多路分离系统调用可被用在Windows
NT上,而select可被用在UNIX平台上。而且,反应堆模式简化了新事件处理器的配置。例如,增加新的Secure_Connection_Handler、来执行所有网络通信的加密/解密将不会影响Reactor的实现。最后,与SunSoft
IIOP中的事件多路分离代码(它与一种使用情况紧耦合在一起)不同,TAO所用的反应堆模式[8]的ACE实现已被应用于其他许多OO事件驱动的应用中,范围从HTTP服务器[37]到实时航空系统的基础结构[11]。
上下文:管理连接是ORB核心的另一项关键责任。例如,实现IIOP协议的ORB核心必须建立TCP连接,并为每个IIOP
server_endpoint初始化协议处理器。通过使连接管理逻辑局限在ORB核心中,应用特有的仆人可以只聚焦于处理客户请求,而不用处理低级的网络编程任务。
但是ORB核心并不只局限于在IIOP和TCP传输之上运行。例如,在TCP可以可靠地传输GIOP请求的同时,它的流控制和拥塞控制算法可能会阻碍它被作为实时协议来使用[10]。同样地,当客户和仆人共驻于同一终端系统时,使用共享内存传输机制可能更为高效。因而,ORB核心应该灵活到能支持多种传输机制[16]。
问题:CORBA体系结构明确地使(1)ORB核心执行的连接管理任务与(2)应用特有的仆人所执行的请求处理去耦合。但是,实现ORB的内部连接管理活动的常见方法是使用低级的网络API,比如socket。同样地,ORB连接建立协议常常与它的通信协议紧密地耦合在一起。
例如,图3-9演示SunSoft IIOP的连接管理结构。SunSoft IIOP的客户端实现了一种硬编码的连接缓存策略,它使用了client_endpoint对象的链表。如图3-9所示,无论何时(1)client_endpoint::lookup被调用,都会遍历该链表,以找到一个未用的端点。如果在缓存中没有到服务器的未用端点,一个新的连接(2)被发起;否则现有连接就会被复用。同样地,服务器端使用server_endpoint对象的链表来生成(3)select事件多路分离机制所需的读/写位掩码。该链表维护的被动传输端点(4)接受连接并(5)接收来自与服务器相连的客户的请求。

图3-9 SunSoft IIOP中的连接管理
SunSoft IIOP的设计的问题是它紧密地耦合(1)使用Socket网络编程API的ORB的连接管理实现与(2)使用GIOP通信协议的TCP/IP连接建立协议,从而产生了以下缺点:
那么ORB核心的连接管理组件怎样才能支持多种传输,并允许在开发周期的后期灵活地(重)配置与连接有关的行为呢?
解决方案?/FONT>>接受器-连接器模式:应用接受器-连接器模式是增加ORB核心连接管理和初始化的灵活性的有效方式
[8]。该模式使连接初始化与连接端点被初始化后所执行的处理去耦合。其中的Acceptor组件,也就是,ORB核心的服务器端,负责被动的初始化。相反,其中的Connector组件,也就是,ORB核心的客户端,负责主动的初始化。一般而言,接受器-连接器模式应该在这样的情况下应用:客户/服务器中间件必须允许灵活地配置网络编程API、且必须维护初始化角色的适当分离。
在TAO中使用接受器-连接器模式:TAO联合使用接受器-连接器模式与反应堆模式来处理GIOP/IIOP通信的连接建立。在TAO的客户端ORB核心中,Connector发起到服务器的连接,以响应操作调用或到远地对象的显式绑定。在TAO的服务器端ORB核心中,Acceptor创建GIOP
Connection Handler来服务每个新的客户连接。Acceptor和Conntion_Handler都派生自Event_Handler,从而使得它们可以被Reactor自动分派。
TAO的Acceptor和Connector可通过任意的传输机制来配置,比如ACE包装外观所提供的Socket或TLI。此外,如3.3.3.4所描述的,TAO的Acceptor和Connector可以通过自定义的策略来参数化,以选择适当的并发机制。
图3-10演示接受器-连接器模式在TAO的ORB核心中的使用。当客户(1)调用远地操作时,它通过Strategy_Connector发出connect调用。这个Strategy_Connector(2)查询它的connection
strategy,以获取一个连接。在此例中,客户使用 “缓存式连接策略”、重复利用到服务器的连接,并只在现有连接全忙时创建新连接。这样的缓存策略使连接设置时间最小化,从而减少了端到端的请求响应时间。
在服务器端ORB核心中,Reactor通知TAO的Strategy_Acceptor(3)接受新连接的客户,并创建Connection_Handler。Strategy_Acceptor将并发机制的选择委托给TAO的并发策略中的一种,例如,在3.3.3.4描述的反应式、thread-per-connection,或thread-per-priority策略。在Connetion_Handler在ORB核心中被启用后,(4)它在连接上执行必要的GIOP协议处理,(5)并最终通过TAO的对象适配器分派(6)请求给适当的的仆人。

图3-10 在TAO的连接管理中使用接受器-连接器模式
上下文:在对象适配器将客户请求分派给适当的仆人后,仆人就执行该请求。执行可能发生在与接收它的Connection_Handler相同的线程控制中,相反,也可能发生在不同的线程中,与其他请求并发执行。
实时CORBA规范[38]定义了一种线程池API。此外,CORBA规范还为应用定义了POA接口,以指定由单个线程、或使用ORB内部的多线程策略来处理所有请求。要满足应用的QoS需求,开发高效地实现这些不同的并发API的ORB是很重要的[24]。并发允许长时间运行的操作同时执行,而不会妨碍其他操作的进行。同样地,要使实时系统的分派响应时间最小化,占先式多线程的使用至关紧要[11]。
并发常常通过OS平台上的多线程能力来实现。例如,SunSoft IIOP支持图3-11中所示的两种并发体系结构:单线程反应式体系结构和thread-per-connection体系结构。SunSoft
IIOP的反应式并发体系结构在单线程中使用select来将每个到达的请求分派给单独的server_endpoint对象,后者随即从适当的OS内核队列中读取请求。在(1)中,请求到达,并由OS进行排队。然后,select(2)通知相关联的server_endpoint,有等待中的请求。Server_endpoint最后(3)从队列中读取请求并进行处理。

图3-11 SunSoft IIOP并发体系结构
相反,SunSoft IIOP的thread-per-connection体系结构在它自己的线程控制中执行每个server_endpoint,为所有在它的线程内的连接上到达的请求进行服务。在连接建立后,select在连接的描述符上等待事件。当(1)OS接收到请求时,线程执行select(2)从队列中读取一个请求,并(3)将它传给server_endpoint进行处理。
问题:在许多ORB中,并发体系结构是直接使用OS平台的多线程API来编程的,比如POSIX线程API[39]。但是,这种方法有若干缺点:
在使用低级线程API来开发ORB时,它们难以通过新的并发策略来进行扩展,而又不影响其他ORB组件。例如,给SunSoft IIOP增加thread-per-request体系结构需要进行大量改动,以(1)在协议处理过程中将请求存储在线程专有存储(TSS)变量里,(2)将专有钥通过对象适配器中的调度和去整编步骤传递给TSS变量,以及(3)在分派仆人上的操作之前、访问存储在TSS中的请求。因而,并没有一种容易的方法来修改SunSoft
IIOP的并发体系结构,而不彻底改变它的内部结构。
那么ORB怎样才能支持简单、可扩展和可移植的并发机制呢?
解决方案?/FONT>>领导者/跟随者模式:应用领导者/跟随者模式是增加ORB并发策略的可移植性、正确性和可扩展性的有效方式 [8]。该模式提供一种高效的并发模型,在其中多个线程依次享有一组事件源,以检测、多路分离、分派和处理发生在事件源上的服务请求。通常,领导者/跟随者模式应该在应用需要使上下文切换、同步和数据复制开销最小化、同时还允许多个线程并发运行时使用。
在包装外观提供了可移植性的基础的同时,它们还只是在低级本地OS API之上的一层语法“薄板”。而且,外观的语义行为在多个平台间仍有可能会变化。因此,领导者/跟随者模式定义了一种更高级的并发抽象,把TAO与低级线程外观的复杂性屏蔽开来。通过为ORB开发者提高抽象层次,领导者/跟随者模式使得定义更为可移植、灵活,以及方便地编程的ORB并发策略变得更为容易。例如,如果在线程池中的线程数为1,领导者/跟随者模式就会像反应堆模式一样工作。
在TAO中使用领导者/跟随者模式:TAO使用领导者/跟随者模式来将GIOP事件多路分离给在线程池中的Connection_Handler。在使用此模式时,应用预先派生固定数目的线程。当这些线程调用TAO的标准的ORB::run方法时,一个线程将成为领导者,并等待GIOP事件。在领导者线程检测到事件后,它将一个任意的线程提升为下一个领导者,随后将事件多路分离给与其相关联的Connection_Handler,由后者相对于ORB中的其他线程并发地处理此事件。这一系列步骤在图3-12中显示。

图3-12 使用领导者/跟随者模式构造TAO的并发策略
如图3-12所示,程序分配线程池,选择领导者线程,以(1)为所有服务器进程中的仆人而在连接上进行select。当请求到达时,领导者线程(2)将其读入内部缓冲区。如果它是对仆人的合法请求,在池中的一个跟随者线程就被释放,成为新的领导者(3)且该领导者线程分派upcall(4)。在upcall被分派后,原来的领导者变成跟随者,并返回线程池。新的请求被排队在Socket端点中,直到池中有线程可用来执行请求。
上下文:领导者/跟随者模式允许ORB中的应用和组件并发地运行。但是,并发的主要缺点是需要序列化对共享资源的访问。在ORB中,常见的共享资源包括动态内存堆、CORBA::ORB_init初始化工厂所创建的ORB伪对象引用、POA中的主动对象映射[22],以及先前描述的Acceptor、Connector和Reactor组件。完成序列化的常用方法是在被多个线程共享的各个资源上使用互斥锁。
问题:在理论上,通过同时执行多个指令流,使ORB多线程化可以改善性能。此外,通过允许每个线程同步地、而不是反应式地或异步地执行,多线程还可以简化ORB的内部设计。但是,在实践中,多线程ORB常常并不比单线程ORB执行得更好,甚或会更糟,原因是(1)获取/释放锁的代价,以及(2)当高优先级和低优先级线程竞争同样的锁时所产生的优先级调换[40]。此外,由于用于避免竞争状态和死锁的复杂的并发控制协议,开发者还难以对多线程ORB进行编程。
解决方案?/FONT>>线程专有存储模式:使用线程专有存储模式是使“序列化对ORB中共享资源的访问”所需的锁定数量最小化的有效方式 [8]。该模式允许ORB中的多个线程使用一个逻辑上全局的访问点来获取线程专有的数据,而不会给每次访问带来锁定开销。
通常,线程专有存储应在下述情况下被应用:必须通过全局可见的访问点来访问由在各个线程内的对象共享的数据;此访问点在“逻辑上”与其他线程共享,但对于每个线程来说在“物理上”却是唯一的。
在TAO中使用线程专有存储模式:TAO使用线程专有存储模式来最小化实时应用的锁竞争和优先级调换。TAO中的各个线程在内部使用线程专有存储来存储它的ORB核心组件,例如,Reactor、Acceptor和Connector。如图3-13所示,当线程访问这些组件中的任何一个时,线程通过使用专有钥来作为线程内部的线程专有状态的索引而获取该组件。因而,访问线程专有ORB状态不需要额外的锁定。

图3-13 在TAO中使用线程专有存储模式
上下文:可扩展ORB必须在它们的对象适配器中支持多种请求多路分离和调度策略。同样地,它们必须在它们的ORB核心里支持多种连接建立、请求传递,以及并发请求处理策略。
问题:开发ORB的一种方式是只提供静态的、不可扩展的策略,它们典型地通过以下方式来配置:
虽然这两种配置方法被广泛地使用,它们仍然是不灵活的。例如,预处理器宏仅支持编译时策略选择,而命令行选项只传达了有限数量的信息给ORB。而且,这些硬编码的配置策略完全脱离了任何它们可能会影响的代码。因而,想要使用这些选项的ORB组件必须(1)知道它们的存在,(2)了解值的范围,以及(3)为每个值提供适当的实现。这样的限制使其难以开发高度可扩展的、通过可透明地配置的策略编写的ORB。
那么ORB怎样(1)允许通过与其他ORB组件无关和透明的方式来替换组件策略的子集,以及(2)封装每种策略的状态和行为来使对一个组件的变动不会偶然地影响整个ORB?
解决方案?/FONT>>策略模式:应用策略模式是支持多种可透明地“插用”(pluggable)的ORB策略的有效方法
[15]。该模式分解出算法可选方案间的相似性,并显式地将策略名与它的算法和状态关联起来。而且,策略模式还去掉了对策略实现的词法依赖,因为应用仅仅通过公共的基类接口来访问专门化的行为。通常,策略模式应该在应用的行为可通过多种可互换策略进行配置时使用。
在TAO中使用策略模式:TAO使用多种策略来分解出常常被硬编码进传统ORB的行为。图3-14中演示了若干这样的策略。例如,TAO在它的对象适配器中支持多种请求多路分离策略(例如,理想哈希
vs. 主动式多路分离[36]),并在它的ORB核心中支持多种连接管理策略(例如,进程级缓存式连接 vs. 线程专有缓存式连接)和处理器并发策略(例如,反应式
vs. 领导者/跟随者的变种)。

图3-14 TAO中的ORB核心和POA策略
上下文:TAO支持许多潜在的策略变种。表3-1显示了一个简单的策略例子,用于创建TAO的两种配置。配置1是航空控制应用,它具有确定性的实时需求[11]。配置2是电子医学成像应用[41],它具有高吞吐量需求。一般而言,正确地合成所有ORB策略必须消除下面两种需求带来的压力:(1)确保语义兼容的策略的配置,以及(2)简化大量独立策略的管理。
|
|
策略配置 |
|||
|
应用 |
并发 |
分派 |
多路分离 |
协议 |
|
航空控制 |
Thread-per-priority |
基于优先级 |
理想哈希 |
VME底板 |
|
医学成像 |
Thread-per-connection |
FIFO |
主动式多路分离 |
TCP/IP |
表3-1实例应用和它们的ORB策略配置
问题:在复杂的ORB软件(以及其他类型的软件)中大量使用策略模式会产生不合需要的副作用:可扩展性变得难以管理,原因如下: