Web服务可靠消息传输简介

介绍

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。

  • 客户端需发送一个应用级消息时,RMS首先向指定URL发送CreateSequence消息。
  • RMD截获消息,并通过CreateSequenceResponse予以响应。其中包括序列的识别符SequenceID
  • 现在,RMS将在原始消息增加Sequence信息头,其中包括SequenceID和消息编号(在本例中是1)。
  • RMS继续为其他消息增加递增后的序列头信息。
  • RMD将这些消息转送给服务端应用,并提供唯一性、有序性等基本保障。
  • 根据预定的定时策略,RMD会在某个时候向RMS反馈SequenceAcknowledgement消息。RMS创建序列时,会向RMD传送一个确认地址(即AcksTo地址)。在本例中,我们假设AcksTo地址是一个WS-A匿名URI——也就是说你使用的是一个透明的通道。RMD将以HTTP方式响应确认信息。因为本例模拟的是单向可靠传输环境,因此在这之后就不会有完整的SOAP封包回馈给客户端,所以RMD会产生一个空SOAP封包,写入头信息后,通过HTTP返回。回馈到达客户端应用后,将由RMS接收。

注意,这种确认并不是与消息一一对应的,而是代表对所有由RMD成功接收的消息的确认。

  • 假如有任何消息丢失,RMS将重发。
  • 一旦RMS成功发送完所有消息,它就可以终结序列了,为此只需向RMD发送一个TerminateSequence消息。
  • RMD向RMS反馈TerminateSequenceResponse。
  • 大功告成!

就细节而言,要全部说清楚是颇费口舌的,但其中最关键的是多出了两个服务调用(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

MakeConnection的基本原理,是客户端定时主动从服务端“拉”(poll)消息。换句话说,消息流向是从RMD到RMS。客户端的RMD主动向服务端的RMS询问是否有消息需要发送。整个流程可以简单概括如下:

  • 客户端同时创建两个序列,第二个备用。
  • 客户端发送请求,理想状态下可能直接收到回应。
  • 因为各种原因,接收某些返回时超时,或连接丢失。
  • 客户端初始化MakeConnection,并传送备用序列的序列标识。
  • 服务端返回丢失的消息和是否还有剩余消息需发送的标识。
  • 没有更多消息需发送时,客户端终止序列。

安全

RM可有多种方法、以插件方式在其他安全模型中工作。不过,安全问题,我们还得谨慎考虑。比如所谓“序列攻击”——两个合法的客户端,各有一个序列,都得到了服务级授权,但其中一个是攻击者。攻击者如果能猜测(或嗅探)到另一个序列标识,那么它就可以展开拒绝服务攻击(如请求结束这个序列)。因此,RM规范必须研究如何将序列与特定环境中的信用证书或Session相结合。也就是说,RM代理有应对这类攻击的能力。在MakeConnection中,具备这种能力尤其重要,否则,未授权用户可以轻松截获发往其他系统的消息。

WSRM-Policy

在发布核心规范的同时,技术委员会还发布了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服务,当请求并发数趋高时,应用的线程池就可能耗尽,无法再处理其他请求。

1.0规范以来的修订历史

WSRM的首次发布时间是2003年3月。2005年6月,1.0规范提交到OASIS。此次草案对1.0规范做了很多修改,主要包括以下各项:

  • 名字空间变化。因为修改较大,1.1规范与1.0不直接兼容,为突出OASIS的所有权,不少名字空间做了调整。
  • 规范清理。技术委员会对规范做了细致梳理,发现了很多小问题,并做了相应清理工作。
  • 增加了CloseSequence。上面已经讨论过,某些情况下,必须对序列做显式关闭。
  • 删除了LastMessage。1.0规范要求对最后一条消息作出标识,其实是多余的。
  • 安全功能得到加强。1.0规范与WS-Security/WS-SecureConversation紧密耦合,1.1规范在这方面增加了灵活性,还支持基于SSL/TLS的安全机制。
  • 采用W3C推荐的WS-Addressing。
  • 简化了WSRM-Policy。老版规范中包含大量定时参数,无法动态调整,新版规范予以删除,或移入CreateSequence。
  • 支持双向可靠传输条件下的防火墙穿越,即引入了MakeConnection。

规范的实现

WSRM 1.0规范的实现很多,如Microsoft WCF(以前叫作Indigo)、Apache Sandesha2等等。OASIS WSRX技术委员会以2006年初的最后版草案为基础,展开了各个实现版本的兼容工作,随后有五家公司参与。尽管结果并不理想,只有三家公司实现了兼容。不过委员会还将在此次公开预览版基础上,再次推动此项工作,希望能有更多公司参与进来。

总结

在本文中,还有很多复杂应用没有讲到,有兴趣的朋友,请直接阅读规范。文章的最后,我想结合我自己的经验和一些用户的意见,对WSRM的应用做一个总结:

  • B2B可靠消息传输。很多人都看到了WSRM在B2B市场的潜力。不少企业都在寻找低成本的、安全的与合作伙伴传递采购、票务信息的技术。WSRM是一个理想的选择。
  • 部门与部门间,或服务器与服务器间的通讯。WSRM在企业内部信息传递上也大有可为。越来越多的公司开发并使用基于Web服务和XML的通讯工具,而WSRM恰是在其中保证可靠性的关键技术。
  • 替代JMS。Windows的下一个版本Vista将内置WSRM,这为那些希望用WSRM替换JMS的企业提供了契机。
  • JMS桥接器。你还可以将WSRM作为独立协议,用于实现对多个不同JMS实现的桥接。Apache Synapse开源项目就是这样一个产品。
  • 基于浏览器的消息传输。AJAX的应用日益广泛,在浏览器间直接实现消息传输的想法是很吸引人的。到目前为止,已经有人在为Firefox中实现基于SOAP的AJAX模型而努力。RM恰恰能在其中贡献力量,因为AJAX的非阻塞异步传输模式与WSRM非常般配,更何况WSRM的MakeConnection还可以帮助穿越防火墙。再如实现浏览器中信息订阅(浏览器发出一个订阅请求,利用MakeConnection就可以收到多个回应),也很有前途。

总之,我相信WSRM前景广阔。尽管还需要一些时间,才能让所有公司、技术牵起手来同心协力,但我们正在为这个目标努力奋斗,此次的公众预览版,无疑就是一个重要里程碑。

参考资料

  • OASIS的WSRM 1.1公开预览版规范: http://docs.oasis-open.org/ws-rx/wsrm/200608/wsrm-1.1-spec-cd-04.pdf
  • WSRM Policy 1.1公开预览版规范: http://docs.oasis-open.org/ws-rx/wsrmp/200608/wsrmp-1.1-spec-cd-04.pdf
  • OASIS WSRX技术委员会主页: http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=ws-rx
  • "提交的" WSRM 1.0规范(ZIP): http://lists.oasis-open.org/archives/ws-rx/200506/zip00000.zip
  • WS-RX和可靠消息的概要介绍: http://xml.coverpages.org/reliableMessaging.html#ws-rx
  • Sandesha2 WSRM实现 (支持1.0和1.1): ws.apache.org/sandesha/sandesha2/
  • 微软WCF(当前支持WSRM 1.0): http://msdn.microsoft.com/winfx/technologies/communication/default.aspx

关于作者

Paul Fremantle 是开源Web服务公司 WSO2的联合创始人和技术副总裁。他还是 负责标准化Web服务可靠消息OASIS技术委员会的联值主席,Apache Synapse 项目的代码贡献者和发布经理。在联合创立WSO2之前,Paul是IBM WebSphere部门的一个SOA和Web服务的领导者。Paul还是Apache软件基金会的成员。

查看英文原文: An Introduction to Web Services Reliable Messaging

你可能感兴趣的:(Web服务可靠消息传输简介)