ICE中间件说明文档
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ICE中间件说明文档相关的知识,希望对你有一定的参考价值。
ICE中间件说明文档
1 ICE中间件简介
2 平台核心功能
2.1 接口描述语言(Slice)
2.2 ICE运行时
2.2.1 通信器
2.2.2 对象适配器
2.2.3 位置透明性
2.3 异步编程模型
2.3.1 异步方法调用
2.3.2 异步方法分派
2.4 订阅/发布编程模型
2.5 动态服务管理(IceBox)
2.6 ICE网格计算
2.6.1 分布式部署
2.6.2 负载均衡
2.6.3 注册中心集群
2.7 IceSSL应用
2.8 持久化存储(IceFreeze)
3 ICE平台功能研究小结
1 ICE中间件简介
Ice 是 Internet Communications Engine 的简称,是一种面向对象的中间件平台,支持面向对象的RPC编程,其最初的目的是为了提供类似CORBA技术的强大功能,又能消除CORBA技术的复杂性。该平台为构建面向对象的客户-服务器应用提供了工具、API 和库支持。
由ICE平台开发的应用支持跨平台部署,多语言编程,其中服务端支持C++、JAVA、C#、Python等几种编程语言,客户端还支持Ruby、php。ICE支持同步/异步、订阅/发布的编程模式,支持分布式部署,网格计算,内置负载均衡功能,支持SSL安全加密。
ICE 既可以把TCP/IP、也可以把UDP 用作底层传输机制, 还允许你把SSL 用作传输机制,让客户与服务器间的所有通信都进行加密。
ICE平台还提供一序列的编程库,包括线程模型库、定时器、信号处理器,这些编程库的API全部支持线程安全。
ICE目前的最新版本为ICE3.4.1,是2010年6月份发布的,ICE3.1.1(2006年10月份发布)及以前的版本支持Windows、LINUX、AIX、HP-UX、Solaris操作系统,但是ICE3.2.0(包含)以后的版本官方不再保证对AIX的完全支持,而3.3.1(包含)以后版本连HP-UX都不保证完全支持,虽然源代码中仍然提供了这两个操作系统下的源代码编译文件Make.rules.AIX和 Make.rules.HP-UX,根据官方的论坛说明是因为他们基本上没有这两个系统的商业客户,所以不再对这两个平台进行代码测试,如果有需要的需要和他们联系。
2 平台核心功能
2.1 接口描述语言(Slice)
Slice (Specification Language for Ice)是一种用于使对象接口与其实现相分离的基础性抽象机制。 Slice 在客户与服务器之间建立合约,描述应用所使用的各种类型及对象接口。这种描述与实现语言无关,所以编写客户所用的语言是否与编写服务器所用的语言相同,这没有什么关系。
Slice 定义由编译器编译到特定的实现语言。编译器把与语言无关的定义翻译成针对特定语言的类型定义和API。开发者使用这些类型和API 来提供应用功能,并与Ice 交互。目前ICE支持Slice到C++、JAVA、C#、Python、Ruby、PHP的语言映射。
Slice示例:
module Family //对应C++的名字空间
{
interface Child;
sequence<Child*> Children; // OK 可以利用C++的Vector
interface Parent //对应C++的类
{
Children getChildren(); // OK
};
interface Child
{
Parent* getMother();
Parent* getFather();
};
};
利用Slice 定义接口/类方法时和其他编程语言很相似,需要一个返回值和若干个参数,但是需要注意的是Slice不支持引用方式的参数传递,参数要么为输入参数,要么为输出参数,不同时为in和out.作为out参数的时候,不管客户端对out参数的初始赋值是什么,在服务端都取不到该,但是服务端可以对该参数进行赋值,再传递给客户端。例如:
interface Hello
{
string sayHello(int num,out string strMsg);
};
还要注意的是out参数一定是放在所有输入参数后面,不允许交叉。
2.2 ICE运行时
2.2.1 通信器
Ice run time 的主进入点由本地接口Ice::Communicator 表示,通常一个应用服务器只使用一个通信器,一个通信器实例管理着运行时资源,主要的运行时资源包括:
² 线程池:负责接收客户端连接和请求。
² 配置属性:Ice run time 的各个方面可以通过属性进行配置。每个通信器都有自己的配置属性
² 插件管理器:插件是用于给通信器增加特性的对象。例如, IceSSL (参见第23章)被实现成插件。每个通信器都有一个插件管理器,这个管理器实现Ice::PluginManager 接口,通过它,你可以访问通信器的插件集。
² 对象工厂:为了实例化从已知基类派生的类,通信器维护有一组对象工厂,能够替Ice run time 对类进行实例化。用户定义的每一个接口/类都隐式地继承同一个基类Ice:Object.
² 缺省定位器:定位器用于把客户请求的对象标识解析为代理对象。
² 对象适配器:对象适配器用于分派接收到的请求,把每个请求转发到正确的Servant。每个对象适配器可以绑定多个ICE对象,不同通信器的对象适配器和对象完全是相互独立的。
2.2.2 对象适配器
一个通信器含有一个或更多对象适配器。对象适配器处在Ice run time和服务器之间的界线上,负有这样一些责任:
² 它把Ice对象映射到到来请求的servant,并把请求分派给每个servant中的应用代码(也就是说,对象适配器实现了一个向上调用接口,把Icerun time 与服务器中的应用代码连接在一起)。
² 它协助进行生命周期操作,使得Ice对象和servant在创建和销毁时不会出现竞争状况。
² 它提供一个或更多传输端点。客户通过这些端点访问适配器所提供的Ice对象。每个对象适配器都有一个或更多servant,对Ice 对象进行体现;同时还有一个或更多传输端点。如果对象适配器拥有的传输端点不止一个,所有向该适配器作了注册的servant 可以在任何一个端点上响应到来的请求。换句话说,如果对象适配器有多个传输端点,这些端点代表的是通往同一组对象的不同通信路径(例如,通过不同的传输机制)。
² 每个对象适配器都只属于一个通信器(但一个通信器可以有多个对象适配器)。
2.2.3 位置透明性
Ice run time 的一个有用的特性是位置透明性:客户无需知道Ice 对象的实现的位置;对某个对象的调用会被自动引导到正确的目标,不管这个对象的实现是在本地地址空间中,在同一台机器上的另一个地址空间中,还是在一台远 地机器上的另一个地址空间中。位置透明性十分重要,因为有了它,我们能够改变对象实现的位置,而不会破坏客户程序,同时,通过使用IceGrid,像域名和端口号这样的信息可以放在应用的外部,不用出现在串化代理中。
2.3 异步编程模型
ICE平台支持客户端异步调用(AMI)和服务端异步分派编程(AMD)。
2.3.1 异步方法调用
异步方法调用(AMI) 这个术语描述的是客户端的异步编程模型支持。如果你使用AMI 发出远地调用,在Ice run time 等待答复的同时,发出调用的线程不会阻塞。相反,发出调用的线程可以继续进行各种活动,当答复最终到达时, Ice run time 会通知应用。通知是通过回调发给应用提供的编程语言对象的。
要使用异步方法调用,只需要给对应的类或者方法前面加上元数据[“ami”],示例如下:
interface Hello
{
[”ami”] string sayHello(int num);
};
通过Slice编译器生成头文件时,会生成对应的方法同步调用方法和异步调用方法,用户在客户端可以自行选择使用同步调用还是异步调用,非常方便。编译后对于每一个异步方法都会生成相应的异步回调类和异步调用代理方法,类的命名方式 AMI_类名_方法名,上述的slice经编译后生成的类为 AMI_Hello_sayHello,同时这个类提供了两个方法:
void ice_response(<params>);
表明操作已成功完成。各个参数代表的是操作的返回值及out 参数。如果操作的有一个非void 返回类型, ice_response 方法的第一个参数就是操作的返回值。操作的所有out 参数都按照声明时的次序,跟在返回值的后面。
void ice_exception(const Ice::Exception &);
表明抛出了本地或用户异常。
客户端只需要从AMI_Hello_sayHello 类继续并实现上述两个方法就可以了。示例代码如下:
class AMI_Hello_sayHelloI : public AMI_Hello_sayHello
{
void ice_response(const ::std::string& strMsg)
{
printf("%s\n",strMsg.c_str());
}
void ice_exception(const Ice::Exception& exception)
{
printf("error\n");
}
};
生成的异步调用代理方法命名为方法名_async,上述示例生成的方法为 sayHello_async.然后客户端的程序异步调用如下:
hello->sayHello_async(AMI_Hello_sayHelloPtr,<params>);
然后只需在ice_response 方法处理返回结果。
也可以调用同步方法:
hello->sayHello(<params>);
2.3.2 异步方法分派
异步方法分派(AMD) 是AMI 的服务器端等价物,在使用AMD 时,服务器可以接收一个请求,然后挂起其处理,以尽快释放分派线程。当处理恢复、结果已得出时,服务器要使用Ice runtime 提供的回调对象,显式地发送响应。
用实际的术语说, AMD 操作通常会把请求数据(也就是,回调对象和操作参数)放入队列 ,供应用的某个线程(或线程池)随后处理用。这样,服务器就使分派线程的使用率降到了最低限度,能够高效地支持大量并发客户。
要使用异步方法调用,只需要给对应的类或者方法前面加上元数据[“amd”],示例如下:
interface Hello
{
[”amd”] string sayHello(int num);
};
和异步方法调用不同的是,异步分派是服务端的,在服务端会生成相应的异步分派类和异步分派方法。服务端的分派器在接收到客户请求时把请求分派给sayHello_async(const Demo::AMD_Hello_sayHelloPtr&, int, const Ice::Current&).然后用户可以在该方法内把任务参数信息放入队列,在队列中循环检查任务并处理,处理完成后调用 AMD_Hello_sayHelloPtr 类的 ice_response 方法返回给客户端。
2.4 订阅/发布编程模型
IceStorm是一个高效的用于ICE应用的发布/订阅服务,IceStorm有几个比较重要的概念:
² 消息:IceStorm的消息和普通的消息队列中间件中描述的消息有点区别,IceStorm 的消息是强类型的,由对某个Slice 操作的调用代表:操作名标识消息的类型,操作参数定义消息内容。要发布消息,可以按普通的方式调用某个IceStorm 代理上的操作。与此类似,订阅者会像收到常规的向上调用(upcall)一样收到消息。所以IceStorm 的消息递送使用的是“推”模式
² 主题:应用要通过订阅某个主题(topic)来表明自己有兴趣接收某些消息。IceStorm 服务器能够支持任意数量的主题,这些主题是动态创建的,通过唯一的名字来区分。每个主题都可以有多个发布者和订阅者。
² 持久模式:IceStorm 拥有一个数据库,里面维护的是关于其主题和链接的信息。但是,通过IceStorm 发送的消息不会被持久地存储,而是会在递送给主题目前的订阅者集之后,马上被丢弃。如果在把消息递送给某个订阅者的过程中发生错误, IceStorm 不会为该订阅者进行消息排队。
² 订阅者出错:因为IceStorm 消息是采用单向语义递送的, IceStorm 只能检测到连接或超时错误。如果在把消息递送给订阅者的过程中, IceStorm 遇到这样的错误,该订阅者就会立刻被解除与该消息对应的主题的订阅。当然用户在使用过程中也可以通过设定QOS参数来改善这个问题,比如重试次数(retryCount),但是对于ObjectNotExistException 或者 NotRegisteredException之类的硬错误,Ice运行时不会重试,而是仍然直接解除订阅关系。
IceStorm支持两个主要的QOS参数reliability 和 retryCount,reliability的取值分别为ordered 和空值,取ordered时,发布者发布的消息会保证按顺序递送给订阅者。
从IceStorm提供的功能来看,对于不需要进行消息持久存储转发的应用来说很适合,但是由于在订阅者出错后立即解除订阅关系,不是由订阅者主动解除,这个在应用中需要特别注意是否符合实际应用。
IceStorm被实现为IceBox服务,所以在部署IceStorm应用时需要启动IceBox服务。
2.5 动态服务管理(IceBox)
IceBox 用于动态加载用户服务并对他们进行集中管理,可以通过iceboxadmin管理工具对IceBox中的服务进行远程管理,通过IceBox用户服务可以被开发成可以动态加载的动态库组件.
使用IceBox的服务组件需要继承IceBox::Service类,并实现start()、stop()方法,并在实现类中提供服务进入点函数,一般为create()函数,在这函数中创建服务实现类的对象并返回。例如:
extern "C"
{
ICE_DECLSPEC_EXPORT IceBox::Service*
create(Ice::CommunicatorPtr communicator)
{
return new HelloServiceI;
}
}
对于Ice3.3.0以上版本,iceboxadmin提供了启动、停止服务及停止IceBox 服务器的命令管理工具和应用程序接口,管理工具命令如下:
iceboxadmin [options] [command...]
Commands:
start SERVICE Start a service.
stop SERVICE Stop a service.
shutdown Shutdown the server.
2.6 ICE网格计算
IceGrid用于支持分布式网络服务应用,一个IceGrid域由一个注册表(Registry)和任何数目的节点(Node)构成。注册表(Registry)和节点(Node)一起合作管理一些信息以及包含一些应用(Application)的服务进程。每项应用(Application)被指定在特定节点上的服务。这个注册表(Registry)持久记录了这些信息,而节点(Node)负责启动和监测其指定的服务器进程。对于一个典型的配置,一个节点(Node)运行在一台计算机(称之为Ice服务器主机)。注册表(Registry)并不消耗很多处理器时间,所以它常常是和一个节点(Node)运行在同一台计算机上的,注册表(Registry)还可以和一个节点(Node)可以运行在同一进程中.如果需要容错,注册表(Registry)还可以用主从式的设计支持复制(Replication)。
注册表(Registry)的主要责任,是解决作为Ice定位服务的间接代理问题,当客户端第一次尝试使用一种间接代理,客户端Ice run time首先连接注册表(registry),注册表将间接代理的符号信息转化为直接代理的endpoint,然后客户端和直接代理建立一个连接。通过适配器复制,同名适配器可以分布在多个节点上,间接代理可以映射到多个节点上的直接代理,在运行时由注册表服务根据负载均衡自动选择一个直接代理给客户端。
使用间接代理时,客户端可以用以下方式直接获取服务对象代理:
[email protected] // 对象@适配器
更简单一点的话可以用以下方式
MyProxy=theObject // 对象
2.6.1 分布式部署
在部署IceGrid分布式服务时,需要启动注册表服务(icegridregistry),并配置注册表服务地址端口、通信协议和注册信息保存的目录地址(ICE的注册信息保存为BerkeleyDB的数据库文件):
IceGrid.Registry.Client.Endpoints=tcp -p 4061
IceGrid.Registry.Data=/opt/ripper/registry
在服务器节点中和客户端都需要配置注册表服务的地址端口和通信协议:
Ice.Default.Locator=IceGrid/Locator:tcp -h 172.0.0.1 -p 4061
然后分别启动注册表服务(icegridregistry)和节点服务(icegridnode).
ICE提供了部署工具icegridadmin, 这个icegridadmin工具也需要定义Ice.Default.Locator属性.
接下需要编写应用部署文件,应用部署文件以XML方式保存。以下为支持适配器复制的应用配置文件,使用了服务模板:
<icegrid>
<application name="Ripper">
<replica-group id="EncoderAdapters"> //定义适配器复制组
<object identity="EncoderFactory" //identity将在客户端中使用。
type="::Ripper::MP3EncoderFactory"/>
</replica-group>
<server-template id="EncoderServerTemplate"> //定义服务器模板
<parameter name="index"/>
<parameter name="exepath"
default="/opt/ripper/bin/server"/>
<server id="EncoderServer${index}"
exe="${exepath}"
activation="on-demand">
<adapter name="EncoderAdapter"
replica-group="EncoderAdapters"
endpoints="tcp"/>
</server>
</server-template>
<node name="Node1">
<server-instance template="EncoderServerTemplate"
index="1"/>
</node>
<node name="Node2">
<server-instance template="EncoderServerTemplate"
index="2"/>
</node>
</application>
</icegrid>
然后在客户端可以用以下方式获取对象代理:
Ice::ObjectPrx obj = communicator->stringToProxy("EncoderFactory");
2.6.2 负载均衡
ICE平台内嵌负载均衡功能,对于分布大多个节点上的应用服务提供多种负载均衡方案,只需要通过XML配置文件即可完成负载均衡配置。配置项包括Type (负载均衡类型)、Sampling interval(负载信息收集间隙)、Number of replicas(返回给客户端的适配器个数)。
负载均衡类型有以下4种方式:
² Random (随机方式):注册中心随机选择一个适配器给客户端,不检查适配器的负载。
² Adaptive(适配方式):注册中心从所有适配器中选择一个负载最轻的适配器给客户端,Sampling interval参数只有在该类型的负载均衡中有效,这个参数指定节点定期向注册中心报告本地系统负载信息(system load information);
² Round Robin(最近最少使用):注册中心从对应的适配器组中选择一个最近最少使用的适配器给客户。
² Ordered(顺序方式):注册中心根据适配器的优先级,从高到低顺序选择一个适配器给客户端。
配置示例:
<replica-group id="EncoderAdapters">
<load-balancing type="adaptive"/> //配置为适配方式
<object identity="EncoderFactory"
type="::Ripper::MP3EncoderFactory"/>
</replica-group>
2.6.3 注册中心集群
前两节中描述的是属于用户应用的分布部署,分布式部署一个很重要的支撑是ICE的注册中心,所有客户端都向注册中心查询服务代理的真实端点,从而建立通信连接,在这里注册中心又成了一个单点服务,为了避免注册中心成为应用的瓶颈,提高系统的可靠性,ICE3.3.0以上版本提供了注册中心集群功能。
ICE注册中心集群通过主从式的注册中心复制来实现,一个集群中有一个主注册中心,若干个副注册中心,主从的区别通过IceGrid.Registry.ReplicaName属性配置来实现,主注册中心的名称为 Master,其他的名字可以任意取。启动时先启动主注册中心,再启动其他注册中心,通过主注册中心更新的信息都将同步给副注册中心,各副注册中心之间不通信。如果主注册中心失效,需要从其他副注册中心提拨一个成为主注册中心,但是从3.3版本的说明文档中来看,如果需要把某个副注册中心提拨成为主注册中心需要重新启动相应进程并修改IceGrid.Registry.ReplicaName 属性值为Master,或者删除该属性,默认情况下该属性值为Master.
使用集群方式时,在客户端配置时把所有的主从注册中心地址端口全部填到Ice.Default.Locator,例如:
Ice.Default.Locator=IceGrid/Locator:default -p 12000:default -p 12001
在应用节点也把所有的注册中心地址端口绑定,这样应用的更新会同时通知所有的注册中心。
2.7 IceSSL应用
ICE平台可以通过简单的配置来支持SSL应用,配置过程如下:
² 首先需要通过修改配置文件来启用SSL插件,C++服务端的配置方法为:Ice.Plugin.IceSSL=IceSSL:createIceSSL
只需要把IceSSL动态库放到LD_LIBRARY_PATH包含的路径下即可。
² 然后修改适配器的监听选项:
MyAdapter.Endpoints=tcp -p 8000:ssl -p 8001:udp -p 8000 //表示该适配器在三种协议端口上同时监听。
ICE还提供了多种配置属性来满足实际应用,例如下例所示:
Ice.Plugin.IceSSL=IceSSL:createIceSSL
IceSSL.DefaultDir=/opt/certs //默认证书目录
IceSSL.CertFile=pubkey.pem //证书文件
IceSSL.KeyFile=privkey.pem //私钥文件
IceSSL.CertAuthFile=ca.pem //信任的根证书文件
IceSSL.Password=password //私钥文件查看密码
2.8 持久化存储(IceFreeze)
ICE提供的持久化方案可以支持普通用户数据(键/值对)的持久化存储和服务对象实例的持久化管理,普通用户数据的持久化存储使用比较简单,服务对象实例的管理相对复杂一点,暂时不关注。
ICE的持久存储介质为BerkeleyDB,对普通数据的持久化在C++实现中采用Map的方式进行操作,用户需要用Slice定义需要存储的数据,并用slice2freeze 生成相应的Map操作类,然后对数据的操作就可以使用Map容器函数来进行。示例如下:
首先生成需要存储的数据类型:
slice2freeze --dict StringIntMap,string,int StringIntMap
代码使用:
Ice::CommunicatorPtr communicator =
Ice::initialize(argc, argv);
// Create a Freeze database connection.
Freeze::ConnectionPtr connection = Freeze::createConnection(communicator, "db"); //连接到数据库文件。
// Instantiate the map.
StringIntMap map(connection, "simple");//创建表。
// Clear the map.
map.clear();
Ice::Int i;
StringIntMap::iterator p;
// Populate the map.
for (i = 0; i < 26; i++)
{
std::string key(1, ‘a‘ + i);
map.insert(make_pair(key, i));
}
// Iterate over the map and change the values.
for (p = map.begin(); p != map.end(); ++p)
p.set(p->second + 1);
// Find and erase the last element.
p = map.find("z");
assert(p != map.end());
map.erase(p);
// Clean up.
connection->close();
communicator->destroy();
IceFreeze还允许使用结构体和类对象作为值进行存储,但是只有public的成员变量会被存储,其他成员变量不会被存储。
对于较高版本的ICE,还允许对值建立索引,如果值为结构体或者类对象,那么还允许以结构体/对象变量作为索引,通过slice2freeze编译后会生成对应的索引查询函数。例如定义了如下需要存储的数据结构:
module Demo
{
struct Struct1
{
long l;
};
class Class1
{
string s;
};
};
然后执行以下命令生成映射表,同时生成索引,以class1的成员变量s为索引。
Slice2freeze
--dict Demo::IndexedStruct1Class1Map,Demo::Struct1,Demo::Class1 --dict-index Demo::IndexedStruct1Class1Map,s,case-sensitive BenchTypes Test.ice
编译后代码中会自动生成findByS(string &),在程序中可以按以下方式直接调用:
IndexedStruct1Struct2Map& m=...;
IndexedStruct1Struct2Map::iterator p = m.findByS(os.str());
3 ICE平台功能研究小结
ICE平台提供的功能比较多,除了文档中罗列的部分,还支持程序包分发(IcePath2)、防火墙穿透(Glacier),鉴于目前的项目应用暂时不对这两部分作介绍。
从ICE官方提供的Demo和自己编写的测试程序在Iinux(opensuse)运行良好,对于适用于AIX的3.1.1版本在AIX上运行异步编程的测试也很顺利,但是目前还未对ICE平台的应用做性能测试。
从文档的介绍,ICE平台支持同步/异步、订阅/发布、分布式部署、内部持久化存储,支持接口描述语言到各种面向对象开发语言的映射,可以满足ESB系统开发的技术需求,但是也有一定的风险,ICE3.1以后的版本对AIX、HP-UX操作系统不保证完全支持,对3.1以后各个升级版本需要进行编译并运行测试。
以上是关于ICE中间件说明文档的主要内容,如果未能解决你的问题,请参考以下文章