技术开发 频道

详解SOA五种基本架构模式

模式三:事务处理服务模式

    服务构建的另一个重要属性是:怎么处理从边界组件或服务中得到的信息?事务处理服务模式(Transactional Service Pattern)可以解决这种问题,并且还能解决可靠性问题。

    可以把SOA活动简化为服务收到服务消费者要求做某件任务的请求,服务处理请求(可能还会请求其它服务一起做这件任务),然后回应发起请求的服务消费者。图6显示了这样的一个商业系统中的活动场景。前台与订购服务进行对话。订购服务登记订单,把订单发送到供应商,然后通知账单服务。这些事件处理完成后,订购服务向电子商务前台应用发送一条确认信息。这一切看起来井然有序,但万一中途发生错误怎么办?

    

    问题

    比如说,订购服务在确认订单与处理的中途产生故障的话,也就是图6中的步骤1.1与2.0之间,会出现什么情况呢?我想顾客应该会坐在舒适的沙发上,喝着茶水,等着邮递员把她订购的东西送过来。但是虽然她在等,订单却已经消失得无影无踪了。

    那么如果服务是在报账服务处理订单之间出现故障呢,也就是步骤2.3之前。这种情况下,订购同样会消失——除非订购系统不等待报账便处理了订单,这几乎是不可能的。更糟糕的是,我们已经向供应商发送了订单,供应商已经把账单发了过来,并且货物也随之送了过来,我们还得给货物准备库存。

    消息处理过程处处都可能出现前面提到的这些情况。我们可以安慰自己,说大部分情况下系统会正常工作。然而就像莫非定律所说——我们的服务最终必然会在那宗百万美元的订单上垮掉。现在的问题是:

    如何让服务可靠地处理请求?

    其中一个方案是把这个责任推给服务消费者。在上面提到的场景中,这意味着如果消费者没有在步骤2.5中收到订单确认信息,那么这个订单就是失败的。然而首先,这个方法并不健全,并会降低服务的自治性——服务无法控制消费者,也无法处理一些其它问题。另外,这只能解决部分问题——那些与服务消费者有关的问题。服务之间的相互作用呢?比如在上面的订购场景中提到的——即使在步骤2.1向供应商发送订单以后,仍然可能出现问题。

    第二个办法是同步处理消息。同步操作在性能上会产生很大的问题,特别是当服务需要与外部服务、系统或资源交互的时候,因为服务在返回结果之前,整个流程都要等待第三方的回应。更主要的是,实际上这并没有真正解决问题。如果服务在流程中出现故障了,我们无法确认是哪里的故障。我们只知道消息传输出现了故障,而要确定故障环节,则需要服务消费者的帮忙。

    表面看来,如果服务能够使用永久性地储存介质(比如到数据库)就可以解决这个问题。我觉得这个方向是不错;但是,这还不够。因为如果服务在储存状态之前出现了故障,传入的消息仍然会丢失,并且服务对此一无所知。还应该注意到,如果使用永久性存储介质,我们虽然可以追踪到在过程的哪一环节出现故障,但是我们无法确定消息是否已经发送到了其它服务。

    要解决这些问题,以及整体的可靠性问题,我们需要事务处理服务。

    解决方案

    使用事务处理服务模式,一次性处理从读取消息到响应的整个流程。

    事务处理服务模式的主要组件是消息泵(message pump),见图7。消息泵监听着终端或边界传入的消息。如果接收到消息,消息泵就开始一个事务操作,读取消息,把消息发送到其它组件/类进行处理,处理后,结束事务操作(完成或失败)。如果可以以事务处理的方式发送请求或回应,就可以把这些操作放入到事务处理中,否则你就需要为操作失败准备补偿逻辑。

    

    使用事务处理编程模型的好处是它要么是纯语义学的,要么完全不是,因此不存在边界效应。由于事务的四个属性(ACID),所有的操作和消息都一定会被完全处理完、或完全没有被处理,所以如果有消息离开了服务,那么触发这个行为的传入消息肯定是被完全处理过了。

    ACID事务

    一个事务是一个完整的任务单位。一个任务单位如果满足ACID所定义的四个属性,那么它就是一个事务。

    * 原子:事务中的所有事件都是以一个原子单位的形式发生的(atomic unit)。这些事件要么全部发生,要么全部不发生。

    * 一致:不管事务完成与否,事务的资源必需在整个过程保持一致。

    * 隔离:所有外部的观察者(不参与事务处理)都不能看到内部的状态。只能在事务处理开始前或完成后查看状态。

    * 持久:事务处理过程中做出的改动储存在永久性存储介质中,因此即使系统重启后也不会丢失。

    当然,你要选择事务服务模式的重要因素肯定还是性能。由于需要准备、为了持久性而做的输入输出和锁定管理等,事务通常会比较慢。我一般会预告确定目标场景并进行测试,以确保能够得到一个足够好的方案。

    实现事务处理服务模式的一种方法是为所有服务间的消息使用事务消息传输。事务消息传输(transactional message transport)使得模式的实现变得非常简单——只要按照前面提到的步骤来就可以了:开始事务、读取、处理、发送、完成。另外一种方法,也是更常见的一种方法,是在接收到消息后把消息放到事务处理资源中(比如队列或数据库),然后向服务消息者发送一条确认消息。但是这种情况下最初的消息不包括在事务处理中,因此你要准备应付服务消费者的多次消息发送。比如,服务消费者没有收到确认消息,于是“又”发送了一条请求消息提取100万美元。

    图8是图6的事务处理服务模式。简单重复一下,该场景是一个关于电子商务的前台订购服务。订购服务登记订单,把订单发送到供应商,然后通知账单服务。这些事件处理完成后,订购服务向电子商务前台应用发送一条确认信息。

    

    在图8中,使用事务处理服务模式,步骤2.0到2.5(订购服务的行为)处于同一事务中。这意味着如果你因为故障或其它意外没有处理下订单的消息,那么服务就不会发出任何消息。这是个很让人开心的消息,因为我们不用再写复杂的补偿逻辑了。这里有一个小问题,那就是如果订购服务在步骤1.0到1.2之间出现故障的话会有什么情况。该场景不是100%安全的;它有很小的几率在我们把消息放到队列等待处理的时候出现故障,从而没有发送确认消息。这可能导致重复接收到同样的请求。一个在服务这边处理重复消息的办法是在服务启动时查看消息队列并对所有消息发送确认消息,这种情况下,服务消费者可能会收到多于一条的确认消息。

    请注意,在这个示例中,只能在账单处理过程仅仅产生一个发货单的情况下使用单独的事件。如果账单服务还需要处理信用卡,并且订购服务需要得到确认信息才能继续的话,就不能使用单独的事件了。当不能使用单独的事件的时候,需要把过程分成较小的事务,这时整个过程就被称为连续操作。需要把流程分为几个较小的事务的另一个条件是看服务是否是分布式的。

    必须注意把事务的范围定到终端/边界和外部的消息发送者是不一样的。虽然从表面看来,这个区别不是很重要;但是实际上它确实很重要——因为前者是增强服务的可靠性而后者是提高系统的耦合性并会给你带来让人头痛的问题。如果你把事务扩展到服务之外,那将是非常大的转变,因为其它的服务是运行在自己的机器的,有它自己的服务等级协议等等。把内部资源暴露到服务信任协议之外是很冒险的做法。

    现在我们看看如何实现事务处理服务。

    技术相关

    这一部分我们将简单的了解一下利用当前的技术实现这个模式的意义。

    如果消息传输是对事务敏感的,那么实现事务处理服务就容易得多。大部分ESB(比如Sonic ESB和Iona Artix)都是事务敏感的,另外还有消息中间件(比如MSMQ)、所有JMS实现以及SQL Server Service Broker。如果你在使用事务消息传输,你可以通过仅仅在资源上创建一个事务来实现事务处理服务模式。如果,比如你还要在同一任务单元中进行更新数据库的操作,你可能还需要分布式的事务处理。然后只要从ESB或消息中间件读取消息、处理、发送响应或其它处理过程生成的消息到外部或目标队列,最后一切顺利的话结束事务。

    要注意通常要在事务处理中用到多项资源,比如,一个消息队列和一个用于存储消息处理后的任何状态变化的数据库,这时你应该使用分布式事务处理。在.NET 2.0及以上版本中,如果有需要的话,你可以通过打开一个TransactionScope对象(定义于System.Transactions)显式地过渡到分布式事务处理。

    如果消息传输不支持事务,只在你把消息存储到了事务存储库(比如队列或表)后有确认信息。你仍然有在没有得到服务消费者的确认的情况下处理事务的风险,因此你得做好再次接收请求的准备,以防确认消息丢失或根本就没有发送。如果出现故障,而处理传入的消息的事务向其它服务发送了消息,你也要准备好补偿逻辑。

    毒信息

    当我们以事务的方式读取信息时,需要注意分辨并处理毒信息。毒信息是一种有缺陷的信息,它会使服务发生故障,或者使服务在处理这条消息的时候一直产生处理失败的结果。原因在于,如果在事务中读取了一条毒信息,所产生的故障就会使信息返回队列;而毒信息则在队列中等待服务恢复,然后再被服务读取,依此循环。某些信息技术产品可以辩认这种毒信息并将其丢弃。你需要确认的是这些辩论机制到位,并且能够妥善地处理所有故障,或者至少把这些故障交给你亲自处理。

    有一种叫WS-ReliableMessaging貌似跟此有关。然而,虽然名字看起来很相似,但是实际上这只是一种能够稳定地点对点传输消息的协议,可以说更像是HTTP的TCP协议,跟持久性或事务处理根本没有一点关系。但许多ESB是事务性质的,能够支持这种协议,因此你可以通过结合标准协议和事务消息处理来得到一个完善的结果。

    其它相关的协议有WS-Coordination和其“同宗”的WS-AtomicTransaction和WS-BusinessActivity。现在我们主要来谈谈WS-AtomicTransaction。从根本上说,WS-AtomicTransaction定义了一种编排分布式事务处理的协议。一般来说,我不会建议使用WS-AtomicTransaction,因为它在服务间引入了太多的耦合关系。比如,在图8描述的场景里——我们真的想在等待外部供应商答复的时候锁定资源并且降低我们的订单的优先级吗?

    如果使用了事务敏感中间件的话,情况就会有所不同。这时已经不是跨越服务的单一事务,而是分成了三个更小的事务:一个发送服务;内部中间件保障消息的传递;最后一个在中间件与读取者之间——这是与基础设施的耦合,可以从边界组件上分离出来。

    质量属性场景

    这一部分是从需求的角度讨论使用模式的架构效益。大多架构需求是通过使用场景表现的质量属性(可扩展性、灵活性、性能等)来描述的。这些场景也可供其它情况作为应用模式的参考。

    事务处理服务引入的事务语义可以简化编码和测试。另外,它还能极大地提高服务的可靠性与稳定性。因为其“全有或全无”的属性使得编码变得简单,这让开发人员能够集中精力实现商业价值,而不是考虑一些边界效应或者类似的问题。

    下面是几个场景,可以给你考虑使用服务托管模式的理由。

质量属性(第一层)质量属性(第二层)场景示例
可靠性数据损失在任何情况下,凡是经过系统确认的消息都不会丢失
易测性覆盖率所有场景都能得到95%甚至更高的测试覆盖率

     之所以事务处理服务模式能为我们节省编码时间,是因为事务不像非事务代码有那么多的边界效应。还有一种可以节省编写代码时间的模式是工作流化模式(Workflodize Pattern)。

1
相关文章