深蓝海域KMPRO

Web Service Case Study: 事务性Web服务

2002-08-26 09:34

Web Service Case Study: 事务性Web服务  


  
 柴晓路 (
fennivel@uddi-china.org)

Chief System Architect

2002年4月26日

本文是Web Service Case Study系列文章的第三篇。在这篇文章中,我将围绕一个事务性应用展开讨论,探讨在Web服务环境中实现原先在数据库层次或者对象层次中实现的事务特性。具体的,这里的应用实例是一个分布式的数据库同步的应用,我们需要解决的是在多个分布在Internet上的同构数据库完成增量式的基于事务的数据同步问题。

应用背景

以下描述中使用的地名、机构名和系统名称纯属虚构,但是是从具体应用中抽象出来的。

Briliiance City是一个医疗保健公众服务正处于建设中的城市。市政府的最终目标是将整个城市的医疗机构(包括综合性大型医院、专业医院、分区医院等)、健康保健机构(社区医院、社区医生)等等通过Internet联系在一起,从而实现将市民的医疗记录和健康保健记录统一管理。具体的来看,这个公众服务网络最终需要具备以下特性:

一致的逻辑数据:对于每个市民而言,他的医疗记录和保健记录在逻辑上由这个服务网络统一管理,从任意的服务结点(任何医疗机构或是健康保健机构)获得的个人数据都应该是一致的,也就是说,从逻辑上,整个服务网络只有一个市民个人医疗保健信息数据库,而所有的服务结点都是这个数据库的客户端应用。

优秀的网络环境的适应性:由于整个城市的基础设置,尤其是网络基础设施尚不是非常出色,虽然每个机构都能够连上Internet,但是除了一些比较大的机构,诸如综合性大型医院、专业医院、社区医院等能够使用宽带,并一直在线以外,相当多的机构还是在普遍使用拨号上网的方式。同时有些机构的上网方式是能够获得公网IP的,而有些则没有公网IP。从系统的实现角度来考虑,我们的服务网络需要满足这些复杂的网络环境。
机构系统的兼容性:由于服务结点的数量巨大,其使用的平台和语言各不相同,这个服务网络需要能够容纳所有类型的服务结点。

开放的界面和接口:由于这是一个公共服务,因此不仅个人用户能够方便地通过Web或者桌面应用查询个人的医疗保健信息,同时也需要提供非GUI的交互界面,以便使企业应用或者其他政府应用可以使用这个医疗保健网络服务所能提供的信息。

目前,在Brilliance City的东城区,大部分医疗保健机构都采用了一家称为Health Inside的软件公司的产品部署了各自的应用系统,包括医院信息系统、医疗保健系统等等。由于采用了同一家公司的产品,因此他们在底层数据层的设计是基本一致的,市政府打算以此为试点,如果应用情况良好再推广到全市。

解决方案

根据整个服务网络的应用背景,我们设计了这样一个解决方案:一个分布式的医疗保健的信息服务网络。具体描述如下:

数据逻辑集中,物理分布

在这个服务网络中,数据仍然被保存在各个服务结点,对于这样一个Internet环境下的应用而言,数据库集中是无法想象的,Internet不可能永远稳定工作,其次如果采取数据库集中,骨干网上的流量也是无法接收的。由于数据是物理分布的,而逻辑上只有一个合法版本的数据,因此我们需要有跨结点的分布式数据同步机制来保障数据的一致性。

数据同步的事务性

当一个服务结点执行了某个事务之后,为了完成数据同步,我们需要把这个事务在其他相关的服务结点上也同样加以运行,由于数据之间具有关联性,因此在其他结点上的运行必须同样保证事务性。

通过交换中心的消息队列传输

由于各个服务结点的联网状况比较复杂,很多结点都无法一直保持在线的状态,因此使用消息队列来传递事务将比较有效。我们设置了一个用于交换事务数据的交换中心,在交换中心上为每个服务结点提供了in/out消息队列。交换中心是部署在Internet主干上的,因此只要服务结点连上了Internet,就可以通过交换中心收发包含事务的消息。

从数据库模式到逻辑数据模式

整个系统的实施将分布走,首先先将服务网络覆盖到Health Inside的客户,此时由于所有服务结点都是同构系统,因此我们一开始事务是基于数据库模式的,也就是归根于数据库表记录的增删改等。随后将慢慢地延伸到其他公司的产品或解决方案,此时基于数据库模式的事务将不在有效,需要就市民的医疗保健记录及其操作制订基于XML的业务事务规范,所有的事务都将基于这样的格式进行传播,此时在各个服务结点就需要有一个从基于数据库模式的事务到基于XML数据模式的业务事务。为了在这个转换过程中减少代价,降低风险,我们在一开始的基于数据库模式的事务消息交换时,同样使用XML来描述事务,使用Web服务架构来构建交换中心。如此,待服务网络向其他类型的系统延伸的时候,对于服务网络而言,基本无需更新,工作量集中在每个服务结点的数据库模式向业务模式的转化,以及服务结点接入服务网络两大块。前者是面向业务的,工作量无法节约。而后者由于采用了Web服务架构,可以通过Web服务的规范协议方便地构筑系统交互。

基于数据中心的公共界面

在这个服务网络中,除交换中心外,我们还设置了一个数据中心,设置的目的是多方面的。第一个目的是为统一管理市民的基本信息,从这个意义上将,数据中心也是一种服务结点,在数据中心上对市民信息进行更新,同样需要通过数据同步和消息队列的机制将这一更新事务传递到其他相关的服务结点。第二个目的是为对外的面向市民、面向企业的公共服务提供数据,如此就无需在运行时刻频繁地对各个服务结点进行数据请求。同时,当某个服务结点崩溃,需要重建的时候,可以从数据中心下载所有其管理和使用的数据。

体系架构

Figure 1.   体系架构图示 


 

这个服务网络的基本体系架构可参见上图。其技术核心就是各个服务结点之间的数据同步事务的实现。而这一机制的实现,在服务网络的第一期目标下,基本可以归结为两点:

数据库事务的XML表示

在Web服务环境下事务性的保证

数据库事务的XML表示

由于在第一期工程中,所有的服务节点是同构系统,使用了相同的数据库结构,而且为了最大化地减小每个服务节点的实现代价,因此我们采用了完全基于数据库事务的数据同步方式。每条数据同步消息将包含一个事务(transaction)的描述,而一个事务可以包含多个操作(operation),每个操作要么是在某张表中更新(或添加)记录,或是在某张表中删除记录。

根据这样的设计思想,我们可以定义了transaction元素(包括其子元素)来表示一个事务消息,具体的Schema定义如下:

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema targetNamespace="urn:dealeasy:webservice:exchange:schema"
             xmlns="dealeasy:webservice:exchange:schema"
             xmlns:xs="
http://www.w3.org/2001/XMLSchema"
             elementFormDefault="qualified"
             attributeFormDefault="unqualified">

  <xs:complexType name="field">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="type" type="xs:string"/>
      <xs:element name="value" type="xs:base64Binary"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="primaryKey">
    <xs:sequence>
      <xs:element name="field" type="field" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="allField">
    <xs:sequence>
      <xs:element name="field" type="field" minOccurs="0"
                    maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="save_data">
    <xs:sequence>
      <xs:element name="tableName" type="xs:string"/>
      <xs:element name="primaryKey" type="primaryKey"/>
      <xs:element name="allField" type="allField" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="delete_data">
    <xs:sequence>
      <xs:element name="tableName" type="xs:string"/>
      <xs:element name="primaryKey" type="primaryKey"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="operations">
    <xs:sequence>
      <xs:element name="save_data" type="save_data" minOccurs="0"
                    maxOccurs="unbounded"/>
      <xs:element name="delete_data" type="delete_data" minOccurs="0"
                    maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="transaction">
    <xs:sequence>
      <xs:element name="operations" type="operations"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

 在这个模式定义中,核心是对数据的操作,这里为简便性起见,仅仅定义了两个操作save_data和delete_data,这也是符合国际惯例的定义。理由是,新增数据(记录级)和更新数据(记录级)可以使用相同的调用。相似的模式定义我在本系列的第一篇文章中已经提及。

这里采用的描述方法是针对通用的数据库操作的,也就是说无论具体数据库的结构如何,只要每个端系统都是相同的,那么这个消息模式就可以应用。在save_data元素下,包含了三个元素:

tableName,这个元素指明了需要操作的记录所在的数据库表(Table)的名字;
primaryKey,这个元素用于描述待操作的数据记录(record)的主键,系统通过这个主键可以准确定位到需要操作的记录(Record),如果没有找到由该主键标识的记录,那么对应的操作就是新增记录(insert record),如果找到了由该主键标识的记录,那么对应的操作就是更新记录(update record);
allField,这个元素用于描述待操作的数据记录中除主键字段外的所有字段,值得注意的是主键字段也可以是多个的。
而对于delete_data,其结构与save_data是类似的,唯一的差别就是没有allField元素,原因相信大家也很容易理解,在删除数据时,只要知道主键就足够了。

有了Schema的模式定义文档,我们就能够生成SOAP消息了,下面首先给出的是一个由单个save_data操作和单个delete_data操作组成的事务的例子。该事务从candidate_employee中删除一条记录,而在employee中添加了一条记录。

<env:Envelope
  xmlns:env="
http://www.w3.org/2001/06/soap-envelope"
  env:encodingStyle="
http://www.w3.org/2001/06/soap-encoding">
  <env:Body>
    <transaction:transaction xmlns:po="Some-URI">
      <operations>
        <delete_data>
          <tableName>candidate_employee</tableName>
          <primaryKey>
            <field>
              <name>id</name>
              <type>uuid</type>
              <value>crystal</value>
            </field>
          </primaryKey>
        </delete_data>
        <save_data>
          <tableName>employee</tableName>
          <primaryKey>
            <field>
              <name>id</name>
              <type>uuid</type>
              <value>crystal</value>
            </field>
          </primaryKey>
          <allField>
            <field>
              <name>name</name>
              <type>string</type>
              <value>Crystal Zhuang</value>
            </field>
            <field>
              <name>gender</name>
              <type>string</type>
              <value>female</value>
            </field>
            <field>
              <name>email</name>
              <type>string</type>
              <value>crystalzh@email.com</value>
            </field>
          </allField>
        </save_data>
      </operations>
    </transaction:transaction>
  </env:Body>
</env:Envelope>

 事务性的保证

由于我们使用Web服务架构来架构我们的交换中心。因此在我们定义好了事务消息的XML描述之后,就需要实施如何通过SOAP消息来传输所需要传播的事务消息了。对于这个服务网络而言,其核心问题就是要将服务节点的事务(目前是数据库事务,以后将延伸到业务事务)在各个相应的服务结点上同样以满足事务性的条件下执行。由于我们采用的是Web服务架构,Web服务架构的现有技术中尚未有正式的支持事务的规范或技术。因此在这部分,我们将首先围绕当前这个服务网络中的应用来讨论Web服务的事务性,当解决了当前这个应用实例中的问题之后,我们将从通用全面的角度继续考察更复杂情况下的Web服务事务性的保证。

最简单的情况

如果我们在传输事务消息的时候,总能保证单个事务消息使用单个SOAP消息来传输的话,那么这就是最简单的情况。此时,SOAP仅仅是传输事务数据的机制,而并不包含事务控制的功能。

具体的流程可以描述如下:

服务结点A完成了数据库事务T,事务处理模块使用我们先前制订的XML模式将事务T表示为XML格式T(XML)。

服务结点A的SOAP Service将T(XML)包装在SOAP Body里面,组成SOAP消息,向交换中心发送,此时消息在交换中心中位于服务结点A的"out"消息队列中。

交换中心获得服务结点A的"out"消息队列中的事务消息,并分析出与之相关的需要实施事务同步的服务结点集合(假设在这里我们仅找到一个相关的服务结点B)。

交换中心将这则事务消息T(XML)复制到服务结点B的"in"消息队列(当然也可以不复制,而采取指针的方式,不过采取这种方式的话,在维护方面的复杂度会有稍许的提高)。

服务结点B上线之后,检查交换中心的消息队列,获得事务消息,在SOAP Service中将T(XML)从SOAP消息中剥离并交给下层的事务处理模块。
服务结点B的事务处理模块将T(XML)通过XML解析,转换成内部的事务,并实施运行。
从单条消息到多条消息

然而,并不是在任何情况下,使用一条SOAP消息就能够传输一则事务的。由于SOAP消息最终是要和某一种网络协议进行绑定并在网络上进行传输的,大多数网络连接很难保证在一个连接内能稳定地传输大量的数据,比如HTTP,我们在下载/上传大文件的时候,经常会发生中断,此时我们就不得不重传(HTTP没有断点续传),重传需要耗费同样长的时间,同时传输时间越长发生错误的可能也越大,因此传输大文件如果不加以特别的措施的话,常常会变成一场噩梦。我们通常的方法,是将大文件分割成多个小块,分别传输,等全部传输完毕后在对等方组合。由于单个文件块比较小,传输稳定性得到了提升,同时即使传输失败,重传的话也比较快。

我们将这一用于文件传输的方法,延伸到事务消息的传输中,由于这是一个平台级的特性,因此我们按照SOAP规范的推荐,使用SOAP Header来控制事务消息的合并执行,同时为了提高响应率,我们的算法并不一定要等到关联单个事务的消息全部收到才会执行事务,我们采取了一定地提前执行的机制,具体细节,我将在下面详细阐述。

首先我们给出transactionControl这个SOAP Header Extension的模式定义。

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="
http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
  <xs:element name="transactionControl" type="transactionControl"/>
    <xs:complexType name="transactionControl">
    <xs:sequence>
      <xs:element name="simpleTransaction" type="simpleTransaction"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="simpleTransaction">
    <xs:sequence>
      <xs:element name="transactionID" type="xs:string"/>
      <xs:element name="begin" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="commit" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="rollback" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="part" type="xs:integer" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

在这个模式定义中,我们看到transactionControl这个SOAP Header Extension包含了一个子元素simpleTransaction,我们在下面就使用这个子元素来描述事务消息,为是么不直接使用transactionControl元素,而要在其下再定义一层子元素,是为了以后对事务控制特性进行进一步的扩展,我们会在这基础上实现两阶段提交和三阶段提交等。

simpleTransaction元素包含了五个子元素:transactionID、begin、commit、rollback和part。他们的含义分别如下:

transactionID:事务的标识ID,两个描述事务的SOAP消息当切仅当具备相同的transactionID时,我们才认为他们是一个事务的两个组成部分。

begin:表示事务可以开始执行。

commit:表示事务可以提交。

rollback:表示事务可以回滚。

part:这是一个整数类型的值,表示SOAP Body中的内容是当前事务的第几个部分,整个事务应当按照part值所描述的顺序依次执行。

由于这五个子元素可能同时出现(rollback和commit不会同时出现),当服务结点接受到事务消息时,上述的五个子元素被解释执行的先后次序为:transactionID、begin、part(触发解析SOAP Body)、commit & rollback。他们的工作原理可描述如下:

当服务结点受到一个事务SOAP消息之后,首先解析出transactionID,如果是新的transactionID,那么在服务器端的事务池中,新建一个事务,并将transactionID赋予之。如果事务池中已经有了这个transactionID,那么就将这个事务消息与之关联。当完成transactionID的识别之后,如果消息中带有begin,那么就启动这个被关联的事务池中的事务。如果消息中带有part,假设part的值为n:

将当前消息的SOAP Body作为事务的第n部分存入事务的缓存控件;

检查当前事务的缓存空间,如果发现已经包含了1到m之间所有的事务部分,那么就依次执行从1到m之间所有未执行的事务部分。

如果消息中带有commit,那么在缓存事务部分的时候(这一定是最后一个事务部分),对其加上标志:"这是该事务的最后一个部分"。当该部分被执行时,整个事务将被提交。

如果消息中带有rollback,那么对应的事务被立即回滚,然后该事务失效,如果之后仍然收到该事务空间中的消息的话,服务结点将丢弃这些消息。

例如:服务结点可能以如下次序接受到关联同一事务的事务消息:

[part(2)], [part(4)], [begin,part(1)], [part(3)], [commit, part(6)], [part(5)]

那么服务结点的动作序列应当为:

接收到[part(2)],在事务池中创建一个事务空间,并缓存该事务部分,现在缓存为(2);

接收到[part(4)],缓存该事务部分,现在缓存为(2, 4);

接收到[begin,part(1)],启动事务,缓存该事务部分,现在缓存为(1, 2, 4),事务1,2两个部分被执行;

接收到[part(3)],缓存该事务部分,现在缓存为(…, 3, 4),事务3,4两个部分被执行;

接收到[commit, part(6)],缓存该事务部分,现在缓存为(…, 6),同时第6部分被置commit标志;

接收到[part(5)],缓存该事务部分,现在缓存为(…, 5, 6),事务5,6两个部分被执行,然后事务被提交。

在这一节中介绍的方法解决了在分布式的环境中,在单点数据库中完成了事务的执行。这个机制有效的解决了我们这个服务网络应用实例的当前需求。

然而,我们考虑到,在将来整个服务网络将向公众开放,包括个人用户和企业用户在内的各种用户都可以访问和使用服务网络。虽然一开始只有一些数据查询的事务开放给他们使用,不过随着整个服务网络的成熟,我们将会在数据中心层次上对外提供业务处理服务,而这些服务被使用时,势必发生一些业务事务,根据整个服务网络的设计原则,这些业务事务需要在整个服务网络上被执行,在这种情况下,与前面我们介绍的单个事务的重复广播有所不同,这里发生的是一个涉及到多个服务结点的分布式事务。例如:这个事务可能同时涉及某个市民的医疗记录和保健记录,而这两者分别是由两个服务结点来管理的。前面的机制已经无法解决这一问题,下面我们将两阶段提交协议(2PC)引入到我们的Web服务环境中来,以尝试解决这一分布式事务的问题。

两阶段提交(Two-Phase Commit)

两阶段提交。分布式系统中处理用户提交的事务时,事务管理器通常使用两阶段提交协议保证所有操作所涉及到的分布式环境中的各个数据库中的相应数据被锁定从而最后被正确地更新。如果因为某种原因操作不能完成,则要求统一执行"回滚"操作,回到事务开始前的状态。如果事务被成功执行,那么所有相关的数据库都被正确更新,此时这些更新应当被分布式环境中的所有数据库系统都了解。最后解除相关数据库重相关数据的锁定状态,以便进行下一个事务的执行。

以下我们描述一个扩展的两阶段提交的协议过程(这个扩展的2PC比标准的2PC多了预先准备的三个步骤,也被称为3PC):

事务管理器部署在中央服务器上,它处于中心控制结点的位置,以事务发起者的身份开始工作,它首先在事务管理器日志中写入一条"Prepare Transaction"的记录,然后将相应的事务请求发送到所有相关的服务结点(需要执行事务的部分)上。

当每个参与事务的服务结点接收到"Prepare Transaction",服务结点可以按照自己的负载状况选择是"Accept"还是"Reject",并将"Accept"或"Reject"消息发回事务管理器。

当事务管理器收到所有服务结点的响应后,如果全部都是"Accept"消息,那么继续下一步。如果至少有一个服务结点返回的是"Reject"消息,那么这个事务将不会真正开始,事务结束。当然,在处理"Reject"消息的时候,可以有一些优化的方法,我们可以在收到第一条"Reject"消息的时候,即时将事务结束(当然也可以选择缓存在队列里,等过一段时间后重试),并且丢弃以后收到的和这个事务相关的"Accept"或"Reject"消息。
当所有服务结点发回"Accept"消息后,事务管理器准备真正开始执行事务。它在事务管理器日志中写入一条"Begin Transaction"的记录,然后将相应的事务执行内容发送给所有相关的服务结点。

各个服务结点将此事务写入自己的日志,然后锁定资源以供该事务使用,当资源准备完毕后,服务结点向事务管理器所在的计算机发送"Ready"消息。

当事务管理器所在的中心控制结点接收到所有的"Ready"消息后(即参与事务的所有服务结点都必须发回"Ready"信息),它在日志上记录"Commit Transaction",要求该事务被提交,并通知所有相关的服务结点。如果在一定的时间内,至少有一个服务结点没有返回"Ready"信息,那么它视该事务执行失败。将会在日志上记录"Rollback Transaction",并发送给所有服务结点,要求回滚事务,并将该事务结束。
当这些相关的服务结点接收到"Commit Transaction"通知后,这些服务结点将自身承担的事务部分提交(Commit),并解除对资源的锁定,同时通知事务管理器执行完毕。

如果服务结点接收到"Rollback Transaction"通知后,这些服务结点将自身承担的事务部分回滚(Rollback),并解除对资源的锁定。
在这里,我们有两个假设:

事务管理器总是工作正常的,否则服务结点在无限期等待事务管理器的"Commit"或"Rollback"消息时,将处于无法工作状态,因为服务结点是不能自主地选择提交或是回滚的,否则就有可能与其他服务结点的数据不再同步。此时其他服务结点可能已经提交或者回滚了。

当服务结点锁定事务执行时需要的资源,并且发送了"Ready"消息之后,我们只能认为他一定能完成事务的执行,否则如果在提交阶段发生错误,很可能其他结点已经提交了,提交了的事务是无法回滚的。

在仔细地分析了两阶段提交之后,我们来扩展先前定义的transactionControl元素,为这个SOAP Header Extension增加新的功能。

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="
http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
  <xs:element name="transactionControl" type="transactionControl"/>
  <xs:complexType name="transactionControl">
    <xs:sequence>
      <xs:element name="transactionID" type="xs:string"/>
      <xs:element name="simpleTransaction" type="simpleTransaction" minOccurs="0"/>
      <xs:element name="twoPhaseCommitTransaction" type="twoPhaseCommitTransaction" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="simpleTransaction">
    <xs:sequence>
      <xs:element name="begin" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="commit" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="rollback" type="xs:boolean" nillable="true" minOccurs="0"/>
      <xs:element name="part" type="xs:integer" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="twoPhaseCommitTransaction">
    <xs:sequence>
      <xs:element name="serviceNode" type="xs:string"/>
      <xs:element name="prepare" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="request" type="xs:string" nillable="true" minOccurs="0"/>
            <xs:element name="response" type="xs:string" minOccurs="0"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="begin" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="request" nillable="true" minOccurs="0"/>
            <xs:element name="part" minOccurs="0">
              <xs:complexType>
                <xs:simpleContent>
                  <xs:extension base="xs:integer">
                    <xs:attribute name="final" type="xs:boolean"/>
                  </xs:extension>
                </xs:simpleContent>
              </xs:complexType>
            </xs:element>
            <xs:element name="response" type="xs:string" minOccurs="0"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="rollback" minOccurs="0"/>
      <xs:element name="commit" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

 simpleTransaction那部分基本上没有什么大的变化,只是将transactionID提取到transactionControl下,以供公共使用。而twoPhaseCommit元素就是供我们描述的扩展两阶段提交协议使用。

twoPhaseCommit元素共有以下子元素:serviceNode元素、prepare元素、begin元素、rollback元素和submit元素。我们下面来分别讲解这几个元素的用途:

serviceNode元素:描述服务结点的标识,该元素的值唯一标识了一个服务结点。

prepare元素:完成"Prepare Transaction"阶段的交互。事务管理器发出的"Prepare Transaction"消息是使用prepare元素及其子元素request来表示,同时如果发往服务结点A,那么该消息中的serviceNode元素取值也应该是A。而服务结点对"Prepare Transaction"消息的响应则是使用prepare元素及其子元素response来表示,具体的response元素的值是"accept"表示可以执行事务,如果是"reject"则表示不能执行事务。同样,服务结点A发回的响应消息,则消息中的serviceNode元素取值也应该是A。

begin元素:完成"Begin Transaction"阶段的交互。事务管理器发出的"Begin Transaction"消息是使用begin元素及其子元素request来表示。事务管理器发出的描述事务内容的消息应当使用begin元素的子元素part来表示,part中的数值为其在事务内容消息序列中的序号,而此时SOAP Body中的内容就是相应的事务内容。这一分块传输的机制与先前的simpleTransaction中描述的是类似的,其中最后一则消息分块的part元素应当带有值为"true"的final属性(参照前面的模式文档,part元素可带有一个属性final, final的类型是xs:boolean)。当服务结点接收到所有的消息分块后,即申请所有所需的资源,并使用begin元素及其子元素response来响应。如果资源申请成功,则response的值为"ready",否则则为"failure"。

rollback元素:如果某个服务结点返回的begin/response的值为"failure",那么事务管理器就需要向每个服务结点发送rollback消息。

commit元素,如果每个服务结点返回的begin/response的值都是"ready",那么事务管理器就需要向每个服务结点发送commit消息。

下面我们给出一个消息序列来解释这些元素的使用方式,这个消息序列的应用背景是一个涉及两个服务结点A、B的事务执行。

事务管理器   消息特征   服务结点
准备事务 --> A, prepare.request --> 服务结点A接收事务准备消息
接收响应 <-- A, prepare.response(accept) <-- 服务结点A响应事务准备消息
准备事务 --> B, prepare.request --> 服务结点B接收事务准备消息
接收响应 <-- B, prepare.response(accept) <-- 服务结点B响应事务准备消息
启动事务,发送事务内容 --> A, begin.request, begin.part(1) --> 服务结点A接收事务内容部分1
启动事务,发送事务内容 --> B, begin.request, begin.part(1, final) --> 服务结点B接收事务内容部分1,完成事务内容接收
发送事务内容 --> A, begin.part(2, final) --> 服务结点A接收事务内容部分2,完成事务内容接收
接收响应 <-- A, begin.response(ready) <-- 服务结点A响应,事务准备完毕
接收响应 <-- B, begin.response(ready) <-- 服务结点B响应,事务准备完毕
提交事务 --> A, commit --> 服务结点A提交事务
提交事务 --> B, commit --> 服务结点B提交事务

 未完待续

在本文章系列的第三篇中,我们借助一个服务网络的案例来分析了Web服务事务性的实现,前面我们讨论了Web服务环境下的简单的事务广播,以及分布式事务的扩展的两阶段提交模型的实现。然而大家应该发现,首先,这样的模型仅仅适用于内部网络环境,因为其中缺乏必要的服务结点身份验证,非法的消息可能会造成事务机制的崩溃。同时,在这里描述的事务相对而言还是短事务,而商务上对长事务的需求也是很多的。因此我们考虑了两个方向的解决方案延伸:

安全性事务控制:所谓安全性事务控制,在我们在这个使用SOAP消息来描述事务控制的方案中,结合SOAP消息的安全扩展应当是一个比较有效的方式。Web Services Security(WS-Security)是一个SOAP Header Extension,为Web服务提供了一种保障服务安全性的语言。WS-Security通过消息完整性、消息机密性和简单的消息认证来实现消息安全的目的。这些机制能够被用来适应现有的大量的安全模型以及加密技术。WS-Security同时也提供了一个通用的机制用于将许可证(签署了的信任声明,如x509信任状或者Kerberos tickets等)与消息关联,而不需要指定特殊的格式。我们可以尝试使用这种技术来为事务控制提供安全性。

长事务支持:LRUOW(Long-running Unit of Work)模型是由IBM公司研究小组提出的一种扩展的事务模型。这个模型允许长时间运行的商业流程可以执行多个事务性ACID步骤,同时保证整个处理流程的原子性和独立性。每个LRUOW上下文创建一个版本空间(version space)。每个步骤都操纵版本化的对象(versioned object)。当进入一个LRUOW上下文时,对象的最初状态与全局的版本空间相关联。当一个LRUOW完成时,它的版本空间与全局版本空间重新协调(reconcile)。这个扩展的事务模型弱化了传统事务特性,但提供模型应用者更多的适用性和灵活性。为使系统的并发事务不因长事务而被阻塞,这个模型不强制长时间运行的事务对访问的对象加锁,并允许因此引发的对象数据版本冲突由特定的应用程序方法解决。这个模型也允许单个子事务的失败不强制整个事务的撤消,而允许应用程序采用补偿事务修复失败损失,然后让这个事务继续处理流程。这个模型还支持嵌套事务的提交与回滚。所以,这个扩展的事务模型可以适应企业内部集成应用程序、以及企业之间集成Web服务的需求。我们尝试将该模型应用到Web服务环境下去。

参考资料

作者简介

 柴晓路: 上海得易电子商务技术有限公司(DealEasy)首席系统架构师、XML Web Sevices技术顾问,UDDI-China.org创始人,UDDI Advisory Group成员,IBM developerWorks专栏作家。2000年获复旦大学计算机科学硕士学位,曾在国际计算机科学学术会议(ICSC)、亚太区XML技术研讨会(XML Asia/Pacific'99)、中国XML技术研讨会(北京)、计算机科学期刊等各类国际、国内重要会议与期刊上发表论文多篇。专长于Web Services技术架构、基于XML的系统集成和数据交换应用及方法,同时对数据库、面向对象技术及CSCW等技术比较擅长。

 

相关推荐