本文是Web Service Case Study系列文章的第三篇。在这篇文章中,我将围绕一个事务性应用展开讨论,探讨在Web服务环境中实现原先在数据库层次或者对象层次中实现的事务特性。具体的,这里的应用实例是一个分布式的数据库同步的应用,我们需要解决的是在多个分布在Internet上的同构数据库完成增量式的基于事务的数据同步问题。
应用背景
以下描述中使用的地名、机构名和系统名称纯属虚构,但是是从具体应用中抽象出来的。
Briliiance City是一个医疗保健公众服务正处于建设中的城市。市政府的最终目标是将整个城市的医疗机构(包括综合性大型医院、专业医院、分区医院等)、健康保健机构(社区医院、社区医生)等等通过Internet联系在一起,从而实现将市民的医疗记录和健康保健记录统一管理。具体的来看,这个公众服务网络最终需要具备以下特性:
目前,在Brilliance City的东城区,大部分医疗保健机构都采用了一家称为Health Inside的软件公司的产品部署了各自的应用系统,包括医院信息系统、医疗保健系统等等。由于采用了同一家公司的产品,因此他们在底层数据层的设计是基本一致的,市政府打算以此为试点,如果应用情况良好再推广到全市。
|
解决方案
根据整个服务网络的应用背景,我们设计了这样一个解决方案:一个分布式的医疗保健的信息服务网络。具体描述如下:
数据逻辑集中,物理分布
在这个服务网络中,数据仍然被保存在各个服务结点,对于这样一个Internet环境下的应用而言,数据库集中是无法想象的,Internet不可能永远稳定工作,其次如果采取数据库集中,骨干网上的流量也是无法接收的。由于数据是物理分布的,而逻辑上只有一个合法版本的数据,因此我们需要有跨结点的分布式数据同步机制来保障数据的一致性。
数据同步的事务性
当一个服务结点执行了某个事务之后,为了完成数据同步,我们需要把这个事务在其他相关的服务结点上也同样加以运行,由于数据之间具有关联性,因此在其他结点上的运行必须同样保证事务性。
通过交换中心的消息队列传输
由于各个服务结点的联网状况比较复杂,很多结点都无法一直保持在线的状态,因此使用消息队列来传递事务将比较有效。我们设置了一个用于交换事务数据的交换中心,在交换中心上为每个服务结点提供了in/out消息队列。交换中心是部署在Internet主干上的,因此只要服务结点连上了Internet,就可以通过交换中心收发包含事务的消息。
从数据库模式到逻辑数据模式
整个系统的实施将分布走,首先先将服务网络覆盖到Health Inside的客户,此时由于所有服务结点都是同构系统,因此我们一开始事务是基于数据库模式的,也就是归根于数据库表记录的增删改等。随后将慢慢地延伸到其他公司的产品或解决方案,此时基于数据库模式的事务将不在有效,需要就市民的医疗保健记录及其操作制订基于XML的业务事务规范,所有的事务都将基于这样的格式进行传播,此时在各个服务结点就需要有一个从基于数据库模式的事务到基于XML数据模式的业务事务。为了在这个转换过程中减少代价,降低风险,我们在一开始的基于数据库模式的事务消息交换时,同样使用XML来描述事务,使用Web服务架构来构建交换中心。如此,待服务网络向其他类型的系统延伸的时候,对于服务网络而言,基本无需更新,工作量集中在每个服务结点的数据库模式向业务模式的转化,以及服务结点接入服务网络两大块。前者是面向业务的,工作量无法节约。而后者由于采用了Web服务架构,可以通过Web服务的规范协议方便地构筑系统交互。
基于数据中心的公共界面
在这个服务网络中,除交换中心外,我们还设置了一个数据中心,设置的目的是多方面的。第一个目的是为统一管理市民的基本信息,从这个意义上将,数据中心也是一种服务结点,在数据中心上对市民信息进行更新,同样需要通过数据同步和消息队列的机制将这一更新事务传递到其他相关的服务结点。第二个目的是为对外的面向市民、面向企业的公共服务提供数据,如此就无需在运行时刻频繁地对各个服务结点进行数据请求。同时,当某个服务结点崩溃,需要重建的时候,可以从数据中心下载所有其管理和使用的数据。
体系架构
Figure 1. 体系架构图示
这个服务网络的基本体系架构可参见上图。其技术核心就是各个服务结点之间的数据同步事务的实现。而这一机制的实现,在服务网络的第一期目标下,基本可以归结为两点:
|
数据库事务的XML表示
由于在第一期工程中,所有的服务节点是同构系统,使用了相同的数据库结构,而且为了最大化地减小每个服务节点的实现代价,因此我们采用了完全基于数据库事务的数据同步方式。每条数据同步消息将包含一个事务(transaction)的描述,而一个事务可以包含多个操作(operation),每个操作要么是在某张表中更新(或添加)记录,或是在某张表中删除记录。
根据这样的设计思想,我们可以定义了transaction元素(包括其子元素)来表示一个事务消息,具体的Schema定义如下:
|
在这个模式定义中,核心是对数据的操作,这里为简便性起见,仅仅定义了两个操作save_data和delete_data,这也是符合国际惯例的定义。理由是,新增数据(记录级)和更新数据(记录级)可以使用相同的调用。相似的模式定义我在本系列的第一篇文章中已经提及。
这里采用的描述方法是针对通用的数据库操作的,也就是说无论具体数据库的结构如何,只要每个端系统都是相同的,那么这个消息模式就可以应用。在save_data元素下,包含了三个元素:
而对于delete_data,其结构与save_data是类似的,唯一的差别就是没有allField元素,原因相信大家也很容易理解,在删除数据时,只要知道主键就足够了。
有了Schema的模式定义文档,我们就能够生成SOAP消息了,下面首先给出的是一个由单个save_data操作和单个delete_data操作组成的事务的例子。该事务从candidate_employee中删除一条记录,而在employee中添加了一条记录。
|
|
事务性的保证
由于我们使用Web服务架构来架构我们的交换中心。因此在我们定义好了事务消息的XML描述之后,就需要实施如何通过SOAP消息来传输所需要传播的事务消息了。对于这个服务网络而言,其核心问题就是要将服务节点的事务(目前是数据库事务,以后将延伸到业务事务)在各个相应的服务结点上同样以满足事务性的条件下执行。由于我们采用的是Web服务架构,Web服务架构的现有技术中尚未有正式的支持事务的规范或技术。因此在这部分,我们将首先围绕当前这个服务网络中的应用来讨论Web服务的事务性,当解决了当前这个应用实例中的问题之后,我们将从通用全面的角度继续考察更复杂情况下的Web服务事务性的保证。
最简单的情况
如果我们在传输事务消息的时候,总能保证单个事务消息使用单个SOAP消息来传输的话,那么这就是最简单的情况。此时,SOAP仅仅是传输事务数据的机制,而并不包含事务控制的功能。
具体的流程可以描述如下:
从单条消息到多条消息
然而,并不是在任何情况下,使用一条SOAP消息就能够传输一则事务的。由于SOAP消息最终是要和某一种网络协议进行绑定并在网络上进行传输的,大多数网络连接很难保证在一个连接内能稳定地传输大量的数据,比如HTTP,我们在下载/上传大文件的时候,经常会发生中断,此时我们就不得不重传(HTTP没有断点续传),重传需要耗费同样长的时间,同时传输时间越长发生错误的可能也越大,因此传输大文件如果不加以特别的措施的话,常常会变成一场噩梦。我们通常的方法,是将大文件分割成多个小块,分别传输,等全部传输完毕后在对等方组合。由于单个文件块比较小,传输稳定性得到了提升,同时即使传输失败,重传的话也比较快。
我们将这一用于文件传输的方法,延伸到事务消息的传输中,由于这是一个平台级的特性,因此我们按照SOAP规范的推荐,使用SOAP Header来控制事务消息的合并执行,同时为了提高响应率,我们的算法并不一定要等到关联单个事务的消息全部收到才会执行事务,我们采取了一定地提前执行的机制,具体细节,我将在下面详细阐述。
首先我们给出transactionControl这个SOAP Header Extension的模式定义。
|
在这个模式定义中,我们看到transactionControl这个SOAP Header Extension包含了一个子元素simpleTransaction,我们在下面就使用这个子元素来描述事务消息,为是么不直接使用transactionControl元素,而要在其下再定义一层子元素,是为了以后对事务控制特性进行进一步的扩展,我们会在这基础上实现两阶段提交和三阶段提交等。
simpleTransaction元素包含了五个子元素:transactionID、begin、commit、rollback和part。他们的含义分别如下:
由于这五个子元素可能同时出现(rollback和commit不会同时出现),当服务结点接受到事务消息时,上述的五个子元素被解释执行的先后次序为:transactionID、begin、part(触发解析SOAP Body)、commit & rollback。他们的工作原理可描述如下:
当服务结点受到一个事务SOAP消息之后,首先解析出transactionID,如果是新的transactionID,那么在服务器端的事务池中,新建一个事务,并将transactionID赋予之。如果事务池中已经有了这个transactionID,那么就将这个事务消息与之关联。当完成transactionID的识别之后,如果消息中带有begin,那么就启动这个被关联的事务池中的事务。如果消息中带有part,假设part的值为n:
如果消息中带有commit,那么在缓存事务部分的时候(这一定是最后一个事务部分),对其加上标志:"这是该事务的最后一个部分"。当该部分被执行时,整个事务将被提交。
如果消息中带有rollback,那么对应的事务被立即回滚,然后该事务失效,如果之后仍然收到该事务空间中的消息的话,服务结点将丢弃这些消息。
例如:服务结点可能以如下次序接受到关联同一事务的事务消息:
[part(2)], [part(4)], [begin,part(1)], [part(3)], [commit, part(6)], [part(5)]
那么服务结点的动作序列应当为:
在这一节中介绍的方法解决了在分布式的环境中,在单点数据库中完成了事务的执行。这个机制有效的解决了我们这个服务网络应用实例的当前需求。
然而,我们考虑到,在将来整个服务网络将向公众开放,包括个人用户和企业用户在内的各种用户都可以访问和使用服务网络。虽然一开始只有一些数据查询的事务开放给他们使用,不过随着整个服务网络的成熟,我们将会在数据中心层次上对外提供业务处理服务,而这些服务被使用时,势必发生一些业务事务,根据整个服务网络的设计原则,这些业务事务需要在整个服务网络上被执行,在这种情况下,与前面我们介绍的单个事务的重复广播有所不同,这里发生的是一个涉及到多个服务结点的分布式事务。例如:这个事务可能同时涉及某个市民的医疗记录和保健记录,而这两者分别是由两个服务结点来管理的。前面的机制已经无法解决这一问题,下面我们将两阶段提交协议(2PC)引入到我们的Web服务环境中来,以尝试解决这一分布式事务的问题。
两阶段提交(Two-Phase Commit)
两阶段提交。分布式系统中处理用户提交的事务时,事务管理器通常使用两阶段提交协议保证所有操作所涉及到的分布式环境中的各个数据库中的相应数据被锁定从而最后被正确地更新。如果因为某种原因操作不能完成,则要求统一执行"回滚"操作,回到事务开始前的状态。如果事务被成功执行,那么所有相关的数据库都被正确更新,此时这些更新应当被分布式环境中的所有数据库系统都了解。最后解除相关数据库重相关数据的锁定状态,以便进行下一个事务的执行。
以下我们描述一个扩展的两阶段提交的协议过程(这个扩展的2PC比标准的2PC多了预先准备的三个步骤,也被称为3PC):
在这里,我们有两个假设:
在仔细地分析了两阶段提交之后,我们来扩展先前定义的transactionControl元素,为这个SOAP Header Extension增加新的功能。
|
simpleTransaction那部分基本上没有什么大的变化,只是将transactionID提取到transactionControl下,以供公共使用。而twoPhaseCommit元素就是供我们描述的扩展两阶段提交协议使用。
twoPhaseCommit元素共有以下子元素:serviceNode元素、prepare元素、begin元素、rollback元素和submit元素。我们下面来分别讲解这几个元素的用途:
下面我们给出一个消息序列来解释这些元素的使用方式,这个消息序列的应用背景是一个涉及两个服务结点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等技术比较擅长。 |