ICE官方文档中2.2【The Ice Architecture】章节翻译之一 收藏
每次去参加各类大会或者培训之后,就下定决心说要好好学习英语,不过这个目标还是一次次的没有实现;当然工作忙、时间紧、没语境……都成了有力的接口;但是这次偶想到一个退而求其次的方法,虽然没有练习听力的语境,但是我们可以先把英语翻译能力练习起来,平时看英文doc的时间也不少,自认为看大多数文档都不存在啥障碍,但此次的要求是将他们翻译出来,呵呵,别小看哦,能看懂和能翻译出来可完全是两个level!不信你也可以尝试一把,呵呵;此次,正好遇到自己在看关于ICE相关的一些文档,网上search了也没有比较详细点的中文资料,于是决定将其官方文档中的架构部分都翻译出来,欢迎大家抛砖!
2.2.1 Introduction
ICE是一个面向对象的中间件平台。从根本上讲,这就意味着ICE对于构建面向对象的CS结构应用程序提供Tool、Api、以及各类lib支持。ICE应用程序非常适合于在各式各样的环境下使用:客户端和服务端可以分别用不同的编程语言来实现,而且可以运行在不同的操作系统和服务器架构中,相互间的通信也可以使用各种网络技术。
2.2.2 Terminology
每一种技术在其不断演变、成熟过程中都会创造出一些特有的词汇,当然ICE也不例外,但是,被ICE使用的特有词汇数量却是非常少的。和发明新的术语相比,我们会更倾向于使用已经存在的术语;如果你在过去曾经使用过其他的中间件技术,比如CORBA,那么你将会对接下来的内容非常熟悉;
Clients and Servers
对于一个应用程序,client、server分别对应于哪个部分并没有一致的规定;一般来说,他们是指在一次请求中扮演不同角色的特定应用部分:
Client可以理解为主动实体,他们负责向Server发起某个请求服务;
Server可以理解为被动实体,他们负责对Client请求进行响应并提供服务;
通常,服务端并不是“纯粹的”服务端【从来不发起请求,只负责对请求进行响应】;相反,服务端经常扮演服务端和客户端的双重角色,而他们扮演客户端的角色是为了满足原客户端的请求。
类似的,客户端也不是“纯粹的”客户端【只负责发起请求,而不对请求进行响应】;相反,客户端也经常扮演双重角色;比如,一个客户端可能向服务端发起了一个耗时很长的请求,作为请求的一部分,该客户端对服务端提供了一个回调【callback】对象,让服务端在操作执行完毕之后能够通知到客户端;在这个实例中,客户端在请求发起时扮演了客户端的角色,而在服务端执行完毕之后通知客户端时就扮演了服务端的角色。
类似上面的角色互换在很多系统中是非常普遍的,一般来说,CS类型的应用系统更多的被准确描述为点对点【Peer-to-Peer】系统。
ICE Objects
一个ICE对象是一个概念性的、抽象的实体。一般来说一个ICE对象具有如下一些特征:
它是一个本地的、或者是能对客户端请求进行响应的远程地址空间的一个实体。
一个single ice对象能够在一台服务器上被实例化、或者在多台服务器上被冗余的实例化;虽然一个对象有多个同时存在的实例,但他仍然是一个single ice对象。
每个ice对象有一个或者多个接口【interface】;接口就是一组操作【operation】组成的集合;客户端通过调用接口来发起请求。
一个操作有零个或者多个参数,返回值也一样;参数和返回值都有一个特定的类型【type】;对于参数又分成两类:输入型参数【in-parameters】被客户端初始化并传给服务端;输出型参数【out-parameters】被服务端初始化并传给客户端;返回值只不过是一种特殊的输出型参数。
一个ice对象有一个能够区别于其他对象的主接口;除此之外,一个ice对象还提供零个或者多个可选接口,这些可选接口被称为facets;客户端能够选择一个对象的facets中的其中一个来一起工作。
每个ice对象都有一个唯一的对象标识【object identity】;一个对象的标识就是让他能够区别于其他对象的标识值;ice对象模型假设对象标识都是全球唯一的,也就是说,在一个ice通信域里面,没有两个对象拥有相同的对象标识。实际上,对象标识并不一定要求使用类似UUID一样的全球唯一标识,只需要在你自己的ice通信域里面保证任意的两个对象标识不冲突就可以了。
Proxies
客户端若要和一个ice对象进行通信,客户端必须持有一个ice对象的代理【Proxy】;代理对于客户端地址空间来说就像是本地对象一样,对于客户端而言,代理就代表着ice对象(可能是远程对象),对于一个ice对象来说,代理就是一个本地大使;ice中的代理和CORBA中的引用【Reference】是等价的,ice使用代理替换引用主要是为了避免混淆,因为引用已经在各种不同的编程语言中有了太多其他的意思。当客户端对代理触发一个操作时,ice的运行时会做如下的一些事情:
找到需要通信的ice对象;
若ice对象所在的服务器不在运行状态时,尝试激活他;
在服务器上激活ice对象;
传输输入型参数给ice对象;
等待服务端操作完成;
返回输出型参数、返回值给客户端;当遇到问题时会抛出一个异常;
为了完成如上所有步骤的操作,代理封装了所有必要的信息;特别地,一个代理包括的信息有:
地址信息:允许客户端运行时能够找到正确的服务器;
对象标识:能明确找到服务器上的具体某个特定对象;
可选的facet标识:确定代理需要关联的某个特定facet;
Stringified Proxies
代理内部包括的信息能够被表示成一个字符串;比如,字符串
SimplePrinter:default -p 10000
就是一个代理的最具可读性的表示;ice运行时对此提供了api调用的支持,他允许把一个代理转换成他的字符串化的形式,相反也是可以的。这是非常有用的,比如可以将代理保存在数据库或者是文本文件中。
假设客户端知道了一个ice对象的对象标识、以及他的地址信息,那他就能靠提供的这些信息生成一个代理,就像无中生有一样;换句话说,代理的内部没有任何不透明的信息;一个客户端需要知道的仅仅是一个对象标识、地址信息、以及对象的类型(当需要触发某个操作时)。
Direct Proxies
直接代理是代理的一种类型,他嵌入了对象标识、以及他的服务器所运行的地址信息;完整的地址信息包括两部分:
协议标识,比如TCP/IP、或者UDP;
协议指定的地址,比如主机名以及端口号;
为了和直接代理后面的对象进行通信,ice运行时使用代理里面的地址信息去联系服务器;然后客户端每次发起请求时都会将该对象的标识发送给服务端。
Indirect Proxies
间接代理有两种形式:他只提供对象标识、或者他只提供一个和对象标识一起的对象适配标识【object adapter identifier】。只通过他的对象标识就能被访问的对象成为知名对象【well-known object】;比如,字符串
SimplePrinter
就是一个被标识为SimplePrinter的知名对象的有效代理。
一个包括对象适配标识的间接代理有如下的字符串化的形式:
SimplePrinter@PrinterAdapter
该对象适配的所有对象均可以通过该代理被访问,而不管该对象是否是知名对象。
注意:间接代理里面是不包括地址信息的。为了确定正确的服务器,客户端ice运行时将代理信息传递给定位服务【location service】;依次地,定位服务根据对象标识或者对象适配标识作为关键字,到包括了服务器地址信息的列表中查询,然后返回正确的服务器地址信息给客户端。这样客户端运行时就知道怎样和服务端进行通信了。
以上的整个过程类似于DNS【domain name service】将域名转换为IP;当我们使用一个域名时,比如打开www.zeroc.com去请求一个web页面,主机名在后台首先被解析成IP地址,一旦获取到了正确的IP地址,就可以直接用来连接到正确的服务器上了。对ICE而言,除了映射是从一个对象标识或者对象适配标识找到对应的协议地址对,其他的都是非常类似的;客户端运行时通过配置信息知道如何联系到定位服务,就像web游览器通过配置知道使用哪个DNS一样。
Direct Versus Indirect Binding
从代理中相关信息解析成协议地址对的这个过程被称为绑定;可以想象到,直接绑定是针对于直接代理的,而间接绑定就是针对间接代理的。
间接绑定的主要优势是他允许我们在不变动客户端已经持有的代理的情况下对服务器进行变更(也即变更服务器地址);换句话说,直接代理可以避免定位到服务器的额外查询,但是当服务端变更到另外一台机器时,此时直接代理就失效了;而另一方面,间接代理是可以继续工作的,即使我们移动或者变更了服务器。
Fixed Proxies
固定代理也是代理的一种类型,他和一个特定的连接绑定;与包括地址信息或者适配名称不同,固定代理包含了一个连接的句柄【Handle】。连接句柄只有在连接打开时有效,一旦连接关闭,代理也就不再有效;固定代理不能够被【marshaled】,也就是说,他们不能被当作参数在操作调用时进行传递;固定代理经常被用于双向通信【bidirectional communication】,这样服务端能够在不重新打开一个新连接时,直接对客户端执行回调【callback】操作。
Routed Proxies
路由代理将所有的请求都指向一个特定的目标对象,而不是直接发送调用请求到一个实际对象;路由代理对于实现类似Glacier2服务时非常有用,因为他能让客户端和服务端在防火墙的保护下进行通信。
Replication
复制是让对象适配器在多个地址有效的必要条件;复制的目标是对相同服务端运行在多个不同服务器上提供冗余;即使服务器中的某一台出现了问题,在其他服务器上的服务端仍然有效。
特别的,复制的使用意味着:客户端能够通过一个地址访问一个对象,也可以通过其他的地址而返回相同的结果;这些对象要么是无状态的,要么他们的实现被设计成和数据库保持同步,因为各个对象的状态需要保持一致。
当一个代理对一个对象指定多个地址时,ice对此提供了有限的支持;在初始化连接时,ice运行时会随机选择地址空间中的其中一个,而当这个连接失效时,运行时则尝试用其他地址进行连接。比如,考虑如下这个代理:
SimplePrinter:tcp -h server1 -p 10001:tcp -h server2 -p 10002
该代理表明对象标识为SimplePrinter、使用TCP协议、并在两个地址server1和server2的对象是有效的;对于用户或者系统管理员来说,最重要的就是确保服务端真正的运行在不同端口号的服务器上。
Replica Groups
除了以上我们谈到的基于代理的复制之外,ice还提供了被称为复制集群【replica groups】的更有用的形式,他需要使用到定位服务。
一个复制集群有一个唯一的标识,且由任意数量的对象适配器组成;一个对象适配器只可能是最多一个复制集群的成员,这样的适配器被称为可复制对象适配器【replicated object adapter】。
一个复制集群被确定之后,他的标识就可以被用在间接代理中替代适配器标识;比如,一个标识为PrinterAdapters的复制集群可以被用在如下的代理:
SimplePrinter@PrinterAdapters
复制集群被定位服务视为一个“虚拟对象适配器”;当解析一个包含复制集群标识的间接代理时,定位服务的行为就是一个实现细节。比如,一个定位服务能够决定返回在集群内的所有对象适配器的地址信息,此时客户端ice运行时就会在前面讨论过的有限的复制方式中随机选择其中的某一个地址;另外一个可能性就是对于定位服务只返回唯一一个地址,这样的决定将是依赖于启发式的。
不管定位服务解析复制集群的方式如何,关键的收获还是间接性的:在绑定过程中定位服务作为中间人能够加入更多的智能化。
Servants
就像我们前面提到的,一个ice对象是一个概念性实体,他有类型、标识、以及地址信息;不管怎样,客户端请求最终必须以一个确切的服务端处理实体而结束,该处理实体能够提供对操作调用时的具体行为;说得更确切些【To put this differently】,一个客户端请求最终必须以执行服务端代码结束,那些代码用特定编程语言编写,并且通过特定的处理器执行。
伺服器就是在服务端能够对操作被调用时提供相应实际行为的artifact;一个伺服器对一个或者多个ice对象提供本体【substance】;在实际情况中,一个伺服器只不过是一个服务端开发人员编写的类实例,该实例在服务端运行时注册成为一个或多个ice对象的伺服器;类中的方法就相当于ice对象提供的接口中的操作,并对各操作提供具体的行为。
单个伺服器一次能依附于【incarnate】单个ice对象或者同时依附于多个ice对象;如果是前者,被伺服器依附的ice对象在伺服器中是明了的;如果是后者,伺服器随着每次请求而根据ice对象标识被提供,如此他就能决定在请求期间哪个对象会被依附。
与此相反的,单个ice对象可以拥有多个伺服器;比如,我们可能会为一个对象创造一个代理,该代理对不同的服务器有不同的地址;在这个情况中,我们有两台服务器,每台服务器中对相同的ice对象包含一个伺服器;当客户端对该ice对象请求一个操作时,客户端运行时会直接发送某个请求到确定的一个服务器;换句话说,对于单个ice对象的多个伺服器让我们可以构建具有冗余功能的系统:客户端运行时企图向某服务器发送一个请求时,若此次请求失败了,那么还可以将此次请求发送给另外一台服务器;当第二次尝试请求也失败时,客户端应用程序才会收到一个被服务端返回的错误信息。
At-Most-Once Semantics
ice支持至多一次【at-most-once】语义:ice运行时尽其所能去分发请求到正确的目的地,依赖于确切的环境他可能会对一次失败的请求进行重试;ice保证要么他对某个请求进行分发,要是他不能分发请求,此时他会明确告诉客户端一个适当的异常;对于一个请求绝不会【under no circumstances】被分发两次,也即,重试只有在明确知道上次请求失败的情况下才会发起(注意:通过UDP协议进行的自带寻址信息的触发会是个例外,那种情况下,重复的UDP包会导致违反至多一次语义)。
支持至多一次语义是非常重要的,因为他们能够保证不是冥等【idempotent】的操作可以被安全的使用;所谓冥等操作是指某操作即使被执行多次也返回相同的结果;比如,x = 1; 就是一个冥等操作;即使我们执行两次,返回的结果也都和前次的一样。另一方面,x++; 该操作就不是冥等的:如果我们执行该操作两次,那结果就和第一次返回的结果是不一样的。
如果不支持至多一次语义,我们可以针对网络失败构建出更加健壮的分布式系统;然而,现实系统需要支持非冥等操作,所以至多一次语义是必要的,即使他们因此而降低了系统的健壮性。ice允许将个别操作标识为冥等的,对此类操作,ice运行时使用相对于非冥等操作更具侵入性的错误恢复机制。
Synchronous Method Invocation
默认情况下,ice的请求分发模型是同步的远程过程调用【remote procedure call】:此时操作调用时的行为就像是本地过程调用一样,也即,客户端线程在操作调用期间被暂缓【suspend】直到调用完全结束。
Asynchronous Method Invocation
ice也知道异步方法触发【AMI】:客户端能够异步地调用某个操作,也即客户端照常使用一个代理去触发某个操作,但是除了传递普通参数之外,还需要传递回调对象,而且客户端触发是立刻返回的。一旦操作完成了,服务端运行时触发由客户端初始传入的回调对象中的方法,然后将操作结果传给回调对象(若处理失败,则传递异常信息给回调对象)。
服务端并不能辨别以同步方式或者其他方式请求的异步触发,服务端只是知道客户端对某个对象触发了一次操作而已。
Asynchronous Method Dispatch
AMD是服务端的AMI;对于默认的同步分发,服务端运行时直接定位到服务器内的应用程序代码对触发的操作进行响应;在操作执行期间,服务端的某个执行线程被完全占用,当操作完成时被释放。
对于异步分发【AMD】,当客户端的某个操作触发到达服务端时,服务端的应用程序代码被告知;但是与AMI中代码立刻去处理请求不同,服务端应用可以选择延迟对请求的处理,如此他就释放了针对请求的执行线程;此时服务端应用程序代码就可以去做任意的他想做的;最后,一旦操作的结果可用了,应用程序代码通过调用api通知服务端ice运行时前面被分发过来的饿请求已经处理完成了,然后ice运行时将操作的结果返回给客户端。
AMD是非常有用的,比如服务端提供了一个执行非常耗时的操作给客户端,或者服务端的某个操作是需要从其他额外的数据源中去异步的读取数据并将其返回给客户端;对于同步分发【SD】,每个客户端需要一直等待服务端执行线程返回的数据;显然,该方法对于有很多个客户端时是非常不具有可扩展性的;对于异步分发,成千上万的客户端可以对某个相同的操作进行请求触发,但却不会完全占用服务端的任何执行线程。
AMD的另外一个使用场景是:当完成某个操作并将操作结果返回给客户端时,此次却还需要继续保持服务端执行线程去做一些其他事情,比如执行cleanup或者是更新持久层存储等。
同步和异步分发对于客户端而言是完全透明的,客户端不可能告诉服务器是采用同步还是异步的方式去处理一个请求。
Oneway Method Invocation
客户端能够通过单向请求的方式来调用操作;单向触发【oneway invocation】支持“best effort”语义;对于单向触发,客户端运行时将请求传递给local transport,只要被local transport缓冲器记录了,那对于客户端的请求就完成了;而实际的触发请求随后会被操作系统异步的发送;对于单向触发,服务器并不进行响应,也即,流量只是从客户端向服务端的,而服务端到客户端却是没有的。
单向请求是不可靠的;比如,目标对象可能不存在,此时请求就被丢失掉了;类似的,该操作可能被分发给服务器上的某个伺服器,但是操作却可能会失败(比如参数非法);即使这样,客户端仍然无法接收到任何关于出错的通知信息。
单向请求只可能适用于没有返回者、没有输出型参数、不会抛出用户级异常这类型的操作。
对于服务端的应用程序代码而言,单向请求是透明的,也即,没有任何方法可以区分从一个单向请求发起的双向调用。
单向请求只对提供了基于流的传输协议(比如TCP/IP/SSL)的目标对象有效。
注意:即使单向请求通过基于流的传输协议进行发送,他们在服务端被处理时有可能是无序的;这是因为每次调用都是在他自己所在线程中被分发的;即使请求到达服务端时请求在被初始化时进行了排序,那也不意味着他们将会按照那个顺序被处理--线程调用的异常行为可能会导致某个单向请求比另外一个比他更早到达的单向请求早完成。
Batched Oneway Method Invocation
每个单向请求都发送一个独立的消息给服务器;对于一系列短消息而言,这样做的代价是相当高的:因为对于每条消息客户端和服务端运行时都必须在用户模式和内核模式之间进行切换;在网络层面,每条消息都会带来流控制和消息确认的代价。
批量单向请求【Batched oneway invocations】允许以单条信息的方式发送一系列的单向请求:每次触发一个批量单向操作时,请求都会在客户端运行时被缓存,一旦你将所有需要发送的单向请求都累计在一起了,你可以通过调用一个独立的api一次将所有的请求都发送完毕;随后客户端运行时在一条消息内将所有已经缓存起来的请求发送给服务器,服务器也以一条消息的形式接收到所有的触发请求;这就避免了客户端和服务端不断重复的切换内核,在网络传输上也更加容易,因为一条大的消息体被传输要比很多小信息体传输高效得多。
一个批量单向信息中的单个请求被一个单独线程按照他们加入该批次的顺序进行分发;这就保证了在一个批次的单向信息被服务器处理时是有序的。
批量单向请求对于消息服务特别有用,比如IceStorm。
Datagram Invocations
数据包请求与单向请求有着类似的“best effort”语义;但是,数据包请求需要对象使用UDP作为传输协议,而单向请求采用TCP/IP。
就像单向请求,数据包请求也需要操作没有返回值、输出型参数、用户级异常;数据包请求通过UDP协议去触发某个操作,只要本地UDP堆栈接收到消息操作就返回了;而实际的操作调用在后台被网络堆栈异步的发送。
数据包请求也是不可靠的,就像单向请求一样。
和单向请求不一样的是,数据包请求有很多额外的错误描述:
个别请求可能在网络层被丢失;这是因为UDP包投递的不可靠性所致;比如,若依次触发三个操作,中间的那个操作可能就会被丢失;类似的事情在单向请求中就不可能出现,因为他们是基于连接的传输协议进行分发的,个别请求不可能丢失。
个别请求可能无序到达;这也是因为UDP的特性所致;因为每个请求作为一个单独的数据包进行发送,数据包在网络上可能通过不同路径传输,这样请求到达时的顺序就可能和他们被发送时的顺序是不一样的。
数据包请求非常适合于在LANs中发送小消息,此时丢失率是很低的;他们也适合于低延迟比可靠性更重要的应用场景,比如针对快速、交互式的应用程序;最后,数据包请求能够被用于同时向多台服务器组播消息。
Batched Datagram Invocations
关于批量单向请求,批量数据包请求允许在缓冲器中收集很多的请求,然后通过调用api将整个缓冲器中数据作为一个数据包进行发送,之后清除缓冲器;批量数据包降低了重复系统的调用,还允许底层网络更加有效的运行;但是,批量数据包请求只对批量消息的容量未超过网络的协议数据单元【PDU】时有用;如果一个批量数据包的容量太大,将会被拆分成多个数据片段,这样一个或者多个数据片段可能会被丢失,从而导致整个批量消息的丢失;然而,有一点是可以保证的,即一个批量数据包中的所有请求要么被分发,要么都不被分发,一个批次中某个请求被丢失是不可能的。
在服务端批量数据包使用一个单独的线程来分发各批次中的各个请求;这就保证了在一个批次中各请求是被有序的执行,在服务端请求不可能出现重新排序的现象。
Run-Time Exceptions
任何的操作调用都可能引发运行时异常;运行时异常是被ice运行时预先定义好的、且覆盖了通用的错误情况,比如连接失败、连接超时、资源分配失败;对于应用程序而言,运行时异常就像本地异常一样,这样对于具有异常处理能力的编程语言具有的本地异常处理进行了优美的集成。
User Exceptions
用户级异常被使用来标示依赖于客户端的应用程序特定的错误;用户级异常能够携带任意的复杂数据,且能够被继承,这样就使得客户端能够通过捕获继承体系中的某个异常,从而很容易的处理错误类别。
Properties
ice运行时中的很多参数都可以通过properties文件来进行配置;Properties是一个键值对【name-value pair】,比如Ice.Default.Protocol = tcp; Properties一般被存储在文本文件中,然后被运行时解析,比如线程池大小,跟踪级别、以及其他各种配置参数。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sfdev/archive/2008/12/04/3446640.aspx