Corba中IDL的设计


(来源:http://www.ccw.com.cn)

从技术的角度来讲,把系统设计成n层结构需要解决系统管理上的一些问题,如网络的延时,系统的反应时间,服务的可用性,负载的管理,分布式的缓冲和分布式的垃圾回收等。而且,对于每一个能提高系统效率的新的解决方案,也会随之带来新的问题。但是这些在设计大型的分布式应用系统的技术上的问题,都可以通过使用一些基本的设计方法和技巧来加以解决。
通过使用迭代(Iterator)的设计模式来定义idl语言,从而解决corba程序中诸如性能管理,缓冲,分布式垃圾回收等问题。
一.性能上的问题
虽然corba的体系结构简化了网络的内在的复杂性。但它不能保证一定可以构造一个高
性能,高效率的系统,要实现这个目标,整个系统的设计一定要考虑到网络固有的结构,主要是以下的三个因素。
1. 远程调用的数量。
2. 数据传输的数量。
3. 不同数据类型的转换和包装。
如果在系统设计的开始加以考虑,这些问题将会得到解决。
在基于corba的体统设计中,idl在组件的设计中起了很大的作用,应为它定义了服务端程序相互遵循的接口标准。
二.IDL的设计
一个通常在IDL设计中被忽视的问题是哪一个接口用于服务器端的应用程序,以及暂时的(transient)和持久的(persisent)corba对象。
1.一个服务器端的应用程序是一个用于实现对象方法的,与语言无关的对象。在corba程序的模型中,服务器端的程序通过可移植对象适配器(Portable Object Adaptor,即POA)向系统注册,从而能一直接受用户对它的调用。
2.同服务器端的对象相比,暂时的corba对象并不用POA向系统注册,他们在用户向系统请求的过程中由服务器端的应用程序生成。这些暂时的corba对象的生命周期不会超过所在进程或生成该对象的线程的生命周期,而且他们的对象句柄并不公开。
3.持久性的corba对象同持久性的状态相关联,有着特殊的用途。
以下主要讨论使用暂时的corba对象来管理大量数据的传输。这个方法在处理可能丢失数据的程序时非常有用,如下例所示:
用户要查询大量的数据,在得到了前20个数据后,另一个用户也提出一个查询,可能的情况是前面查询的数据将丢失。在一个单进程的应用程序中,这不是一个问题。但是在分布式编程和设计中。将会占用很多的网络带宽和cpu的处理时间。
基于如上所述,图一提供了一个客户端和服务器端程序的交互相应的示意图。客户端的代理是个远端的代理类,用于处理同远端服务器程序的连接以及把客户端的请求发送到服务器端。客户端向提供所需服务的服务器端付出请求,服务器端返回一组产品的信息,如果这个结果中有n个产品,则N个元素经过参数转换后在返回。初一看,这个设计是可行的,如果不发生前面所说的不可预料的情况的话。

下面的代码定义了用来实现客户端和服务器端交互作用的IDL接口和方法。该文件中只定义了一个方法,用来返回一组产品的信息。
Module productcatalog
{
struct ProductItem
{
string productName;
};
typedef sequence ProductItemList;
Interface ProductCatalog
{
ProductItemList getProductItems(in string group,in string category,in string status )
Raises {SomeRemoteException};
};
};
从客户端的角度看,对服务器端程序的一个请求,来检索产品的信息需要如下的几步骤。如图二所示.

代理的对象提供了一个包,绑定到一个特定的服务器端的对象实例上来充当一个中间的对象,用来管理对远程服务器对象的调度了管理。它的主要任务包括:
1. 准备一个请求
2. 把调用请求提交给ORB的桩(stub)
3. 捕捉(trap)远端的异常。(最好把它们映射到用户自己定义的异常中)
4. 准备好用于接受特定对象模型的数据结构。这些数据要被客户端的应用程序使用产品类代理的实现如下面的代码所示:
public class ProductServiceProxy
{
private static ProductServiceProxy instance;
private ProductServiceProxy() {}
public static ProductServiceClient getInstance()
{
if(instance==null)
instance=new ProductServiceProxy();
return instance;
}
public ArrayList getProducts(String group,String category,String status)
throws SomeException
{
ArrayList productList=new ArrayList();
Try
{
ProductService productService=LocateRemoteService();
If(productService==null)
//return empty list or throw user defined exception
}
try
{
//get products using criteria,when request returns data,prepare data and store
results in collection object
}
catch(SystemException se){//handle exception }
}
catch(Exception e){//handle exception}
return productList;
}
}
客户端的代理类用单一(singleton)模式来实现,从而可以保证只有一个客户端的代理对象实例生成,对于每一个需要远端调用的请求,LocateRemoteService()方法就会调用,从而来验证远端对象的句柄,如果连接已断开或者对象的句柄已经失效,就会自动绑定,从而能防止失败。
另外的一种IDL的设计是,服务器端程序返回一系列的产品ID号而不是产品本身。
如图三所示:

就网络本身来讲,这两个设计的差别主要在于产品ID号的数据量要远远小于产品本身的数据量。而用户也可以选择其中的一个ID号来察看产品的详细信息。这个设计同前面的设计相比更趋合理。因为它能根据用户的需求来传送数据,从而不会导致 大量无用数据的传输。
但是这个设计方案也有一个弊端,即要决定是否要显示产品ID号。而检索每一产品都需要查询所有的产品信息,这样就会带来同第一个方案相同的问题。因为为了 数据,需要进行n+1次的远程调用。
第三种方案如图四所示,它主要重新设计了IDL,利用用户的习惯和系统计算的限制。这个设计上的改进主要基于如下的考虑。

对于很多的用户界面的应用程序来讲,如基于JAVA SWING或WEB的程序,一次现实多少数据常常会受到屏幕大小的限制。对与WEB的程序来讲,查询的结果常 常会被分为很多页,由导航栏来控制信息的显示。而且,一般来说,用户常常需要一定 的时间来浏览显示的信息,然后才会选择下一步的动作。这就给程序的运行了调用带来 了一定意见的缓冲。
为了实现这个目的,需要运用如下的机制来规范信息的流动了传输。一个方法就是使用迭代(Iterator)设计模式.因此需要重新设计IDL,从而能使大量的数据分成多 个块来传输。为了方便程序的实现,需要首先定义一个基本的迭代接口来规定远程迭代 类的方法。下面的代码定义了一个基本的BaseIterator和BaseListIterator接口。
/* Base Iterators in IDL */
#include “util/exceptions/idlexceptions.idl”
module iterator
{
interface BaseIterator
{
Boolean hasNext() raises(SomeRemoteException);
Short count() raises(SomeRemoteException);
};
interface BaseListIterator
{
Boolean hasPrevious() raises(SomeRemoteException);
Short previousIndex() raises(SomeRemoteException);
Boolean hasNext() raises(SomeRemoteException);
Short nextIndex() raises(SomeRemoteException);
Short count() raises(SomeRemoteException);
};
};
/* util/iterator/iterator.idl */
这两个接口为下面跟具体业务有关的接口的实现,如ProductIterator接口的实现提供了基础。
/* 具体业务有关的接口定义,本例中为ProductIterator接口 */
#include “util/iterator/iterator.idl”
module productcatalog
{
struct productItem
{
string productName;
……
};
typedefsequence ProductItemList;
interface ProductIterator : iterator::BaseListIterator
{
ProductItem next() raises (SomeRemoteException);
ProductItemList nextBlock(in short size) raises (SomeRemoteException);
ProductItem previous() raises (SomeRemoteException);
ProductItemList previousBlock(in short size) raises (SomeRemoteException);
};
interface ProductCatalog
{
ProductIterator getProductItems(in string group,in string category,in string status)
raises (SomeRemoteException);
};
};
}
值得注意的是,在上面的代码中,ProductCatalog接口中的getProductItems方法返回的是ProductIterator而不是先前的ProductItemList;
在服务器端,实现ProductIterator的类在管理数据传输的过程中起到了集成的作用。而且从本质上来讲,ProductIterator接口是个暂时的(transient)corba对象,在调用getProductItems()的过程中生成。之所以是暂时的,是因为它的内容有查询的结果决定。如果查询后返回的结果是集合型的或者通过如ArrayList的集合类返回,则ProductIterator的实现就比较简单,因为ArrayList类本身就已经提供了了ListIterator和Iterator。另外一个就是在服务器端程序实现ProductCatalog上。GetProductItems方法返回ProductIterator对象的句柄,而不是 方案一中的Product IDs和方案二中的Products.由于Product和Product ID都是Corba的数据类型,因此服务器端的程序需要进行数据类型的映射和转换。而在方案三中,由于使用了ProductIterator,数据类型的映射就由服务器端的对象实现来决定了。
在客户端,代理程序发起初始的请求,同时缓存了远端的迭代(Iterator)类的句柄。代理程序也可以在返回之前立刻得到下面的N个数据。图五列出了客户端和服务器端组件的交互作用的过程。
当客户端初始化一个请求时,客户端的代理程序首先检查缓冲中是否已经存在该请求的结果。如果缓冲中没有相关的信息,代理就会向服务器发出查询请求。查询的结果是个远端的Iterator(一个corba对象的句柄),并保存在客户端的代理中。接下来的客户端请求将会检索一个或一堆的产品信息。当然,你也可以把产品信息保存在缓冲区。当初始化一个新的查询时,你也可以先清除掉缓冲中的信息。下面的代码重新定义了实现了ProductServiceProxy接口。
Public class ProductServiceProxy
{
private int nItems=15;
private ProductIterator remoteIterator;
private static ProductServiceProxy instance;
private ProductServiceProxy() {}
public static ProductServiceClient getInstance()
{
if(instance==null)
instance=new ProductServiceProxy();
return instance;
}
public ArrayList getProducts(String group,String category,String
status) throws SomeException
{
ArrayList ProductList = new ArrayList();
Try
{
ProductService productService= locateRemoteProductService();
If(productService==null)
//return empty list or throw user defined exception
try
{
remoteIterator=productService.getProducts(…..);
}
catch(SystemException se){//handle exception}
}
catch(Exception e){//handle exception}
return this.getMoreProducts(nItems);
}
public ArrayList getMoreProducts(int nItems) throws SomeException
{
//retrive the next 10-15 items using cache iterator
}
}
由于远端的迭代类(Iterator)是个暂时的CORBA对象,它要依赖于服务器端程序生命周期。因此,系统的设计一定要保证在客户端程序完成之前,该对象一定要存活。
三.结论。
企业级应用程序的设计比较复杂。前期的设计对以后系统的性能会有很大的影响。而在CORBA环境中,IDL的设计就显得尤为重要。好的IDL设计,充分利用JDK的API,如计时器,垃圾回收机制,集合框架能大大的提高系统的性能。从而能构造一个强壮的,高度可用的分布式系统。