Web 服务可靠消息传输简介

阅读数:1102 2007 年 9 月 20 日

话题:SOA架构

介绍

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

参考资料

关于作者

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

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