WCF4.0进阶系列—第九章 事务支持(下)
本章上篇内容:WCF4.0进阶系列—第九章 事务支持(上)
【正文】
设计支持事务的WCF服务
本章前面小节的内容为你展示了在WCF服务中如何实现事务,但是在设计支持事务的WCF服务时,仍然有许多问题值得你关注。
事务、会话和服务实例模式
如果你在WCF服务中在一个或多个操作上设置OperationBehavior特性类的TransactionAutoComplete属性值为false,那么你必须设置服务实例模式为PerSession。这是因为WCF运行时需要在两次调用操作之间维护事务的状态。如果在每个操作上设置TransactionAutoComplete属性值均为true,WCF运行时则不必维护事务的状态,因为它在每个操作结束时完成当前的事务,此时你可以使用PerCall或者Single服务实例模式。
当你使用PerSession服务实例模式时,ServiceBehavior特性类为WCF提供两个额外的属性:
- ReleaseServiceInstanceOnTransactionComplete,如果该属性设置为true,那么在每个事务结束时,WCF运行时将自动地结束会话并回收服务实例。如果客户端程序再调用另外一个操作,它必须创建并连接至一个新的服务实例。设置该属性为false将允许一个会话处理多个事务。该属性的默认值为true。
- TransactionAUtoCompleteOnSessionClose,如果该属性设置为true,当客户端程序关闭会话时,WCF运行时将自动完成当前的事务。该属性的默认值为false,当客户端关闭会话时,事务将被抛弃,事务所做的工作将被还原。
从上述两点中,你应该记住重要的一点, 事务不能跨越多个会话。这在实践中意味着如果一个客户端发起一个事务,通过该事务向服务发送请求以执行一些工作,然后关闭该连接,那么该事务将终止。事务的结果取决于最后的操作是否已经成功的完成事务,或者调用SetTransactionComplete,或者在调用的操作上设置TransactionAutoComplete属性为true。如果未采用上述任何一种方式,那么事务将被抛弃,在事务中执行的工作都将回滚。
听起来,这似乎是一个缺点,但是实际上这非常有用。如果事务跨越多个会话存在,那么将不能保证一个事务曾经终止过,而且一个事务锁定的资源可能还保留了不确定的锁(其他事务可能也锁定了该资源)。
事务和消息传送
事务性操作把关于事务状态的信息回传至客户端程序。到目前为止所有定义的消息都遵循请求/响应模型;客户端程序发送请求,然后等待服务的响应。在第十二章"实现单向操作和异步操作"中你可以定义单向操作,这种类型的操作不需向客户端程序回传响应。请注意,单向操作不支持事务。
事务和多线程
在第七章中你已看到,如果设置ConCurrencyMode属性为Multiple,那么WCF服务可以实现对操作的多个并发调用。当你使用该模式时应当注意下面几点:
- 为服务中每个操作添加OperationBehavior特性,而且该特性类的TransactionAutoComplete属性必须设置为true。事务不能跨越多个操作。
- 为服务合约添加ServiceBehavior特性,而且该特性类的ReleaseServiceInstanceOnTransactionComplete属性必须设置为false。在客户端关闭连接时必须显示地释放服务实例。
- 为服务合约添加ServiceBehavior特性,而且该特性类的TransactionAutoCompleteOnSessionClose属性必须设置为true。当会话结束时所有线程上的事务必须终止。
在工作流服务中实现事务
WF在工具栏的消息模传送区域中提供TransactedReceiveScope活动,用以在工作流服务中实现事务性操作。该活动对外公开两个属性
- Request,该属性必须是一个Receive活动,该活动定义了服务接受的消息,其标志操作的开始
- Body,该属性包含一个活动,该活动定义了操作的逻辑。一般地,你通过顺序活动处理客户端请求的数据以实现该活动。该活动还包含了一个SendReply活动用以回传响应消息至客户端
下图展示了TransactedReceiveScope活动,该活动实现了产品服务中的ChangeStockLevel操作。该操作更新AdventureWorks数据库的产品的数量。该服务的代码本书源代码的Chapter9文件夹下的ProductsWorlflow方案中。请注意,Request属性包含一个Receive活动,该活动侦听请求ChangeStockLevel操作的消息;Body属性包含一个Sequence活动,该活动调用ChangeStockLevel自定义活动(实际上该活动修改库存数量);Body属性还包含一个SendReply活动,该活动发送更新后的结果至客户端(如果修改成功返回true,否则返回false)。
与非工作流WCF服务的定义方式和控制方式想对比,工作流服务中的事务性活动有根本性不同:
- 不能显示地指定工作流运行时如何完成事务(对于非工作流服务,没有TransactionAutoComplete特性的概念)。在工作流服务中,TransactedReceiveScope活动确定了操作的结果;如果Body属性中的Sequence活动定义的TransactedReceiveScope结束,并且没有发生任何异常,那么操作被认为成功的完成,并且当事务完成时进入提交过程。如果在Body属性中抛出了一个异常,事务将被抛弃。
- 在SendReply活动中事务性工作不会结束。在Body属性中定义的Sequence活动可以在发送响应结束后,包含其他的逻辑。这些逻辑中包含的工作也包括在同一个事务的范围内。只有当Sequence活动结束才能被事务性工作识别。请注意,这意味着工作流服务可能发送一个响应至客户端,然后执行其他一些可能失败的过程,然后再抛弃事务。但是在这种情况下,客户端程序可能并为立即获知事务失败,因此使用该特性时需加倍小心。
- 使用TransactedReceiveScope活动实现一个操作暗指TransactionFlowOption.Allowed。你不能指定一个操作必须在一个现存事务中执行(TransactionFlowOption.Mandatory);如果一个客户端程序没有使用事务调用操作,工作流运行时将自动创建一个事务。此外,该事务的默认隔离级别为Serializeable,尽管这将被客户端程序创建的事务范围中隔离级别覆盖。
你可以在工作流客户端程序中使用TransactionScope活动启动一个事务。该活动提供的属性可供你指定事务的隔离级别和超时。下图展示了一个简单的客户端工作流程序,该工作流调用ChangeStockLevel操作。
该工作流创建了一个TransactionScope活动,而该活动又包含一个顺序活动,然后在顺序活动中两次调用ChangeStockLevel操作。这两个操作都包含在同一个事务中。你可以在组件服务控制台中查看事务统计以确认这点。为了进行额外的检查,ChangeStockLevel自定义的活动将在控制台输出事务的详细信息。从下图中我们可以看到,两次调用ChangeStockLevel操作的事务ID相同。
长时间运行的事务
事务会锁定资源。为了减少对其他用户的影响,以及提高服务的高可用性和并发数量;应当设计运行时间尽可能短的事务,应在避免在事务执行过程中等待用户输入。尽管在本章中使用购物车服务演示了事务,但是其实现并不是最佳的实践(因为实际的情况与本章中的练习有较大的差别);如果用户使用之前章节创建的GUI客户端访问ShoppingCartService服务,那么事务可能持续相当长的时间(你不能控制用户何时调用Checkout操作),在这期间资源一直被锁定。
另外一个常见的场景就是B2B方案。企业间的事务持续很长一段时间(可能好几天),这是能经常见到的。如此长时间运行的事务你需要使用可替换的方案。常见的方案是:一个服务执行更新,然后立即释放相应的资源。这有效地将每一个更改做为一个单独事务。服务应当维持一个更改的列表。在后续的时间点,如果服务需要回滚这些操作,可以访问该列表,然后执行反向操作。该复原操作有时被称之为"补偿性事务"。WF提供的CompensableActivity活动就是为了实现该目的。
CompensableActivity活动提供一个Body属性,在该属性中你可以为长时间运行的任务定义工作流,比如等待客户端的输入,或者等待来自其他服务的消息。如果错误发生,你可以在CancellationHandler属性中定义补偿逻辑以复原Body属性中工作流所做的更改。如果Body属性中的工作流已经完成,你仍然可以在CancellationHandler属性中定义适当的逻辑复原更改。仅仅只有当CompensableActivity活动执行的工作被确认,那么所有的更改才当视为永久性更改。为了确认CompensableActivity活动执行的工作,你需要使用Confirm活动。
创建补偿性事务将会带来许多潜在问题。比如,如果其他用户在补偿性事务执行的期间内,已经对同一个数据做了进一步改变,它可能不可以恢复一个操作。此外,其他用户可以看到补偿性事务已经更改的数据,因次如果你复原这些操作,那么其他用户的事务可能产生某些不一致。
更多详细的信息,超出本书的讨论范畴,如果你有兴趣,请查阅MSDN http://msdn.microsoft.com/en-us/library/dd489432.aspx
【总结】
在本章,你已经看到如何在WCF客户端程序和服务中定义和控制事务。一个程序可以创建一个现存的事务列表,或者通过适当的参数实例化TransactionScope对象以创建一个新的事务。事务可以从客户端,通过网络,流向客户端。你可以通过ServiceBehavior特性类和OperationBehavior特性类在服务合约和服务的操作上指定事务的需求。在WCF服务的操作上通过执行OperationContext.Current.SetTransactionComplete方法以表明事务可以被提交。应用程序可以通过调用TransactionScope对象的Complete方法完成事务。
你了解到如何配置WCF服务和客户端程序,以在其收发的SOAP消息中包含关于事务的信息。你还了解到事务如何影响WCF服务的设计
最后,你还看到如何在工作流服务中使用TransactedReceiveScope活动实现事务。TransactedReceiveScope活动用以实现运行时间较短的事务。如果服务需要支持长时间运行的操作,那么应该使用定义了补偿逻辑的CompensableActivity活动,以避免长时间锁定资源。