OASIS WS-RX技术委员会近期发布了Web服务可靠消息传输(WSRM,Web Services Reliable Messaging)1.1规范的公众预览版。作为该委员会的联值主席,我感觉有必要对WSRM及其规范做一个介绍。本文以WSRM 1.1草案四为准。
WSRM规范允许两个系统实现可靠传输消息。其目标是保证消息从发送者正确传输到接收者。可靠消息传输的定义是复杂的,但不妨用Java中的JMS系统实现XML消息可靠传输类比。二者的关键区别在于,JMS是一个标准API和编程模型,它具有不同的实现和连线协议(wire-protocol)。而WSRM恰恰相反,它是一个没有规定实现API和编程模型的连线协议,是由基于SOAP的系统构成的。
在解释连线协议之前,我想先说说WSRM与SOAP交互的适配方式。和基于队列的系统不同,WSRM对于现有应用来说是透明的。在队列系统中,总会存在一个显式的第三方队列系统,发送者放入消息,接收者再从中取出。而在RM里,客户端和服务端的SOAP处理引擎就内置处理器/代理(Handler/Agent),它们负责消息的传输。在应用层级,这些代理是不可见的,它们保证在消息丢失或未到达时重新传输消息。比如你搭建一个SOAP/JMS系统用于实现可靠的SOAP消息传输时,必须定义队列并修改Web服务中的URL。而在WSRM中就不需要这样做,因为它能自适应现有的HTTP(或其他)命名规范和URL。
WSRM中逻辑上有两类代理:RMS(RM Source)和RMD(RM Destination)。在一个给定的SOAP协议栈中,二者都可以有一个或多个处理器。
RMS:
RMD:
需要强调的是,不应将RMS、RMD和客户端/请求者、服务端/响应者混为一谈。在两端可靠(请求和响应消息都能可靠传输)的环境中,无论是在客户端还是服务端,都同时存在RMS和RMD。
WSRM中最重要的概念是序列(Sequence)。我们可将序列看作一个契约,在此基础上,RMS和RMD才能在发送者和接收者间实现可靠的消息传输。每个序列都有生命周期,变化范围很大。在序列中,缺省的最大消息数是2^63,相当于2.92亿年中每秒发送1000个消息!
序列可通过CreateSequence创建,并用TerminateSequence终结。例如:
<soap:body>
<wsrm:createsequence>
<wsrm:acksto>
<wsa:address>http://Business456.com/serviceA/789</wsa:address>
</wsrm:acksto>
</wsrm:createsequence>
</soap:body>
在序列中,每个消息都有一个在初始值上依次递增的消息编号(message number)。以下是一个序列头和消息编号的例子:
<soap:header>
<wsrm:sequence>
<wsrm:identifier>http://Business456.com/RM/ABC</wsrm:identifier>
<wsrm:messagenumber>1</wsrm:messagenumber>
</wsrm:sequence>
</soap:header>
消息编号,用于在SequenceAcknowledgement头中识别消息。以下是SequenceAcknowledgement头信息例子:
<soap:header>
<wsrm:sequenceacknowledgement>
<wsrm:identifier>http://Business456.com/RM/ABC</wsrm:identifier>
<wsrm:acknowledgementrange lower="1" upper="1" />
<wsrm:acknowledgementrange lower="3" upper="3" />
</wsrm:sequenceacknowledgement>
</soap:header>
我们先看个例子,为求简洁仅要求单向可靠,也就是说在这个例子里,客户端仅有一个RMS,服务端仅有一个RMD。
注意,这种确认并不是与消息一一对应的,而是代表对所有由RMD成功接收的消息的确认。
就细节而言,要全部说清楚是颇费口舌的,但其中最关键的是多出了两个服务调用(Create和Terminate),此外就是一些信息头。这种设计并非多此一举。在以前的规范草案里,CreateSequence被设计为隐式调用,从而造成第一个消息的迷乱。而按照目前的设计,你一旦成功创建一个序列,就等于在消息传输两端签订了一个契约。在绝大多数实现中,即使没有显式发送TerminateSequence消息,序列也将会自动超时。当然,如果消息丢失,也会有对应流程予以处理,比如在本例中是重发。
那么,确认消息和普通消息到底有何不同?换句话说,用户可有哪些灵活选择?
首先,确认消息不必和普通消息使用同样的传输通道。RMS可以另行开启HTTP端口(当然也可以是别的端口)用于接收确认信息。这个接收端口的信息由AcksTo地址标明。若AcksTo地址和WS-A的ReplyTo地址相同,则RMD可以用与获得普通消息同样的方法,得到确认消息。
其次,RMD并非必须对它已接收到的全部消息作出确认。其实,如果在一百万消息中有一条被丢失,那么RMD可以仅对丢失的消息要求Nack。其意思相当于对RMS说,我只对这条丢失的消息感兴趣。第三,RMS可以主动要求确认。假如RMD配置为尽可能少确认(为减少网络流量),那么当RMS希望清理自己备份的已发出消息时,就可以通过增加AckRequested头,主动要求确认。随后,RMD将立即通过SequenceAcknowledgement响应RMS。
另外,我们增加了对序列的关闭操作。这样可以避免消息未被全部发送并由RMD成功接收前,RMS关闭序列。现在,RMS通过关闭操作可以告知RMD,“我不会再发送任何消息”。接着,RMD做最终确认。最后就可以关闭序列了。
至于对消息请求的响应,除了两个方向均有一个序列外,基本没有区别。这两个序列是彼此独立的,因此在其上传输的消息彼此没有联系。如果一定要说有的话,那就是你可以使用如下方法优化这两个序列的创建:在CreateSequence中请求返回序列(使用Offer)。
想象你是一个客户端,很显然,需要双向可靠连接。在这种情况下,客户端创建一个序列后,可提交给服务器并等待响应。这样一来,实际上是在一次消息交换中创建了两个序列。不过,这两个序列没有依赖关系,可以中止其中一个,再使用另一个。
绝大多数互联网用户的机子上不光有HTTP服务器。问题的关键不是如何架设HTTP服务器,而是我如何将消息发到你的机子。比如,很多家庭用户都是通过带NAT(Network Address Translation)功能的路由器/防火墙上网。如果不作一些复杂配置,NAT将拒绝所有主动进入的数据包。同样,我在咖啡馆通过无线局域网上网,也有问题,因为我的自己没有公网IP地址。当然,如果我只需要单向可靠传输,还不是问题,直接回复HTTP请求即可。但如果要双向可靠,问题就来了。
假如某回应消息丢失,服务端就需要向客户端重发消息。但客户端地址是未知的,这时又没有已经建立的连接可用,怎么办呢?
MakeConnection的基本原理,是客户端定时主动从服务端“拉”(poll)消息。换句话说,消息流向是从RMD到RMS。客户端的RMD主动向服务端的RMS询问是否有消息需要发送。整个流程可以简单概括如下:
RM可有多种方法、以插件方式在其他安全模型中工作。不过,安全问题,我们还得谨慎考虑。比如所谓“序列攻击”——两个合法的客户端,各有一个序列,都得到了服务级授权,但其中一个是攻击者。攻击者如果能猜测(或嗅探)到另一个序列标识,那么它就可以展开拒绝服务攻击(如请求结束这个序列)。因此,RM规范必须研究如何将序列与特定环境中的信用证书或Session相结合。也就是说,RM代理有应对这类攻击的能力。在MakeConnection中,具备这种能力尤其重要,否则,未授权用户可以轻松截获发往其他系统的消息。
在发布核心规范的同时,技术委员会还发布了WSRM中用于的WS-Policy Framework模型的PAL(Policy Assertion Language)。在原来的1.0规范中,策略模型非常复杂,在WSRMP中有很多定时参数。对此,第一步,技术委员会删除了大量无用的、让用户无法动态调整的参赛;第二步,部分参数转移到CreateSequence。这就意味着,在没有WS-Policy的前提下,用户也能成功使用WSRM。那么我们不得不问:WSRM的可靠性到底是指的什么?
“我能从WSRM得到什么层级的可靠性呢?”回答这个问题并不容易。WSRM其实是一个连线协议,而非应用级端对端协议。之所以这样设计,主要出于两个考虑。第一,Web服务标准(WS-*)一般都不针对实现层设计,这是为了提升其松散耦合能力。第二,要提供端对端的可靠性,就必须有某种与应用相关联的事务管理器。而事务管理功能,是由别的WS-*规范支持的,实现方法也有多种,因此对于WSRM来说,如此做意义就不大了。
WSRM自身提供的可靠性保证,其实就是要求消息能从RMS到RMD成功传输,且RMD予以确认。就“确认”而言,不同的实现可能又有不同的含义。比如Apache Sandesha2,这是一个开源的WSRM实现,它有一个插件式的存储管理器。在这个实现中,只有消息被持久化到磁盘后才会发出确认消息。也就是说,Sandesha 允许服务器宕机与重启。WSO2 Tungsten服务器支持这种操作模型。
WSRM 1.0规范对传输过程做了很多加固工作,如AtLeastOnce、AtMostOnce、ExactlyOnce和InOrder。不过,这些保障措施都是用于RMD与应用之间,而非连线上的。因此在1.1规范中,我们删除了这些部分。这些保障当然仍要提供,但应该是在实现层而非连线协议的责任。
如果你从事消息处理工作,那么通常会学习一些为实现可靠传输的编程模型(PM),如JMS。WSRM或许会让你大吃一惊——它不要求任何新的PM。当然各厂商的具体实现可能有所不同,但WSRM核心规范,是不依赖于任何特定PM的。比如Sandesha允许用户自定义RM。如果没有序列,它会自动创建;不再需要发送消息时,它会自动超时并结束序列。换句话说,RMS和RMD不过是整个消息处理流程中的处理器(Handler),没有队列等等需要用户硬编码配置的可见实体,RM可以和现有Web服务共享同样的URI。因此,WSRM可以在不写任何代码的前提下与已有的Web服务集成。
当然,我们也可以从编码角度做一些有意义的思考。现在不少Web服务协议栈和API,如Microsoft WCF (Indigo)、JAX-WS和Apache Axis2都支持非阻塞异步调用Web服务。在这种模型里,客户端通过回调实现对消息的处理。
非阻塞异步模型对于WSRM非常重要。试想,如果Web应用阻塞调用其他Web服务,当请求并发数趋高时,应用的线程池就可能耗尽,无法再处理其他请求。
WSRM的首次发布时间是2003年3月。2005年6月,1.0规范提交到OASIS。此次草案对1.0规范做了很多修改,主要包括以下各项:
WSRM 1.0规范的实现很多,如Microsoft WCF(以前叫作Indigo)、Apache Sandesha2等等。OASIS WSRX技术委员会以2006年初的最后版草案为基础,展开了各个实现版本的兼容工作,随后有五家公司参与。尽管结果并不理想,只有三家公司实现了兼容。不过委员会还将在此次公开预览版基础上,再次推动此项工作,希望能有更多公司参与进来。
在本文中,还有很多复杂应用没有讲到,有兴趣的朋友,请直接阅读规范。文章的最后,我想结合我自己的经验和一些用户的意见,对WSRM的应用做一个总结:
总之,我相信WSRM前景广阔。尽管还需要一些时间,才能让所有公司、技术牵起手来同心协力,但我们正在为这个目标努力奋斗,此次的公众预览版,无疑就是一个重要里程碑。
Paul Fremantle 是开源Web服务公司 WSO2的联合创始人和技术副总裁。他还是 负责标准化Web服务可靠消息OASIS技术委员会的联值主席,Apache Synapse 项目的代码贡献者和发布经理。在联合创立WSO2之前,Paul是IBM WebSphere部门的一个SOA和Web服务的领导者。Paul还是Apache软件基金会的成员。
查看英文原文: An Introduction to Web Services Reliable Messaging