一、设置环境事务
默认情况下,服务类和操作没有环境事务,即使客户端事务传播到服务端也是如此。
尽管强制事务流从客户端传播过来,但服务端的环境事务依旧为null。为了启用环境事务,每个操作必须告诉WCF启用事务。为了解决这个问题,WCF提供了OperationBehaviorAttribute的TransactionScopeRequired属性:
// 指定服务方法的本地执行行为。 [AttributeUsage(AttributeTargets.Method)] public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior { //...... // 获取或设置一个值,该值指示方法在执行时是否需要事务环境。 public bool TransactionScopeRequired { get; set; } }TransactionScopeRequired的默认值为false。这就是为何默认情况下服务没有环境事务的原因了。将TransactionScopeRequired设置为true,将会为操作启用环境事务。
public class MyService : IMyService { [OperationBehavior(TransactionScopeRequired=true)] public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction != null); } }如果客户端事务传播给服务,WCF会把客户端事务作为操作的环境事务。如果没有传播,WCF会为操作创建一个新的事务,并把此事务视为操作的环境事务。
服务类构造函数没有事务,他不能参与到客户端事务里,所以不能让WCF将它加入到事务域中来。除非你手动创建环境事务,否则不要在服务构造函数里执行事务代码,构造函数中的代码不会加入到客户端事务。
下图所展示的是服务使用的事务,这个事务是有绑定,操作契约属性来分配的:
在上图中,非事务客户端调用服务1,服务1的操作契约配置为TranscationFlowOption.Allowed(允许参与事务,如果调用方启用了事务,则参与),尽管绑定允许事务流传播,但是由于客户端没有包含事务,所以无法传播事务。服务1的操作配置需要事务域。因此,WCF为服务1创建了一个新的事务A。服务1随后调用其它三个服务。服务2使用的绑定也是允许事务流操作,而且操作契约强制需要客户端事务流。因为操作行为配置需要配置事务流,WCF把事务A设置为服务2的环境事务。服务3的绑定和操作契约不允许事务流传播。但是,因为服务3的操作需要事务域,WCF为此创建了一个新的事务B,并作为服务3的环境事务。与服务3类似,服务4的绑定与操作也不允许事务流 传播,但是服务4不需要事务域,所以WCF不会给它创建环境事务。
二、事务传播模式
服务使用哪个事务取决于绑定的事务流属性(两个值)、操作契约事务(三个值),以及操作行为的事务域(两个值)的设置。下表列出了可行的八种组合:
八种组合实际产生四种有效的事务传播模式,即客户端/服务、客户端、服务和None。上表也列出了每种模式推荐的配置。每种模式在应用程序中都有用武之地。
1.客户端/服务事务模式
如果客户端启用了事务,则服务端就参与事务;如果客户端没有启用事务,则服务端独立启用事务。即不管客户端是否启用事务,服务端总是运行在事务中。
实现步骤:
a.选择一个支持事务的绑定,设置TransactionFlow = true。
b.在方法契约上添加TransactionFlowAttribute声明,设置TransactionFlow(TransactionFlowOption.Allowed)。
c.在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。客户端/服务事务模式是最解耦的配置,因为服务端尽量不去考虑客户端的工作。服务会在客户端事务流传播的时候允许加入客户端事务。
服务代码:
[ServiceContract] public interface IClientServiceTransaction { [OperationContract] //如果客户端启用了事务,则服务端参与事务 [TransactionFlow(TransactionFlowOption.Allowed)] void CSMethod(); }</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span><span style="color: #000000"> ClientServiceTransaction : IClientServiceTransaction { </span><span style="color: #008000">//</span><span style="color: #008000">服务端代码必须置于服务中执行</span> [OperationBehavior(TransactionScopeRequired = <span style="color: #0000ff">true</span><span style="color: #000000">)] </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> CSMethod() { </span><span style="color: #008000">//</span><span style="color: #008000">获取当前事务对象</span> Transaction transaction =<span style="color: #000000"> Transaction.Current; </span><span style="color: #008000">//</span><span style="color: #008000">注册事务流执行结束的事件方法</span> transaction.TransactionCompleted += <span style="color: #0000ff">new</span><span style="color: #000000"> TransactionCompletedEventHandler(transaction_TransactionCompleted); </span><span style="color: #008000">//</span><span style="color: #008000">显示事务的本地ID</span> Debug.WriteLineIf(transaction != <span style="color: #0000ff">null</span>, <span style="color: #800000">"</span><span style="color: #800000"><服务端>事务本地ID:</span><span style="color: #800000">"</span> +<span style="color: #000000"> transaction.TransactionInformation.LocalIdentifier); } </span><span style="color: #008000">//</span><span style="color: #008000">事务流执行结束时会触发此方法</span> <span style="color: #0000ff">void</span> transaction_TransactionCompleted(<span style="color: #0000ff">object</span><span style="color: #000000"> sender, TransactionEventArgs e) { </span><span style="color: #008000">//</span><span style="color: #008000">显示事务全局的ID</span> Debug.WriteLine(<span style="color: #800000">"</span><span style="color: #800000"><服务端>事务全局ID:</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.DistributedIdentifier); </span><span style="color: #008000">//</span><span style="color: #008000">显示事务的执行结果</span> Debug.WriteLine(<span style="color: #800000">"</span><span style="color: #800000"><服务端>事务执行结果</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.Status); } }</span></pre></div>服务配置代码:
<system.serviceModel> <services> <service name="WCF.Service.ClientServiceTransaction"> <endpoint address="ClientServiceTransaction" contract="WCF.Service.IClientServiceTransaction" binding="netTcpBinding"/> <endpoint address="ClientServiceTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="net.tcp://127.0.0.1:9000/"/> </baseAddresses> </host> </service> </services> <bindings> <netTcpBinding> <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004"> <reliableSession enabled="true"/> </binding> </netTcpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <diagnostics performanceCounters="All"/> </system.serviceModel>客户端代码:
class Program { static void Main(string[] args) { ClientServiceTransactionClient prox = new ClientServiceTransactionClient("NetTcpBinding_IClientServiceTransaction"); //第一次调用,客户端没有启用事务 prox.CSMethod(); using (TransactionScope ts = new TransactionScope()) { Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted); Console.WriteLine("<客户端>事务本地ID:" + Transaction.Current.TransactionInformation.LocalIdentifier); //第二次调用事务,客户端启用了分布式事务 prox.CSMethod();</span><span style="color: #008000">//</span><span style="color: #008000">事务提交。如果提交,事务流会执行成功;如果不提交的话,事务流会回滚。 </span><span style="color: #008000">//</span><span style="color: #008000">ts.Complete();</span>}
Console.ReadLine();
}</span><span style="color: #008000">//</span><span style="color: #008000">事务流执行结束时会触发此方法</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Current_TransactionCompleted(<span style="color: #0000ff">object</span><span style="color: #000000"> sender, TransactionEventArgs e) { Console.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000"><客户端>事务全局ID:</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.DistributedIdentifier); Console.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000"><客户端>事务执行结果:</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.Status); } }</span></pre></div>运行结果:
结论:
从图中我们看到:
1.服务端第一次执行成功,事务有本地ID,但没有全局ID。这说明虽然客户端调用没有启用事务,但服务端代码仍在事务中运行,该事务是服务端本地事务。
2.客户的全局事务ID与服务端的全局事务ID相同,这说明客户端与服务端处于同一个事务流中。但二者本地的事务ID不同,这说明它们各自是处于全局“大事务”中的本地“小事务”。由此得出,服务端代码仍在事务中运行,并参与到客户端事务流中
3.由于客户端代码中ts.Complete()被注释了,所以客户端事务执行不会提交,从而导致服务端事务提交失败,全局事务流提交失败。总结:
Client/Service事务模型是最常见的,它对客户端准备了“两手应对策略”:
当客户端启动了事务流的话,服务就参与事务流中,以维持整个系统的一致性,这样客户端的操作和服务端的操作会做为一个整体进行操作,任何一处的操作产生异常都会导致整个事务流的回滚。
如果客户端没有启动事务流,服务端的操作仍需要在事务的保护下运行,它会自动启动事务保护。2.客户端模式
Client事务模型必须由客户端启动分布式事务,并强制服务端必须参与客户端事务流。
当服务端必须使用客户端事务且设计上不能单独工作时,应该选用客户端事务模式。使用这个模式的主要目的就是为了最大化地保持一致性,因为客户端与服务端的操作需作为一个原子操作。还有一个重要原因,即服务共享客户端的事务可以降低死锁的风险,因为所有被访问的资源都会加载到相同的事务里。这意味着事务不可能再访问相同的资源和事务锁。
实现步骤:
a.选择一个支持事务的Binding,设置 TransactionFlow = true。
b.在方法契约上添加TransactionFlowAttribute声明,设置TransactionFlow(TransactionFlowOption.Mandatory)。
c.在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。服务代码:
/// <summary> /// 客户端模式 /// </summary> [ServiceContract] public interface IClientTransaction { [OperationContract] //强制把当前事务加入客户端事务流中 [TransactionFlow(TransactionFlowOption.Mandatory)] void TestMethod(); } public class ClientTransaction : IClientTransaction { //服务端代码必须置于服务中执行 [OperationBehavior(TransactionScopeRequired = true)] public void TestMethod() { Transaction transaction = Transaction.Current; transaction.TransactionCompleted += new TransactionCompletedEventHandler(transaction_TransactionCompleted); Debug.WriteLineIf(transaction != null, "<服务端>事务本地ID:" + transaction.TransactionInformation.LocalIdentifier); } void transaction_TransactionCompleted(object sender, TransactionEventArgs e) { Debug.WriteLine("<服务端>事务全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier); Debug.WriteLine("<服务端>事务执行结果:" + e.Transaction.TransactionInformation.Status); }}</span></pre></div>服务配置代码:
<system.serviceModel> <services> <service name="WCF.ServiceForClient.ClientTransaction"> <endpoint address="ClientTransaction" contract="WCF.ServiceForClient.IClientTransaction" binding="netTcpBinding"/> <endpoint address="ClientTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="net.tcp://127.0.0.1:9000/"/> </baseAddresses> </host> </service> </services> <bindings> <netTcpBinding> <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004"> <reliableSession enabled="true"/> </binding> </netTcpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <diagnostics performanceCounters="All"/> </system.serviceModel> </configuration>客户端代码:
/// <summary> /// 客户端事务 /// </summary> class Program { static void Main(string[] args) { //客户端不启用事务,会产生异常 //ClientTransactionClient prox = new ClientTransactionClient(); //prox.Open(); //prox.TestMethod(); //prox.Close();</span><span style="color: #008000">//</span><span style="color: #008000">------------------------------------------ </span><span style="color: #008000">//</span><span style="color: #008000">客户端启用事务</span> ClientTransactionClient prox = <span style="color: #0000ff">new</span><span style="color: #000000"> ClientTransactionClient(); prox.Open(); </span><span style="color: #0000ff">using</span> (TransactionScope ts = <span style="color: #0000ff">new</span><span style="color: #000000"> TransactionScope()) { prox.TestMethod(); </span><span style="color: #008000">//</span><span style="color: #008000">获取当前事务对象</span> Transaction trans =<span style="color: #000000"> Transaction.Current; </span><span style="color: #008000">//</span><span style="color: #008000">注册服务结束事件方法</span> trans.TransactionCompleted += <span style="color: #0000ff">new</span><span style="color: #000000"> TransactionCompletedEventHandler(trans_TransactionCompleted); </span><span style="color: #008000">//</span><span style="color: #008000">显示事务本地ID;</span> Console.WriteLine(<span style="color: #800000">"</span><span style="color: #800000"><客户端>事务本地ID:</span><span style="color: #800000">"</span> +<span style="color: #000000"> trans.TransactionInformation.LocalIdentifier); </span><span style="color: #008000">//</span><span style="color: #008000">提交事务</span>ts.Complete();
}
prox.Close();Console.ReadLine(); } </span><span style="color: #008000">//</span><span style="color: #008000">事务结束触发的事件</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> trans_TransactionCompleted(<span style="color: #0000ff">object</span><span style="color: #000000"> sender, TransactionEventArgs e) { Console.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000"><客户端>事务全局ID:</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.DistributedIdentifier); Console.WriteLine(</span><span style="color: #800000">"</span><span style="color: #800000"><客户端>事务执行结果:</span><span style="color: #800000">"</span> +<span style="color: #000000"> e.Transaction.TransactionInformation.Status); } }</span></pre></div>运行结果:
如果客户端没有启用事务会产生异常信息
如果客户端启用事务,则客户端会和服务端会使用同一事务流
这种事务模式,服务端不能独立启动事务,服务端事务必须参与客户端事务流中,确保客户端和服务端处于同一事务流中,这样客户端和服务端的代码会做为一个原子执行,当事务流中任何一个环节产生异常都会把整个事务流进行回滚,实现非常好的一致性。
3.服务事务模型
这种事务模型是把服务端事务与客户端事务分离开,服务端代码执行总是会创建自己的事务,并不会参与到客户端事务中去。所以客户端的事务启用与否并不影响服务端事务的执行。
实现步骤:
a.可以使用任何绑定信道。如果选择的是支持事务的绑定,需要设置TransactionFlow = false,因为服务端事务独立启动,并不需要事务流。
b.不需要在方法契约上添加TransactionFlowAttribute声明。如果你非得设置此Attribute的话,请设置TransactionFlow(TransactionFlowOption.NotAllowed),指明服务端拒绝参与事务流。
c.在方法行为上声明OperationBehavior(TransactionScopeRequired=true)。指明服务端需要自己启用事务。当服务端工作不需要在客户端事务范围内完成时,可以选择服务端事务模式(连例如,日志或审计操作,或者不论客户端事务提交成功或终止,都像将事件给服务端)。
例如,我们编写一个记录操作日志的服务,客户端所做的每一步操作都需要调用服务端的方法进行记录。这个日志记录服务端就适用于Service服务模型,不管客户端事务是否正常提交,服务端记录日志的事务不受影响。如果采取的是Client/Service事务模型或Client事务模型的话,当客户端事务没有提交成功,会导致服务端日志无法正常记录。
服务代码:
/// <summary> /// 服务端事务模式 /// </summary> [ServiceContract] public interface IServiceTransaction { [OperationContract] //禁用事务流功能 [TransactionFlow(TransactionFlowOption.NotAllowed)] void TestMethod(); } public class ServiceTransaction : IServiceTransaction { //服务端代码必须置于服务中执行 [OperationBehavior(TransactionScopeRequired = true)] public void TestMethod() { Transaction transaction = Transaction.Current; transaction.TransactionCompleted += new TransactionCompletedEventHandler(transaction_TransactionCompleted); Debug.WriteLineIf(transaction != null, "<服务端>事务本地ID" + transaction.TransactionInformation.LocalIdentifier); } void transaction_TransactionCompleted(object sender, TransactionEventArgs e) { Debug.WriteLine("<服务端>事务全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier); Debug.WriteLine("<服务端>事务执行结果:" + e.Transaction.TransactionInformation.Status); } }服务配置代码:
<system.serviceModel> <services> <service name="WCF.ServiceForService.ServiceTransaction"> <endpoint address="ServiceTransaction" contract="WCF.ServiceForService.IServiceTransaction" binding="netTcpBinding"/> <endpoint address="ServiceTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/> <host> <baseAddresses> <add baseAddress="net.tcp://127.0.0.1:9000/"/> </baseAddresses> </host> </service> </services> <bindings> <netTcpBinding> <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004"> <reliableSession enabled="true"/> </binding> </netTcpBinding> </bindings> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <diagnostics performanceCounters="All"/> </system.serviceModel>客户端代码:
public static void Main(string[] args) { ServiceTransactionClient prox = new ServiceTransactionClient(); prox.Open(); using (TransactionScope ts = new TransactionScope()) { prox.TestMethod(); Transaction trans = Transaction.Current; trans.TransactionCompleted += new TransactionCompletedEventHandler(trans_TransactionCompleted); Console.WriteLine("<客户端>事务本地ID:" + trans.TransactionInformation.LocalIdentifier); //ts.Complete(); } prox.Close(); Console.ReadLine(); } static void trans_TransactionCompleted(object sender, TransactionEventArgs e) { Console.WriteLine("<客户端>事务全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier); Console.WriteLine("<客户端>事务执行结果:" + e.Transaction.TransactionInformation.Status); }运行结果:
结论:
从图中我们可以看到:
服务端本地事务ID不为空,说明服务端代码仍处于事务中执行。但全局事务ID都是空的,说明这种事务模型不存在全局型事务流。
还管客户端事务执行是否成功,服务端事务总是成功执行。总结:
这种事务模型一般应用于:当服务端需要在客户端事务流之外独立运行事务的情况。4.None事务模式
这种种事务模型中服务端代码不启用任何事务也不参与任何事务流。
实现步骤:
a.可以使用任何绑定信道。如果选择的是支持事务的绑定,需要设置TransactionFlow = false,因为服务端事务独立启动,并不需要事务流。
b.不需要在方法契约上添加TransactionFlowAttribute声明。如果你非得设置此Attribute的话,请设置TransactionFlow(TransactionFlowOption.NotAllowed),指明服务端拒绝参与事务流。
c.不需要在方法行为上添加TransactionScopeRequired属性声明,如果你非要设置此属性的话,请设置OperationBehavior(TransactionScopeRequired=false)。指明此方法执行过程中不使用事务。即默认情况下,服务是不使用事务的。
None事务模式允许开发非事务型服务,它可以被事务型客户端调用。
显然这种事务模型很危险,它破坏了系统的一致性:如果客户端在执行过程中产生异常时,将不会回滚服务端的数据。一般这在一些不会出现不一致性的系统中采用这种模型。
事务模型的选择
上面所述的四种事务模型中,Service模型和None模型用得比较少,这两种模式带系统不一致的潜在危险。一般在需要事务流控制的地方,我们应选择Client/Service模型或Client模型来确保客户端和服务端之间系统一致。
三、实例代码下载
点击下载
- 相关阅读:
CentOS7 命令笔记
MarkDown学习
系统管理员资源大全
解决回车键导致的页面无意义刷新
Tomcat远程调试
gson 自定义对象转换格式
maven私服搭建
大明最不该被遗忘的英烈——李定国
HashMap实现原理分析(转)
自定义评分器Similarity,提高搜索体验(转)
- 原文地址:https://www.cnblogs.com/zxj159/p/4040332.html
- 最新文章
android cocos2d-x for Android安装和学习笔记(请用adt-bundle21.1或以上导入)
android opengl
android 图片缓存
android 文字图片合成
android 动画
android 多点
Azure China (14) 使用Azure China SAS Token (2)
Azure VMSS (1) VMSS增加Alert
Linux学习 (2) CentOS 6 虚拟机挂载磁盘
Azure CosmosDB (14) 使用Postman访问CosmosDB REST API
- 热门文章
Azure EA (3) 使用Postman访问海外Azure Billing API
Azure EA (2) 使用Postman访问国内Azure Billing API
Azure EA (1) 查看国内Azure账单
Windows Azure Virtual Machine (39) 清除Linux挖矿病毒
Windows Azure Virtual Machine (38) 跨租户迁移使用托管磁盘的Azure虚拟机
Azure Automation (7) 执行Azure SQL Job
微信小程序WebSocket报错:Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
反向代理WebSocket连接自动断掉的问题
org.springframework.web.util.WebUtils.isSameOrigin(WebUtils.java:816)
Nginx反向代理WebSocket






