谈谈分布式事务之三: System.Transactions事务详解[下篇]

在前面一篇给出的Transaction的定义中,信息的读者应该看到了一个叫做DepedentClone的方法。该方法对用于创建基于现有Transaction对 象的“依赖事务(DependentTransaction)”。不像可提交事务是一个独立的事务对象,依赖事务依附于现有的某个事务(可能是可提交事 务,也可能是依赖事务)。依赖事务可以帮助我们很容易地编写一些事务型操作,当环境事务不存的时候,可以确保操作在一个独立的事务中执行;当环境事务存在 的时候,则自动加入其中。

一、依赖事务(Dependent Transaction)

依赖事务通过DependentTransaction类型表示,DependentTransaction定义如下。和CommittableTransaction一样,DependentTransaction也是Transaction的子类。既然DependentTransaction依赖于现有的Transaction对象而存在,相当于被依赖事务的子事务,所以无法执行对事务的提交,也自然不会定义Commit方法。但是,DependentTransaction具有一个唯一的方法成员:Complete。调用这个方法意味着向被依赖事务发送通知,表明所有与依赖事务相关的操作已经完成。

   1: [Serializable]
   2: public sealed class DependentTransaction : Transaction
   3: {  
   4:     public void Complete();
   5: }

1、通过DependentTransaction将异步操所纳入现有事务

通过Transaction的 静态属性Current表示的环境事务保存在TLS(Thread Local Storage)中,所以环境事务是基于当前线程的。这就意味着,即使环境事务存在,通过异步调用的操作也不可能自动加入到当前事务之中,因为在异步线程 中感知不到环境事务的存在。在这种情况下,我们需要做的就是手工将当前事务传递到另一个线程中,作为它的环境事务。通过依赖事务我们很容易实现这一点。

DependentTransaction通过Transaction的DependentClone方法创建,该方法具有一个DependentCloneOption枚举类型的参数,体现了被依赖的事务再上尚未接受到依赖事务的通知(调用Complete或者Rollback方法)得情况下,提交或者完成所采取的事务控制行为。DependentCloneOption提供了两个选项,BlockCommitUntilComplete表示被依赖事务会一直等待接收到依赖事务的通知或者超过事务设定的超时时限;而RollbackIfNotComplete则会直接将被依赖的事务回滚,并抛出TransactionAbortedException异常。

   1: [Serializable]
   2: public class Transaction : IDisposable, ISerializable
   3: {       
   4:     //其他成员
   5:     public DependentTransaction DependentClone(DependentCloneOption cloneOption);
   6: }
   7: public enum DependentCloneOption
   8: {
   9:     BlockCommitUntilComplete,
  10:     RollbackIfNotComplete
  11: }

下面的代码演示了如果通过依赖事务,采用异步的方式进行银行转账操作。借助于组件ThreadPool,将主线程环境事务的依赖事务传递给异步操作代理,开始异步操作的时候将此依赖事务作为当前的环境事务,那么之后的操作将自动在当前事务下进行。

   1: private static void Transfer(string accountFrom, string accountTo, double amount)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction transaction = new CommittableTransaction();
   5:     try
   6:     {
   7:         Transaction.Current = transaction;
   8:         ThreadPool.QueueUserWorkItem(state =>
   9:         {
  10:             Transaction.Current = state as DependentTransaction;
  11:             try
  12:             {
  13:                 Withdraw(accountFrom, amount);
  14:                 Deposite(accountTo, amount);
  15:                 (state as DependentTransaction).Complete();
  16:             }
  17:             catch (Exception ex)
  18:             {
  19:                 Transaction.Current.Rollback(ex);
  20:             }
  21:             finally
  22:             {
  23:                 (state as IDisposable).Dispose();
  24:                 Transaction.Current = null;
  25:             }
  26:         }, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
  27:         //其他操作
  28:         transaction.Commit();
  29:     }
  30:     catch (TransactionAbortedException ex)
  31:     {
  32:         transaction.Rollback(ex);
  33:         Console.WriteLine("转帐失败,错误信息:{0}", ex.InnerException.Message);
  34:     }
  35:     catch (Exception ex)
  36:     {
  37:         transaction.Rollback(ex);
  38:         throw;
  39:     }
  40:     finally
  41:     {
  42:         Transaction.Current = originalTransaction;
  43:         transaction.Dispose();
  44:     }
  45: }

由于在调用DependentClone方法创建依赖事务时指定的参数为 DependentCloneOption.BlockCommitUntilComplete,所以主线程在调用Commit方法提交事务的时候,由于 依赖事务尚未结束(调用Complete或者Rollback方法),在这里会一直等待。如果依赖事务的Complete或者Rollback一直没有调 用,那么被依赖的事务会一直等到超出事务设置的超时时限。所以,对于基于BlockCommitUntilComplete选项创建的依赖事务来说,应该 及时地调用Complete或者Rollback方法。

2、通过DependentTransaction实现事务型方法

这里所说的事务型方法是指方法的执行总是在事务中执行。具体来讲,有两种不同的事务应用场景:如果当前不存在环境事务,那么方法的执行将在一个独立的事务中执行;反之,如果存在环境事务,在方法执行会自动加入到环境事务之中。

比如说,存储(Deposit)和提取(Withdraw)就是典型的事务型操作。对于单纯的存取款的场景,应该创建一个新的事务来控制存储和提取 操作的执行,以确保单一帐户款项的数据一致性。如果在转账的场景中,应在在转账开始之前就创建一个新的事务,让提取和存储的操作自动加入到这个事务之中。

我们现在就结合可提交事务和依赖事务将Deposit和Withdraw两个方法定义成事务型方法,为了相同代码的重复,在这里把事务控制部分定义在如下一个InvokeInTransaction静态方法中:

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction committableTransaction = null;
   5:     DependentTransaction dependentTransaction = null;
   6:     if (null == Transaction.Current)
   7:     {
   8:         committableTransaction = new CommittableTransaction();
   9:         Transaction.Current = committableTransaction;
  10:     }
  11:     else
  12:     {
  13:         dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.RollbackIfNotComplete);
  14:         Transaction.Current = dependentTransaction;
  15:     } 
  16:  
  17:     try
  18:     {
  19:         action();
  20:         if (null != committableTransaction)
  21:         {
  22:             committableTransaction.Commit();
  23:         } 
  24:  
  25:         if (null != dependentTransaction)
  26:         {
  27:             dependentTransaction.Complete();
  28:         }
  29:     }
  30:     catch (Exception ex)
  31:     {
  32:         Transaction.Current.Rollback(ex);
  33:         throw;
  34:     }
  35:     finally
  36:     {
  37:         Transaction transaction = Transaction.Current;
  38:         Transaction.Current = originalTransaction;
  39:         transaction.Dispose();
  40:     }
  41: }

InvokeInTransaction方法的参数是一个Action类型的代理(Delegate),表示具体的业务操作。在开始的时候记录下当 前的环境事务,当整个操作结束之后应该环境事务恢复成该值。如果存在环境事务,则创建环境事务的依赖事务,反之直接创建可提交事务。并将新创建的依赖事务 或者可提交事务作为当前的环境事务。将目标操作的执行(action)放在try/catch中,当目标操作顺利执行后,调用依赖事务的Complete 方法或者可提交事务的Commit方法。如果抛出异常,则调用环境事务的Rollback进行回滚。在finally块中将环境事务恢复到之前的状态,并 调用Dispose方法对创建的事务进行回收。

借助于InvokeInTransaction这个辅助方法,我们以事务型方法的形式定义了如下的两个方法:Withdraw和Deposit,分别实现提取和存储的操作。

   1: static void Withdraw(string accountId, double amount)
   2: {           
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("id", accountId);
   5:     parameters.Add("amount", amount);
   6:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_WITHDRAW", parameters));
   7: }       
   8:  
   9: static void Deposit(string accountId, double amount)
  10: {
  11:     Dictionary<string, object> parameters = new Dictionary<string, object>();
  12:     parameters.Add("id", accountId);
  13:     parameters.Add("amount", amount);
  14:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_DEPOSIT", parameters));
  15: }

二、TransactionScope

在上面一节,我结合可提交事务和依赖事务,以及环境事务的机制提供了对事务型操作的实现。实际上,如果借助TransactionScope,相应的代码将会变得非常简单。下面的代码中,通过TransactionScope对InvokeInTransaction进行了改写,从执行效果来看这和原来的代码完全一致。

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     using (TransactionScope transactionScope = new TransactionScope())
   4:     {
   5:         action();
   6:         transactionScope.Complete();
   7:     }
   8: }

通过InvokeInTransaction方法前后代码的对比,我们可以明显看到TransactionScope确实能够使我们的事务控制变得非常的简单。实际上,在利用System.Transactions事务进行编程的时候,我们一般不会使用到可提交事务,对于依赖事务也只有在异步调用的时候会使用到,基于TransactionScope的事务编程方式才是我们推荐的。

正如其名称所表现的一样,TransactionScope就是为一组事务型操作创建一个执行范围,而这个范围始于TransactionScope创建之时,结束于TransactionScope被回收(调用Dispose方法)。在对TransactionScope进行深入介绍之前,照例先来看看它的定义:

   1: public sealed class TransactionScope : IDisposable
   2: {
   3:     public TransactionScope();
   4:     public TransactionScope(Transaction transactionToUse);
   5:     public TransactionScope(TransactionScopeOption scopeOption);
   6:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout);
   7:     public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);
   8:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions);
   9:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout, EnterpriseServicesInteropOption interopOption);
  10:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption);
  11:     
  12:     public void Complete();
  13:     public void Dispose();    
  14: }

我们可以看到TransactionScope实现了IDisposable接口,除了Dispose方法之外,仅仅具有一个唯一的方法:Complete。但是TransactionScope却有一组丰富的构造函数。我们先来看看这些构造函数相应的参数如何影响TransactionScope对事务控制的行为。

1、TransactionScopeOption

实际上前面一节中提供的InvokeInTransaction方法基本上体现了TransactionScope的内部实现。也就是说,TransactionScope也是通过创建可提交事务或者依赖事务,并将其作为事务范围内的环境事务,从而将范围的所有操作纳入到一个事务之中。

通过在构造函数中指定TransactionScopeOption类型的scopeOption参数,控制TransactionScope当环境事务存在的时候应该采取怎样的方式执行事务范围内的操作。具有来讲,具有三种不同的方式:

  • 如果已经存在环境事务,则使用该环境事务。否则,在进入范围之前创建新的事务;
  • 总是为该范围创建新事务;
  • 环境事务上下文在创建范围时被取消。范围中的所有操作都在无环境事务上下文的情况下完成。

TransactionScopeOption是一个枚举,三个枚举值Required、RequiresNew和Suppress依次对应上面的三种行为。

   1: public enum TransactionScopeOption
   2: {
   3:     Required,
   4:     RequiresNew,
   5:     Suppress
   6: }

对于Required选项,如果当前存在环境事务TransactionScope会 创建环境事务的依赖事务,负责创建可提交事务,然后将创建的环境事务或者可提交事务作为事务范围的环境事务。如对于RequiresNew选 项,TransactionScope总是会创建可提交事务并将其作为事务范围的环境事务,意味着控制事务范围内操作的事务也当前的环境事务已经没有任何 关系。如果Suppress选项,TransactionScope会将事务范围内的环境事务设为空,意味着事务范围内的操作并不受事务的控制。

Required是默认选项,意味着事务范围内的事务将会作为当前环境事务的一部分。如果你不希望某个操作被纳入当前的环境事务,但是相应的操作也 需要事务的控制以确保所操作数据的一致性。比如,当业务逻辑失败导致异常抛出,需要对相应的错误信息进行日志记录。对于日记的操作就可以放入基于RequiresNew选项创建TransactionScope中。对于一些不重要的操作(操作的错误可被忽略),并且不需要通过事务来控制的操作,比如发送一些不太重要的通知,就可以采用Suppress选项。

2、TransactionOptions和EnterpriseServicesInteropOption

TransactionOptions在前面已经提及,用于控制事务的超时时限和隔离级别。对于超时时限,你也可以选择 TransactionScope相应能够的构造函数以TimeSpan的形式指定。而对于事务的隔离级别,需要着重强调一点:当选择 TransactionScopeOption.Required选项时,TransactionScope指定的隔离级别必须与环境事务(如果有)相匹 配。

比如下面的例子中,我定义两个嵌套的TransactionScope,外部的TransactionScope采用默认的隔离级别,内部在采用ReadCommitted隔离级别,当执行这段代码的时候,会抛出如图1所示的ArgumentException异常。

   1: using (TransactionScope outerScope = new TransactionScope())
   2: {
   3:     TransactionOptions transactionOptions = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
   4:     using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
   5:     {
   6:         //事务型操作
   7:         innerScope.Complete();
   8:     }
   9:     //事务型操作
  10:     outerScope.Complete();
  11: }

image

图1 隔离级别不一致导致的异常

实际上在System.Transactions事务机制被引入之前,像Enterprise Service主要依赖于基于COM+的分布式事务。TransactionScope通过 EnterpriseServicesInteropOption控制System.Transactions事务如何与COM+的分布式事务进行互操 作。具有来讲具有如下三种互操作选项,分别和EnterpriseServicesInteropOption三个枚举值相对应:

  • None:Transaction 和 Current 之间不同步;
  • Automatic:搜索现有的 COM+ 上下文并与之同步(如该上下文存在);
  • Full:System.EnterpriseServices 上下文(可通过调用 ContextUtil 类的静态方法 Transaction 来检索)和 System.Transactions 环境事务(可通过调用 Transaction 类的静态方法 Current 来检索)始终保持同步。这将引入性能损失,因为可能需要创建新的 System.EnterpriseServices 上下文。
   1: public enum EnterpriseServicesInteropOption
   2: {
   3:     None,
   4:     Automatic,
   5:     Full
   6: }

3、事务提交和回滚

对于事务范围中的事务,无论是事务的提交(对于可提交事务)、完成(依赖事务)和回滚都是在Dispose方法中执行的。 TransactionScope中定一个个私有的布尔类型字段(complete)表示事务是否正常结束。该成员的默认值为False,当调用 TransactionScope的Complete方法的时候会将此字段设置成True。当Dispose执行的时候,如果该字段的值为False,会 调用事务的Rollback方法对该事务实施回滚;否则会调用Commit方法(对于可提交事务)对事务进行提交或者调用Complete方法(依赖事 务)通知被依赖的事务本地事务已经正常完成。

除了执行事务的提交、完成或者回滚之外,TransactionScope的Dispose方法还负责将环境事务回复到事务范围开始之前的状态。在 调用Complete和Dispose之前,环境事务处于不可用的状态,如果此时试图获取环境事务,会抛出异常。比如在下面的代码中,在事务范围内部调用 Complete方法后,通过Transaction的Current静态属性获取当前环境事务,会抛出图2所示的InvalidOpertionException异常。

   1: using (TransactionScope transactionScope = new TransactionScope())
   2: {
   3:     //其他事务操作
   4:     transactionScope.Complete();
   5:     Transaction ambientTransaction = Transaction.Current;
   6: }  

image 图2 在TransactionScope完成之后获取环境事务导致的异常

转载于:https://www.cnblogs.com/duanxz/p/4282006.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDU——2444 The Accomodation of Students

The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)                    Total Submission(s): 7086 Accepted Submission(s): 3167 Problem DescriptionThere are a group of studen…

iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

-- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能&#xff0c;乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的&#xff0c;这不愧为一项伟大的设计。今天我们就针对iOS的触摸事件&#xff08;手势操作&#xff09;、运动事件、远程控制…

关于Hyper-V备份的四大注意事项

尽管Hyper-V备份相对简单&#xff0c;但备份管理员仍需注意四大问题。这四方面的问题在创建备份时可能不太重要&#xff0c;但在备份恢复时影响甚大。 1、对于虚拟机来说不仅意味着虚拟磁盘 就目前来看&#xff0c;企业在执行Hyper-V备份时最常见的误区就是把虚拟机当做物理服务…

python为什么忽然火了_为什么Python突然就火了起来了呢?

近日&#xff0c;TIOBE发布10月编程语言排行榜显示&#xff0c;15年来TIOBE指数的前8名一直保持不变&#xff0c;而Python正在成为一种新的大型语言。越来越多的企业在使用Python进行开发&#xff0c;越来越多的人正在加入Python程序员行列!TIOBE 10月编程语言排行榜前20名Pyth…

SQL 2005 全文索引

全文索引技术是目前搜索引擎的关键技术。 试想在1M大小的文件中搜索一个词&#xff0c;可能需要几秒&#xff0c;在100M的文件中可能需要几十秒&#xff0c;如果在更大的文件中搜索那么就需要更大的系统开销&#xff0c;这样的开销是不现实的。 所以在这样的矛盾下出现了全文索…

python重命名窗口_Python:即时重命名方法名称

如果要继续在已切换到使用属性的对象上使用get_Field和set_Field(您只需访问或分配给Field),则可以使用包装器对象&#xff1a;class NoPropertyAdaptor(object):def __init__(self, obj):self.obj objdef __getattr__(self, name):if name.startswith("get_"):retu…

nginx优化之请求直接返回json数据

对于有些服务端接口返回是固定值的json&#xff0c;可通过配置nginx直接返回json&#xff0c;减少程序的加载对资源的占用&#xff0c;减少接口响应时间 location ~* (request/update)$ { default_type application/json; return 200 {"update":"no&quo…

ARP扫描工具arp-scan

2019独角兽企业重金招聘Python工程师标准>>> ARP扫描工具arp-scan arp-scan是Kali Linux自带的一款ARP扫描工具。该工具可以进行单一目标扫描&#xff0c;也可以进行批量扫描。批量扫描的时候&#xff0c;用户可以通过CIDR、地址范围或者列表文件的方式指定。该工具…

数据库索引的作用和优点缺点

为什么要创建索引呢&#xff1f;这是因为&#xff0c;创建索引可以大大提高系统的性能。 第一&#xff0c;通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。 第二&#xff0c;可以大大加快 数据的检索速度&#xff0c;这也是创建索引的最主要的原因。 第…

elementui el-from 怎样显示图片_vue2.0使用weui.js的uploader组件上传图片(兼容移动端)...

本文已同步到专业技术网站 www.sufaith.com, 该网站专注于前后端开发技术与经验分享, 包含Web开发、Nodejs、Python、Linux、IT资讯等板块.最近在使用 vue2.0开发微信公众号网页 其中涉及到 选择图片, 图片的压缩上传, 预览, 删除等操作。项目整体UI框架使用的是 vux, 但可惜的…

面向对象分析

在需求获取阶段&#xff0c;开发人员关注于理解用户以及他们的使用要求。而在需求分析阶段&#xff0c;开发人员关注于理解系统需要构建的内容&#xff0c;其核心是产生一个准确的、完整的、一致的和可验证的系统模型&#xff0c;称为分析模型。 面对对象的分析模型由三个独立的…

python字典输入学生信息_如何用Python将XML中的所有信息输入字典

我通常使用标准库中的ElementTree模块解析XML。它没有给你一个字典&#xff0c;你得到了一个更有用的DOM结构&#xff0c;它允许你为孩子们遍历每个元素。from xml.etree import ElementTree as ETxml ET.parse("root_element xml.getroot()for child in root_element:.…

HDU4267(2012年长春站)

这道题真的是好题&#xff0c;让我对线段树有了全新的认识&#xff0c;至少让我真正感受到了线段树的神奇。 题意是就是线段树区间更新&#xff0c;单点询问的问题&#xff0c;不过这个题好就好在它的区间更新的点并不连续&#xff01; adding c to each of Ai which satisfies…

ITFriend创业败局(四):菜鸟CEO的自我修养

自创业自封CEO以来&#xff0c;短短3个月&#xff0c;又经历了无数的磨练&#xff0c;快速成长中。创业不同于打工&#xff0c;他要求你必须有全局观和综合能力&#xff0c;技术、市场、商务&#xff0c;啥都得会&#xff0c;还要处理各种各样的问题和矛盾。根据个人经历&#…

51nod 1050 循环数组最大子段和

1050 循环数组最大子段和 N个整数组成的循环序列a[1],a[2],a[3],…,a[n]&#xff0c;求该序列如a[i]a[i1]…a[j]的连续的子段和的最大值&#xff08;循环序列是指n个数围成一个圈&#xff0c;因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列&#xff09;。当所给的整数均为负数时…

mysql设置token有效期_记住我 token保存到数据库

记住我 token保存到数据库这里使用jpamysqlorg.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javaspring.datasource.driver-class-namecom.mysql.cj.jdbc.Driverspring.datasource.urljdbc:mysql://127.0.0.1:3306/fly-demo?serverTimezoneUTC&…

Spark- Linux下安装Spark

Spark- Linux下安装Spark 前期部署 1.JDK安装&#xff0c;配置PATH 可以参考之前配置hadoop等配置 2.下载spark-1.6.1-bin-hadoop2.6.tgz,并上传到服务器解压 [rootsrv01 ~]# tar -xvzf spark-1.6.1-hadoop2.6.tgz /usr/spark-1.6.1-hadoop2.6 3.在 /usr 下创建软链接到目标文…

Linux Apache 怎么修改工作模式

Apache默认为prefork模式&#xff0c;主要是考虑到稳定性的原因。  要切换到worker模式&#xff0c;则需要登录到linux上&#xff0c;进行如下操作&#xff1a;  进入/usr/sbin目录  cd /usr/sbin  将当前的prefork模式启动文件改名  mv httpd httpd.prefork  将wo…

python需要背的英语单词怎么写_学Python必须背的42个常见单词,看看你都会吗?...

这42个单词是学习Python必须背会的单词&#xff0c;也是代码中常见的单词。希望你能都背下来&#xff01;&#xff01;1. adult [ˈdʌlt] 成年人2. authentication [ɔːˌθentɪˈkeɪʃn] 身份验证、认证、鉴定3. bit [bɪt] 稍微、小量、小块、一点4. byte [baɪt] …

viewDidLoad、viewWillAppear、viewWillDisappear

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil viewDidLoad viewWillAppear viewWillDisapppear《iOS编程》P137关于视图的初始化代码不能写在视图控制器的初始化&#xff08;1&#xff09;&#xff0c;原因如下&#xff1a;为…