1. 简介
Biztalk 2006的补偿模型(Compensation Model)为解决多种多样的商业过程应用场景提供了一种通用机制,被应用在某些条件下需要回滚跟同一个商业活动相关的已经完成的一部分工作单元的情况。在这些情况下,补偿模型通常需要重新访问已经完成的工作单元,检查这些单元在各个阶段的系统状态,以采用合适的动作补偿已经完成的动作,至少,也可以维护一个审查日志或提供相关通知。
一个课本上的此类场景是,在订单/发货过程,一个客户取消了已经被部分处理但是还没有发货的订单。取消操作何时发生具有不确定性,货物可能已经被从仓库提出,可能已经被分派,也可能已经在路途上,发票可能已经开具并发送等等。订单/发送过程中必须有强壮的取消订单的机制,无论取消操作发生在过程中的哪个环节。
Biztalk 2006的补偿就是设计用于这种情况的机制,它提供了向后跟踪已经完成的单独工作单元,同时自动忽略没有完成的工作单元的灵活手段,它允许补偿逻辑访问被认为已完成的工作单元的过程状态。与异常处理和事务管理协同工作,它允许流程设计者定义当补偿动作被执行时将被调用的商业过程的路径。它不能解决所有的问题和应付所有的场景,无论如何,它被认为是实现失败处理和还原逻辑的必需手段。
2. 长时间运行的事务(Long Running Transactions)
Biztalk的补偿不可避免的跟长时间运行事务的概念有关系。长时间运行事务(L-R事务)是具有更高一层水平事务(面向商业)的工作单元,但它不是标准的ACID(atomic, consistent, isolated and durable)的事务。因为通常(虽然并不总是)这样的工作单元将耗费很长的时间,这里的很长可能意味着几秒钟到几天、几个月,甚至几年。ACID事务之所以不适合这种情况是因为,ACID事务在事务期间锁定访问的数据,并要求并发的事务串行化,而长L-R事务的运行时间很长,不可能在很长的时间内一直锁着数据。L-R事务通常用于需要人工交互的过程或者需要跨组织边界进行交互的过程。一个L-R事务内的子活动可能具有很大程度的不确定性 ,例如,一个商业过程会受许多不受控制的、难于预期的外部因素的影响。 L-R事务的不确定性不适合传统的ACID事务模型,需要一种不像ACID模型那样严格的事务模型。
通常根据简化ACID相关属性来定义L-R事务,比如,微软定义L-R事务为“过程具有一致性和耐久性,但不具原子性和隔离性”。 在biztalk的环境下,这样的描述大致是正确的,但是有些过分简单。它容易让人认为L-R事务是一种有缺陷的ACID事务, 这样看待L-R事务是不正确的。 ACID事务假定具有确定的存在,并使用一组定义明确的约束进行事务管理。L-R事务没有这些假定,因此有更广泛的适应性。关系型数据库申请ACID事务只使用到保存在数据库表中的数据,一个orchestration,必须使用贯穿整个商业过程中使用到的各种各样的数据,这些数据可能是内部数据,也可能是外部数据,外部数据可能是各种各样毫不相干的系统管理的数据,它们可能支持,也可能不支持ACID事务。
L-R事务可以被简单的定义为一个不遵循ACID模型的模仿现实世界的商业事务的机制。一个商业事务的定义 (基于1999年美国的Uniform Electronic Transactions Act) 是“一个或一组有两个或多个合作伙伴或参与者参加的涉及商业或者政府事务的活动”。 一个L-R事务跟一个ACID事务不以同样的方式进行约束,虽然它会展现出某些跟ACID事务相同的特性,它可以被分解为许多更小的内部事务,这些小事务也可能是ACID事务。
3. Sagas(传奇)
术语“saga”在biztalk社区中不是经常被提到,但是它和L-R事务这个概念有很深的关系,并有一个古老的历史。Sagas是由Hector Garcia-Molina 和 Kenneth Salem定义的,他们的论文发表在20年前,Sagas被描述为在关系型数据库中处理“长寿”事务的机制。这个概念是这样的,在一些情况下,一个长寿的ACID事务可以被分解为一系列更小的、单独的内部事务,这些内部事务跟其它Sage的内部事务对资源进行访问是串行的(如下图所示,两个Sage中的ACID事务不会同时发生),每个内部事务保持它的ACID特性,成为外层Sage的一个成员。Sage自身不处理ACID特性,但是会呈现出一个整体性的长寿事务。
图一 并发的Sage
Sage最与众不同的特点是捕获和处理失败的方法。在每个内部事务(ACID事务)水平,事务的失败使用通常的方法进行回滚,可是,如果在Sage的内部事务序列中前面内部事务已经被提交,要回滚这些事务就为时已晚了。因此,如果,在任何Sage中,第二个或者后续的事务失败,Sage将在这个中间状态被总体上被抛弃,此时,有的工作已完成,有的工作还未完成。
为了处理这种情况,Sage使用补偿块(compensation handler)的概念,每个内部事务都被提供一个属于它自己的补偿块,如果发生失败,失败的那个内部事务被以常规方法回滚,然后Sage执行每一个先前已经完成了的那些内部事务的补偿块,它按照内部事务被提交的逆序调用内部事务的补偿块。因此,如果在Sage A中事务4失败了,Sage调用补偿块的顺序将是按照事务3、2、1的顺序调用。
图二 Sagas – 逆向恢复(Backwards Recovery)
这个方法被叫做“逆向恢复(backwards recovery)”,当然,术语“恢复(recovery)”被用于很广泛的领域。我们不能回滚已经提交了的事务,所以每个补偿块必须用一些动作完美的取消或者逆向恢复跟它相关的那个内部事务的动作做产生的效果。更正式的,理想的情况是,保证每个内部事务(T)和它的补偿事务(CT)可以相互交换,如果是f(T,CT),即先执行内部事务T,然后执行它的补偿事务CT,所呈现出的系统状态的变化,跟先执行CT后执行T之后效果一样。如果不能保证这一点, Sage不是一个合适的关系数据库事务机制。
在包含了多个并发Sage的数据库系统中,每个Sage可以看到被另外的Sage做的部分改变,在Sage级别没有隔离性,Sage也不具有原子性。因此,可交换性不是总能达到的,Sage在适用性上有所限制。Sage可以安全的被用于下列一些场景:不需要事务隔离的场景,每个Sage的每个内部事务操作被约束的数据的场景,执行有效的跟其它Sage隔离的约束操作的场景。
除了逆向恢复,Sage模型还引入了正向恢复(forward recovery)的机制。在Sage流程内部定义一些保存点(save points),这些保存点把当前的Sage状态持久化保存到磁盘。Sage中的内部事务,它们遵循ACID模型的规则,自己内部已经能保证一致性和耐久性。保存点能保证Sage自身的上下文数据被持久化。
当一个内部事务失败时,Sage能从保存点再重新开始。如果在失败点和保存点之间有一个或者多个内部事务已经被提交,Sage必须首先使用部分的逆向恢复,执行补偿块补偿已经提交的内部事务,然后回到保存点重新开始Sage。
图三 Sagas – 前向恢复(Forwards Recovery)
这个方法的一个明显的问题是重试之后Sage可能还是不能完成,因此,它很容易陷入一个死循环。解决这个问题的方法之一是,联合补偿块和异常处理器,比如,一个系统可能察觉到这样的循环,然后在合适的重试次数过后,不再循环重试,调用异常处理器,异常处理中进行这种不会恢复的场景处理。另一个方法是,Sage中每个内部事务前都设置一个保存点,这样一旦哪个内部失败就不用先执行任何逆向恢复了,只要重试它自身的失败就可以了。
4. Saga模型和商业过程管理(Business Process Management)
BizTalk Server,跟其它遵循 BPEL4WS规范的系统一样,为使用自动化商业过程的上下文改造和扩展了Sage模型。有一点需要强调,上下文真的跟关系数据有很大的不同,在关系型数据库中,我们通常只有有限的几种需要管理的数据种类,包括:
l 存放在数据表中的关系数据
l 在存储过程中的应用级别的数据,比如声明的变量和光标。
l 在内部事务水平和Sage水平在数据库中管理的底层上下文数据。
当然,这个列表不是完整的列表。目前,存储过程可以很好的跟外部系统交互,发送和接收信息等等。无论如何,关系数据库系统提供一个约束的环境,Sage可能影响到的大多数数据是被数据库系统管理的。复杂的过程工作流通常不在数据库中管理, Sage的流程可以跟多个其它Sage结合以支持更复杂的流程。
在biztalk,事情更加复杂,考虑biztalk的orchestration引擎必须管理的数据:
l 在orchestration中为每个scope定义的内部上下文数据,包括整个orchestration scope。
l 在特定scope级别定义的定制数据,包括:不可改变的message,orchestration变量和相关集,端口和角色链接。这些可能在运行时通过call orchestration形状传递到子orchestration中
l 在orchestration中建立的.net对象。
另外,它还必须提供一个跟广阔范围的外部数据进行交互的安全和可用的环境,包括:
l 跟.net对象交互的数据,它们可能在内存中,或者被持久化到磁盘,或者是通过支持或不支持事务的外部资源管理器管理,或者是跟非biztalk进程共享的数据。
l 更特别的是,通过System.EnterpriseServices.ServicedComponent派生的类可以具有DTC类似的行为。
l 服务(包括web services),应用等等,我们可以经由message box同步或者异步向它们接收或者发送消息。
除了这些,考虑任何一个orchestration可能展现出一个复杂的处理流程,在这个复杂流程中的参与方可能是另一个orchestration,也可能是许多外部系统。跟关系型数据库系统比较,它的环境更具挑战性。考虑一个完整的复杂的商业过程,包括所有参与的系统(biztalk是其中之一),不同的数据存储和消息流在整个商业活动中穿越。Biztalk的orchestration是被设计用来控制这种复杂的流程的关键角色。
早前,我们讨论过,在Sage模型中,一个事务应该可以和它的补偿事务相互交换,这对事务主要是处理有良好数据结构关系数据库系统来说是比较有意义的,在商业过程的场景,我们在某种程度上对这个交换性会放松一些要求。 Orchestrations模型的现实商业过程,经常的参与者包括人类、外部系统和组织等等,它并不总是需要严格的确保事务和补偿事务的交换性。实际上,我们使用补偿模型承担广泛的补偿任务而不是“纯的”补偿机制。 比如, 在许多情况下,补偿模型足够用来简单的警告最终用户已经发生了异常,并告知他们在失败点在哪,哪些子任务已经执行完成。在我们的商业过程管理的状态上,可能并不需要做真的补偿性行为。
5. BizTalk – 基本补偿模型
Biztalk的基本补偿模型是有Sage模型的逆向恢复组成的,这个模型扩展了许多方法。我们先研究基本模型,然后下去看扩展。这个模型集中于使用L-R事务和atomic事务类型的scope。一个L-R 事务类型的scope可以看作是个Sage,跟Sage一样,它里面可以嵌套Atomic类型的scope(atomic scope遵循ACID事务模型)。每个atomic事务可以有一个补偿块(Compensation Block),在补偿块里,开发人员可以实现相关补偿的代码。Biztalk中L-R事务有个缺省的补偿处理器(compensation handling),内建逆向恢复的逻辑。当某个嵌套的atomic事务抛出一个异常,外层的L-R事务可以选择调用缺省补偿块进行逆向恢复。
跟exception 处理器的调用不同,缺省补偿块不会被自动调用,它总是被一个L-R事务的异常处理器中的上下文调用。这里有两点比较容易让人感到迷惑。
首先,补偿块看上去很像异常处理器,所以,开发者会很自然的想这个补偿块是被atomic事务调用的。事实并非如此,当一个atomic事务抛出一个异常后,会被这个atomic scope直接外层的L-R事务的scope的异常处理器捕获,Atomic scopes不能有它自己的异常处理器。补偿块只能在直接外层的L-R事务的异常处理器捕获到异常后调用。
其次,每个L-R事务默认有个缺省的“General Exception”的异常处理器。它的行为就是捕获所有的异常,它在orchestration设计器中是不可见的,是在XLANG/s底层实现的。它等同于一个捕获‘General Exception’的异常处理器,里面包含一个Compensate形状和一个Throw形状,Compensate形状调用相关的这个L-R事务的compensation处理器,Throw形状再次抛出捕获到的异常。
你可以通过显式的新建一个捕获“General Exceptions”的异常处理器来override掉默认的异常处理器。这个方法可以用来抑制补偿,更可取的方法是建一个捕获“.NET exception”类型的异常处理器,默认异常处理器只有在有未被新建的异常处理器捕获的异常时才会被调用。“General Exception”的异常将捕获所有的.NET exceptions,包括不是从System.Exception继承的异常,如此宽松的类型以致不能给捕获到的异常对象提供任何可访问的方法和属性。大多数的.net语言,包括C#,VB和XLANG/s,会要求异常类都从System.Exception类派生,而且在biztalk环境中非System.Exception的异常是非常罕见的。如果你自己建的异常处理器捕获到一个异常,你可以使用Compensate形状调用补偿块。
Atomic的补偿块跟异常处理器有很大不同。它们更像跟这个scope关联的嵌套sub-orchestrations,作为补偿处理代码的容器,其中的代码只有在跟其关联的atomic scope成功提交后才可能被调用,因此它不能被跟其关联的scope中的上下文调用。它总是被外层的L-R scope调用,你可以把这样的调用想像为使用一个call 形状调用一个子orchestration,只不过,在这里是使用Compensate形状。Compensate形状典型的在外层L-R scope的异常处理器中使用,尽管在扩展模型中,它也可能出现在外层L-R scope的补偿块中。补偿事务总是只能作为捕获和处理异常直接或间接的结果,biztalk的补偿有效的构成了内建异常处理模式。
实现类Sage的模型,补偿块被捕获到异常的外层L-R事务直接调用,而不是任何嵌套的事务。假设外层L-R事务的‘Compensation’属性设置为default,biztalk将正如你预期那样的执行补偿动作,根据提交的逆序调用所有的内嵌的已经提交的atomic scope的补偿块。
图四 :基本补偿模型
这就是基本模型的全部逻辑,不过这对于biztalk的开发者来说并不总是显而易见的。在Sage模型中,Sage自己负责执行嵌套的Atomic事务的补偿动作,这也正是biztalk默认的补偿机制。不过,开发者熟悉异常处理而经常会对补偿模型不太了解,这可能是问什么在许多biztalk项目中都没有出现补偿的原因。
6. BizTalk中的前向恢复
Biztalk提供了非常有限的类Sage的前向恢复支持,有两个前向恢复的机制:
l Atomic scope的重试
Atomic事务有个‘Retry’属性,如果设置为true,atomic事务在特定的情况下,atomic会尝试重新执行事务,只有在下面三种情况下会重试:
n Biztalk server尝试提交事务,但是提交失败。
n Biztalk server在事务的结束处持久化状态失败,这是第一种情况的特例,提交包括了状态持久化
n 在atomic事务代码中抛出RetryTransactionException异常
atomic事务将进行21次的重试,在前两种情况,biztalk使用2秒的间隔重试,这个值不会出现在可配置的设置中。如果是抛出一个RetryTransactionException,重试间隔就会被缺省的设置为0,不过,在这种情况下,你可以使用DelayFor属性配置你自己的间隔时间。21次重试后,atmoic事务还是不能被提交,或者orchestration的状态不能被持久化,biztalk将挂起(可恢复)这个orchestration实例。
l 恢复挂起的orchestrations
一个挂起的orchestration实例可以通过手工在管理控制台恢复也可以通过一些脚本来恢复执行。在这种情况下,biztalk会尝试从最近的持久化点重新启动挂起的orchestration,每个事务都在它提交后建立一个持久化点以保存当时的状态,以保证在恢复的时候永远不会存在需要部分逆向恢复的事务。
7. 扩展的缺省补偿模型
如果你已经在biztalk中使用了补偿特性,你一定会发现上面关于默认补偿的描述并不完全就是biztalk所提供的全部功能。Biztalk在许多方面扩展了基本补偿模型,以更好的适应商业过程管理的复杂性提供更大的通用性。
与Sage的扩展版本一致,biztalk允许在外层L-R事务中嵌套L-R事务,任何外层的L-R事务都能包含L-R事务和atomic事务,嵌套的L-R事务还可以继续嵌套别的事务,Atomic事务不能再嵌套任何事务。
这个方法为设计和实现复杂商业过程的补偿策略提供了很大的灵活性。每个嵌套L-R事务都能被用于控制它的直接子事务的补偿,并为上层的L-R事务提供一个补偿目标。正如我们看到的,补偿块总是被异常处理器中的代码调用,由于biztalk orchestration中结构化异常处理器的使用,这允许根据捕获到的异常类型执行相应的商业过程子集的补偿。
当一个外层的L-R事务捕获到一个异常并执行缺省的补偿,biztalk不区分嵌套的L-R事务或者atomic事务,外层L-R事务将按照逆序调用每个成功提交了的事务的补偿事务。Atomic事务被提交完成并且orchestration的状态被持久化后这个Atomic事务被认为是成功完成。嵌套的L-R事务当人不是以ACID方式提交,当它内部所有的活动都正常,没有向它抛出异常或者导致orchestration挂起的问题,这个L-R事务就被认为是成功完成的。
如果嵌套的L-R事务没有定制的补偿块,将执行缺省的补偿逻辑,它的补偿逻辑就是逆序调用内部已经成功完成的直接子事务的补偿事务(即补偿块中的逻辑)。嵌套的L-R事务内部的事务对外层L-R事务不具可见性,外层L-R事务看嵌套的L-R事务是个黑盒子。
在使用嵌套L-R事务扩展缺省类Sage补偿模型和用补偿块的事务都是atomic事务的情况下,我们可以想像这个扩展模型就是一个简单的“补偿树”,叶子是atomic事务和它的补偿块,根和分支是L-R事务。
图五: 补偿树
这个模型展现出一个精妙的特性。考虑一个简单的场景,我们把一个L-R事务(L-RInner)嵌入到一个外层的L-R事务(L-Router),L-RInner包含一个atomic事务(ACID Tx1)。如果在L-RInner范围内在ACID Tx1被提交之后的某一点有个异常被抛出,异常会被L-Router的异常处理器捕获,如果L-Router调用它自己缺省的compensation handler,biztalk将会根据提交的逆序依次调用L-Router的每一个已经完成的直接子事务的补偿事务,可是,L-RInner事务没有成功完成,它抛出一个异常,因此,我们可以预期它的补偿块不会被调用,ACID Tx1也因此不会被补偿。
实际上,L-RInner事务也有缺省的异常处理器,就如我们看到的,它将调用L-RInner 范围内的补偿,然后再次抛出捕获到的异常,因此,ACID Tx1将会得到补偿,即使它的外层L-R事务没有成功完成。L-RInner 将自动拦截这个异常,当然,你可以通过使用显式的异常处理器来override缺省的异常处理器。注意每个L-R事务只对它的直接子事务的补偿负责。
图六:补偿树的基本流
Atomic scopes 不能嵌套事务,所以它在补偿树上它总是表现为一个叶子,另外,它们不能有异常处理器,这点经常是开发者感到惊讶,但这是综合考虑了异常处理器和补偿块后的逻辑结论。当然,你总能把非事务的scopes签入到atomic事务中,为了让在atomic中捕获异常。
8. 在L-R 事务中使用补偿块
前面的章节我们看到嵌套的L-R事务如何跟异常处理器协同机制扩展类Sage的补偿模型。这章我们将看到L-R事务装备自己的补偿块进一步扩展这个模型,以释放Sage定义的某些束缚。
当一个L-R事务配备了自己的补偿块,补偿模型从“缺省”模型改变到“定制”模型,在设计器,可以通过直接改变事务的Compensate属性到Custom,或者右键单击L-R scope,选择加入新的补偿块来做到。注意,跟异常处理器不同,补偿块只能有一个。
在定制模型,在确定的条件下你可以控制整个补偿步骤,你可以用任何你希望的顺序补偿已经完成的子事务。为做到这点,在补偿块中为每个你希望补偿的事务加入一个Compensate shape,当然,你也可以加入附加的逻辑,比如,你可以用Decision shape精确的决定基于当时的orchestration状态使用哪个补偿。
这个扩展模型的一个潜在问题是嵌套事务是否完成的不确定性。当一个L-R事务定制的补偿块被调用时,通常你不能很确切的知道哪个子事务是已经完成了的。这个问题实际上已经变得不是很大的问题了,事实上如果你尝试调用一个未完成的子事务的补偿块,不会发生任何事,补偿块并不会被执行,这不被视为一个异常,控制简单的转移到补偿块的下面部分。
有时,这并不足够,如果你需要显式的测试一个子事务是否已成功的完成了,可以使用XLANG/s 表达式的“succeeded ()”功能,这里需要一个单一参数,就是你需要测试的事务的identifier,返回的是个boolean值,表示事务是否已经完成,你能在decision shape中使用这个功能。事实上,Compensate shape内部就包含了一个用succeeded ()测试事务是否成功的if语句,在成功状态下调用XLANG/s 的‘compensate <tx> ()’的语句,这就是为什么你尝试用Compensate shape执行未完成事务的补偿块什么都不会发生的原因。
这里有个不是很重要的问题,如果你在Decision shape中使用succeeded()功能,然后在判断成功分支中使用Compensate shape调用这个事务的补偿块,Compensate shape本身内部就有个if语句判断这个事务是否完成,succeeded()将会被执行两次,这时可以简单的使用Expression shape,其中使用‘compensate <tx> ()’语句来调用相应的补偿块。
定制补偿有很多结果,比如,你为一个L-R的事务引入一个补偿块,而这个L-R事务也需要捕获异常,作为捕获到异常后的一个结果调用补偿。这种情况,在异常处理器中至少需要有一个Compensate shape,如果你补偿当前的L-R事务(L-R中调用自身的补偿块,Compensate shape不会判断本身事务是否完成,只有调用它的子事务的补偿块时才会判断子事务是否已经完成,并只会调用已经完成了的子事务的补偿块),定制的补偿块将被调用,而不是缺省补偿块。你需要在你的定制补偿块中添加Compensate shapes进行相关的补偿调用。
下面的图显示这个模型,在这种情况,我们在补偿块中按照内部事务的顺序调用补偿,而不是缺省的逆序。
图7:定制补偿 (A)
从另一个角度看,上面展示的模型有不必要的复杂性。Biztalk允许我们直接在异常处理器中调用补偿,所以,我们可以把compensation block中的内容放到异常处理器中来,compensation block也可以不再需要。
图8:定制补偿 (B)
如果你在当前L-R事务的补偿块中调用本事务的补偿块,biztalk将调用当前事务的缺省事务补偿块。这个功能很有用,如果你只是想在缺省补偿逻辑的基础上加些简单逻辑。如果一个L-R事务使用了定制补偿块,没有其他的方法调用到缺省的补偿块。
很奇怪,你可以在atomic的补偿块中调用它的缺省补偿块,在后台,atomic事务会试着去调用嵌套事务的补偿块,但是,atomic事务是不会有任何嵌套事务,所以这个调用不会产生任何动作。
你只能调用当前事务和直接子事务的补偿块,这个规则在异常处理器和补偿块中都起作用。你不能直接调用子事务的嵌套事务的补偿块。这样保持了这个模型的简洁,保证补偿总是被它的直接外层L-R事务管理。
这个模型的缺点是,你可以很容易的不止一次的调用同一个事务的补偿块。在运行时,biztalk服务器将保证每个事务最多被补偿一次。在大多数情况下,任何尝试超过一次补偿一个事务都会在编译阶段被捕获,但仅仅给出警告,orchestration仍然会被继续编译。在运行时,第二次或者并发的尝试去补偿一个已经补偿过的事务,biztalk将会跟尝试补偿没有完成的事务一样,这个补偿会被忽略。这一点没有显式的在XLANG/s表达式中反映出来。
9. Orchestration水平的补偿
一个orchestration能提供一个orchestration级别的事务,跟任何事务一样,你可以把orchestration的compensation model从“缺省”改变到“定制”,在这种情况,因为没有相应的orchestration的补偿块形状,orchestration的补偿块被作为orchestration设计器底部的一个tab显示。
Orchestration水平的补偿有时会令biztalk开发者感到困惑,像任何补偿块一样,orchestration的缺省补偿块或定制补偿块仅能被外层的L-R事务的异常处理器调用。可是,明显的外层L-R事务不会是这个orchestration的一部分,在这种情况,这个orchestration应该是作为用Call shape调用这个orchestration的父orchestration的一部分。注意当使用Start shape直接调用orchestration的情况不适用。(应该还可以在异常处理器中调用自身的补偿块,测试Orchestration的默认异常处理器调用自身定制补偿块的情况)
10. 事务中的数据处理
一个atomic事务在orchestration状态管理方面支持ACID特性。它定义了orchestration中的子单元,它要么全部完成要么全部失败,它导致了在两个状态之间的一致性转换,它将把相关orchestration状态跟过程中的其它部分隔离,它保证所有更改的状态在事务被提交前持久化到磁盘。
另外,atomic事务可以通过自动登记分布式事务(DTC)来扩展它的边界,为充分利用这一特性,在atomic范围内你声明和实例化一个从System.EnterpriseServices.ServicedComponent继承的对象,BizTalk总是视ServicedComponent的类为非序列化的,即使它们被申明为serialisable(ServicedComponent基类自身就被标记为serialisable),因此你只能在atomic事务内部声明ServicedComponent类型的变量。
为了支持ACID特性,atomic事务复制相应的orchestration状态,在事务内部会工作在状态数据的副本之上,比如,一个orchestration的变量,消息或者相关集在外层事务被声明,然后在atomic事务中使用,如果这个atomic事务成功完成,那么在atomic事务中被改变的状态数据会更新的原始状态数据,然后这些改变被提交。对于每个成功的事务(atomic和L-R事务),biztalk保存完成时的orchestration的状态。对于atomic事务,当然就是提交时的状态。如果这个事务以后要被补偿,这些状态数据就是补偿的依据。补偿事务,它们自身是L-R事务。关于状态管理它们不支持ACID特性,这导致一个问题,如果你想在orchestration代码中使用非序列化的.net类型,你必须在atomic scope范围内声明这些类型的orchestration变量,这些变量不能在补偿事务中使用。如果你试着引用这样的变量,你将得到一个编译时错误。Biztalk使用序列化来捕获提交的atomic事务的状态,然后将这些状态提供给补偿块,所以,不能给补偿块提供非序列化的数据。
有一个更进一步的限制,补偿事务不是atomic的,发生在跟它相关联的事务被成功完成后。因此,它们不能登记到atomic事务,也不能跟外部的DTC事务交互,因此它们也不能跟在关联的atomic事务中声明的ServicedComponent 类型的对象交互。这实际上是XLANG/s规范的一部分,但是是通过biztalk视ServicedComponent 类为非序列化的来强制实现的。补偿事务不能引用非序列化类。
你可以在补偿块中任意嵌套事务,包括atomic事务,当一个补偿块抛出一个异常,正如你可能预期的,被传送到这个补偿块被调用的那个上下文,这可能是另一个补偿块,也可能是一个异常处理器,但最终,所有的补偿都开始于一个异常处理器。所有,一个异常将通过补偿块链传送到导致补偿开始的根异常处理器。如果这个根异常处理器是缺省异常处理器,异常会被简单的再次抛出。
原文:
BizTalk Server 2006: The Compensation Model