今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程。众所周知,应用系统开发过程中,事务是一个重要的概念。它是保证数据与服务可靠性的重要机制。 作为面向服务应用的开发平台,WCF也提供了对事物编程模型的支持。.NET 2.0提供的System.Transactions类来开发事务应用程序。同样WCF也支持事务特性,WCF事务机制是什么,它与微软已有的技术如Microsoft 分布式事务协调器 (MSDTC)有何关系?与Enterpise Services(微软应用程序服务器技术)和COM+的事物机制有何区别,这篇文章会详细介绍,今天我们就来学习一下WCF事务处理机制和如何在项目中使用这一特性。全文的结构【1】事务概念 【2】事务属性、【3】事务协议、【4】事务管理器、【5】事务编程、【6】示例代码讲解、【7】总结。
WCF提供的事务机制。其实除了利用已有的.NET 框架提供的事务机制外,还根据自身需求进行了扩展。这个问题不难理解。WCF编程模型告诉我们,WCF的应用通常包含客户系统、WCF服务系统。除了可以借助SQL Server等RDBMS内部的事务机制来实现事务以外,还可以使用NET 2.0提供的System.Transactions类来实现事务处理。这种事务仅仅存在于服务端或者客户端。而WCF要求通常要实现客户端与多个服务端之间操作的事务约束,也就是通常所说的分布式事务。WCF没有完全重新开发一套框架来实现分布式事务。这里它借助了微软早期的技术MSDTC分布式事务协调器来实现的分布式事务。下面我们也会介绍。
首先我们来回顾一下事务的感念:
【1】事务概念 :
什么是事务呢?其实这个事一个数据库系统中的一个概念。事务(Transaction)是并发控制的基本单位。所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
例如,银行转帐:通常包括两个操作:
(1)从一个帐号A扣款;
(2)使另一个帐号B增款。
这两个操作要么都执行,要么都不执行。在银行系统里,数据库系统执行相关的命令来完成两个操作。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保证数据一致性。连个账号的金额不会出现错误。保证转账操作的正确完成。
【2】事务属性:
事务也有自己的特性。这个大家都非常的熟悉。相信每个数据库相关的书籍都会介绍事务的特性ACID。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性:
<1>原子性:
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。一个事务要被完全的无二义性的做完或撤消。在任何操作出现一个错误的情况下,构成事务的所有操作的效果必须被撤消,数据应被回滚到以前的状态。比如转账事务中的两个操作,要么全执行,要么全部执行。
<2>一致性:
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,A账户里10000元,B账户0元,转账成功以后。A和B账户的总额保持不变。还是10000元。不会因为转账成功就增加了总额。
<3>隔离性:
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。串行执行事务:在一个事务执行过程中,数据的中间的(可能不一致)状态不应该被暴露给所有的其他事务。
两个并发的事务应该不能操作同一项数据。数据库管理系统通常使用锁来实现隔离。
<4>持久性:
WCF 支持分布式事务,也就是说事务可以跨越服务边界、进程、机器、网络,在多个客户端和服务之间存在。而与此对应的事务数据信息传播和管理的协议不同。
【3】事务协议:
WCF 使用不同的事务协议来控制事务执行范围(execution scope)。 事务协议的出现时为了实现分布式环境事务传播。
1).Lightweight: 仅能在同一程序域的上下文中传递事务,无法跨越程序域和服务边界。只能在服务内部或外部适用,同时它也是性能最好的一种协议。不过这种协议似乎没什么用处,因为 WCF Framework 中没有任何一种 Binding 支持此协议。
2).OleTx: 允许事务跨越程序域、进程或机器边界。使用 RPC 调用,采取 Windows 专用二进制格式。无法跨越防火墙,也不能和其他异种平台进行整合。多用于 Windows 体系的 Intranet 环境。
3).WS-Atomic(WSAT): 和 OleTx 相似,同样允许事务跨越程序域、进程或机器边界。和 OleTx 不同,WSAT 是一种工业标准,采取 HTTP 协议,TEXT 编码,可以跨越防火墙。虽然 WSAT 也能用于 Intranet,但多数时候它用于 Internet 环境。
事务协议的配置只有在事务传播的情况下才有意义。WCF在预定义绑定中实现了标准的WSAtomicTransaction(WS-AT)协议和Microsoft专有的OleTx协议,这些协议可以用来在消息中加入事务状态的信息。WS绑定可以使用多个WSAT事务协调器,跨越Internet。但是如果只有一个事务协调器,OleTx协议将是默认的协议。我们可以编程或者配置文件设置事务协议。
<
bindings
>
<
netTcpBinding
>
<
binding name
=
"
TransactionalNetTCP
"
transactionFlow
=
"
true
"
transactionProtocol
=
"
WSAtomicTransactionOctober2004
"
/>
</
netTcpBinding
>
</
bindings
>
【4】事务管理器:
分布式事务的实现要依靠第三方事务管理器来实现。他负责管理个个事务的执行情况。最后根据全部的事务执行结果,决定提交或者回滚整个事务。这个也就是通常所说的两阶段提交协议。通常来收事务管理器有3种:LTM、KTM、DTC。他们应用的场合不同。由于事务有本地和分布式事务的区别。所以三种协议使用的场合也不相同。下面依次详细介绍:
【4.1】LTM:轻量级事务管理器,它只能管理本地事务,单个应用程序域中的事务,它根据轻量级事务协议来管理和实现两阶段提交协议。 LTM是一种高效的资源管理器。它只能管理本地事务。在.NET2.0中经常使用。WCF事务编程中我们可以使用其来管理本地事务。
【4.2】KTM:在Vista核心中的新组件,其目的是方便进行大量的错误恢复工作,而且过程几乎是透明的,而KTM之所以可以做到这一点,是因为它可以作为事务客户端接入的一个事务管理器进行工作。与LTM一样, KTM只能管理一个本地服务的事务。而且不支持事务传播给别的服务。
【4.3】DTC:.NET Framework 依靠 MTS/COM+ 服务来支持自动事务。COM+ 使用 Microsoft Distributed Transaction Coordinator (DTC) 作为事务管理器和事务协调器在分布式环境中运行事务。分布式事务协调器 (DTC) 服务可协调更新两个或多个受事务保护的资源的事务, 如数据库、消息队列、文件系统等等。这些受事务保护的资源可能位于单个计算机上,或分布在许多网络计算机上。DTC可以使用OleTx或者WSAT协议。WCF可以借助DTC实现分布式事务机制。DTC可以创建事务、传播事务信息、收集全部事务的结果、通知事务管理器提交或者回滚事务。
在分布式事务中,事务管理器A会向参与事务的其他机器发出调用请求。其它机器拦截请求。获取事务ID,启动本地事务。其他机器同时启动本地资源管理器登记。执行两阶段提交协议。最后根据全部的结果。执行第二阶段是否提交和回滚。DTC管理分布式事务如图所示:
事务资源管理器会根据事物执行的实际情况和需求进行提升。最初的事务由LTM管理,这样能获得最好的性能;
如果事务访问的是KRM资源,开始会由KTM管理事务。当事务访问其它持久化资源或者其它事务并传播事务时,事务就会提升为DTC事务。事务的如果为旧资源,管理器会自动提升为DTC,资源与事务管理器之间的关系如下表:
Resource
|
LTM
|
KTM
|
DTC
|
Volatile
|
Yes
|
Yes
|
Yes
|
SQL Server 2005
|
Yes
|
No
|
Yes
|
Kernel
|
No
|
Yes
|
Yes
|
Any other RM
|
No
|
No
|
Yes
|
本地事务ID和分布式事务ID都可以通过事务类的属性TransactionInformation获得。string LocalIdentifier和Guid DistributedIdentifier。
【5】事务编程:
下面我们来介绍一下WCF事务编程。WCF事务范围可以涉及到客户端、服务端。当然这个取决于你项目具体的配置。在WCF的事务模式主要由绑定协议、事务流属性、事务范围属性决定。在WCF所有的绑定协议中不是所有的协议都支持事务。事务流属性TransactionFlowAttribute 只能用于服务方法(Operation/Method)上,它允许我们进行不同的事务参与设置。注意不能为 IsOneWay=true 的服务设置事务流支持:
TransactionFlowOption.NotAllowed: 不参与任何事务。(默认值)
TransactionFlowOption.Allowed: 允许参与事务。如果调用方(客户端)和服务Binding启用了事务,则参与。
TransactionFlowOption.Mandatory: 强制启用事务。调用方(客户端)和服务 Binding 必须启用事务才能调用本服务。
这样综合作用,匹配的结果就是3种启动事务的模式。分别是:Client/Service transaction、Client transaction、Service transaction模式。他们分别的设置情况是:
【5.1】. Client/Service transaction,最常见的一种事务模型,通常由客户端或服务本身启用一个事务。设置步骤:
(1) 选择一个支持事务的Binding,设置 TransactionFlow = true。
(2) 设置 TransactionFlow(TransactionFlowOption.Allowed)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
【5.2】. Client transaction,强制服务必须参与事务,而且必须是客户端启用事务。设置步骤:
(1) 选择一个支持事务的Binding,设置 TransactionFlow = true。
(2) 设置 TransactionFlow(TransactionFlowOption.Mandatory)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
【5.3】. Service transaction,服务必须启用一个根事务,且不参与任何外部事务。设置步骤:
(1) 选择任何一种Binding,设置 TransactionFlow = false(默认)。
(2) 设置 TransactionFlow(TransactionFlowOption.NotAllowed)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
【6】示例代码讲解:
示例代码这里测试的服务包括两个服务。服务分别有一个操作访问事务资源。定义一个宿主托管服务。一个客户端。客户端调用操作启动事务,服务事务模式。
【6.1】服务端:
服务模式的实现代码如下:首先是服务契约的定义:
//
1.服务契约
[ServiceContract(Namespace
=
"
http://www.cnblogs.com/frank_xl/
"
,SessionMode
=
SessionMode.Required)]
public
interface
IWCFServiceTransaction1
{
//
操作契约
[OperationContract]
//禁止
事务流,使用服务事务
//
TransactionFlowAttribute t = new TransactionFlowAttribute(
[TransactionFlow(TransactionFlowOption.NotAllowed)]
bool
AddDataUsingAdapter(
string
name);
//
操作契约
}
服务类的代码实现如下:
//
2.服务类,实现契约
//
事务有30分钟的超时限制
//
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
[ServiceBehavior(IncludeExceptionDetailInFaults
=
true
, TransactionTimeout
=
"
00:30:00
"
,InstanceContextMode
=
InstanceContextMode.PerCall)]
public
class
WCFServiceTransaction1 : IWCFServiceTransaction1
{
//
实现接口定义的方法
//
需要事务环境,启动事务
[OperationBehavior(TransactionScopeRequired
=
true
)]
//
, TransactionAutoComplete = true)]
public
bool
AddDataUsingAdapter(
string
name)
{
Transaction transaction
=
Transaction.Current;
//
断言是一个本地事务
Debug.Assert(System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier
==
Guid.Empty);
//
输出事务信息
Console.WriteLine(
"
Create a new Transaction at {0}
"
, System.Transactions.Transaction.Current.TransactionInformation.CreationTime);
Console.WriteLine(
"
WCFService 1 Transaction Status is {0}
"
, System.Transactions.Transaction.Current.TransactionInformation.Status);
Console.WriteLine(
"
WCFService 1 Transaction LocalIdentifier is {0}
"
, System.Transactions.Transaction.Current.TransactionInformation.LocalIdentifier);
Console.WriteLine(
"
WCFService 1 Transaction DistributedIdentifier is {0}
"
, System.Transactions.Transaction.Current.TransactionInformation.DistributedIdentifier);
//////
启动事务
using
(System.Transactions.TransactionScope ts
=
new
System.Transactions.TransactionScope())
{
try
{
userTableAdapter adapter1
=
new
userTableAdapter();
//
第1次增加数据
adapter1.CreateUser(name);
Console.WriteLine(
"
Calling WCF Transaction{0} length is:{1}
"
, name, name.Length);
//////
第2次增加数据
//
userTableAdapter adapter2 = new userTableAdapter();
//
adapter2.CreateUser(name);
//
Console.WriteLine("Calling WCF Transaction{0} length is:{1}", name, name.Length);
if
(name.Length
>
8
)
throw
new
Exception(
"
Name is too long,it should be less than 10
"
);
}
catch
(Exception e)
{
System.Transactions.Transaction.Current.Rollback();
Console.WriteLine(
"
Calling WCF Transaction error:{0}
"
, e.Message);
//
throw e;
return
false
;
}
ts.Complete();
//
OperationContext.Current.SetTransactionComplete();
return
true
;
}
}
}
这里把操作行为要设置为 [OperationBehavior(TransactionScopeRequired = true)],需要事务支持。其次是认为增加一个判断name长度的语句,超过8就抛出异常, if (name.Length > 8)
throw new Exception("Name is too long,it should be less than 10...");把刚才的插入操作回滚, System.Transactions.Transaction.Current.Rollback();不提交。
【6.2】宿主:
我们采用托管宿主,这里主要需要配置的就是服务超时时间限制,还有事务流选项,默认即可,使用代码实现也可以。
具体代码如下:
<
system.serviceModel
>
<
services
>
<
service behaviorConfiguration
=
"
WCFService.WCFServiceBehavior
"
name
=
"
WCFService.WCFServiceTransaction1
"
>
<
endpoint
bindingConfiguration
=
"
netTcpBindingTcp
"
address
=
"
net.tcp://localhost:9001/WCFServiceTransaction1
"
binding
=
"
netTcpBinding
"
contract
=
"
WCFService.IWCFServiceTransaction1
"
>
</
endpoint
>
<
endpoint address
=
"
mex
"
binding
=
"
mexHttpBinding
"
contract
=
"
IMetadataExchange
"
/>
<
host
>
<
baseAddresses
>
<
add baseAddress
=
"
http://localhost:8001/WCFServiceTransaction1
"
/>
</
baseAddresses
>
</
host
>
</
service
>
</
services
>
<
behaviors
>
<
serviceBehaviors
>
<
behavior name
=
"
WCFService.WCFServiceBehavior
"
>
<
serviceMetadata httpGetEnabled
=
"
true
"
/>
<
serviceDebug includeExceptionDetailInFaults
=
"
true
"
>
</
serviceDebug
>
<
serviceTimeouts transactionTimeout
=
"
00:30:00
"
/>
</
behavior
>
</
serviceBehaviors
>
</
behaviors
>
<
bindings
>
<
netTcpBinding
>
<
binding name
=
"
netTcpBindingTcp
"
transactionFlow
=
"
false
"
>
</
binding
>
</
netTcpBinding
>
</
bindings
>
</
system.serviceModel
>
【6.3】客户端:
客户端代码主要是添加服务引用的代码就不做详细描述,大家都能明白这个过程。主要是测试代码。进行了两次测试。允许用户输入姓名,代码如下:
try
{
//
正常保存。10位以内。.超过长度就会出错
Console.WriteLine(
"
input name please
"
);
string
name
=
Console.ReadLine();
wcfServiceProxy1.AddDataUsingAdapter(name);
Console.WriteLine(
"
{0} length is :{1}
"
, name, name.Length);
}
catch
(Exception ex)
{
//
出现异常就就通知其他事务资源管理器回滚
//
System.Transactions.Transaction.Current.Rollback();
Console.WriteLine(
"
Exception Message1 is : {0}
"
, ex.Message);
//
throw ex;
}
//////////////////////////////////////////
测试2
//////////////////////////////
//
try
{
///
正常保存。10位以内.超过长度就会出错
Console.WriteLine(
"
input name please
"
);
string
name
=
Console.ReadLine();
wcfServiceProxy1.AddDataUsingAdapter(name);
Console.WriteLine(
"
{0} length is :{1}
"
, name, name.Length);
}
catch
(Exception ex)
{
Console.WriteLine(
"
Exception Message1 is : {0}
"
, ex.Message);
//
throw ex;
}
分别进行了测试,首先输入“FrankXu”长度7,提交新增事务,保存数据成功。其次是输入“FrankXuLei”,长度是10,抛出异常,事务回滚,取消刚才的插入操作。执行的结果如下图:
【7】总结:
事务编程是WCF中一个重要的概念。WCF借助已有的技术System.Transactions可以实现本地事务的编程。而分布式事务则是借助MSDTC分布式事务协调机制来实现。事务也可跨多个数据资源。 使用分布式事务可以将在不同系统上执行的多种不同的操作合并到一个通过或失败的操作中。 在这种情况下,事务由位于每个系统中的 Microsoft 分布式事务协调器 (MSDTC) 进行协调。 在WCF使用 System.Transactions 所提供的类开发事务应用程序时,不必考虑需要使用哪种事务,也不必考虑所涉及的事务管理器。 System.Transactions 基础结构会为WCF自动管理这些事宜。我们可以隐式编程实现事务或者显式编程控制事务。
(1)事务的级别提升和状态管理由事务管理器来实现,WCF很好地与LTM、KTM、MSDTC事务管理器结合,实现了自己需要的事务模型。
(2)WCF提供了支持事务传播的绑定协议包括:WSHttpBinding、WSDualHttpBinding、WSFederationBinding、NetTcpBinding和NetNamedPipesBinding。最后两个绑定允许选择WS-AT协议或OleTx协议,而其他绑定都只使用标准的WS-AT协议。
(3)在此次准备事务编程示例代码的过程中,查阅了不少资料。发现很少有完整的可以执行的代码。WCF学习,实践动手能力必不可少,我在调试代码的过程中遇到很多问题,自己也查阅了资料,并整理响应的解决办法。大家可以再另外一个系列 WCF分布式开发常见错误里看到,我也放到 微软WCF中文技术论坛了,大家也可以查阅。一起交流~
(4)本来计划些个最全面的示例代码,包括2个服务,连个宿主。来测试客户端/服务端事务模式。但是调试过程有很多问题。目前代码可以执行,但是有些潜在的异常,还在调试。我会在后续文章里给放出来吧。
最后上传本文的示例代码 /Files/frank_xl/WCFServiceTransactionDemoFrankXuLei.rar,请大家参考学习。有问题继续交流。
参考文章:
1. WCF分布式开发必备知识(3):Enterpise Services;
2.Sql Server数据库事务介绍(一)---什么是事务: http://blog.csdn.net/tjvictor/archive/2009/04/14/4074326.aspx;
3.什么是事务:http://zhidao.baidu.com/question/95477134.html;
4.《WCF揭秘》;
5.《Programming WCF Services 》;
6.[WCF Transaction] 1. 基本概念, http://hi.baidu.com/feiji123/blog/item/3390d2196779be7ddbb4bd4f.html