加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

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

发布时间:2020-12-16 09:06:55 所属栏目:asp.Net 来源:网络整理
导读:在.NET 1.x中,我们基本是通过ADO.NET实现对不同数据库访问的事务。.NET 2.0为了带来了全新的事务编程模式,由于所有事务组件或者类型均定义在System.Transactions程序集中的System.Transactions命名空间下,我们直接称基于此的事务为System.Transactions事

在.NET 1.x中,我们基本是通过ADO.NET实现对不同数据库访问的事务。.NET 2.0为了带来了全新的事务编程模式,由于所有事务组件或者类型均定义在System.Transactions程序集中的System.Transactions命名空间下,我们直接称基于此的事务为System.Transactions事务。System.Transactions事务编程模型使我们可以显式(通过System.Transactions.Transaction)或者隐式(基于System.Transactions.TransactionScope)的方式进行事务编程。我们先来看看,这种全新的事务如何表示。

一、System.Transactions.Transaction

在System.Transactions事务体系下,事务本身通过类型System.Transactions.Transaction类型表示,下面是Transaction的定义:

   1: [Serializable]
   3: {   
   5:     
   7:     public DependentTransaction DependentClone(DependentCloneOption cloneOption);
   9:     public Enlistment EnlistDurable(Guid resourceManagerIdentifier,IEnlistmentNotification enlistmentNotification,EnlistmentOptions enlistmentOptions);
  11:     bool EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification);  
  13:     public Enlistment EnlistVolatile(ISinglePhaseNotification singlePhaseNotification,1)" id="lnum14">  14:? 
  16:     void Rollback(Exception e);
  18:     void ISerializable.GetObjectData(SerializationInfo serializationInfo,StreamingContext context);
  20:     static Transaction Current { get; set; }   
  22:     public IsolationLevel IsolationLevel { get; }
  24: }

1、Transaction是可序列化的

从上面的定义我们可以看到,Transaction类型(在没有特殊说明的情况下,以下的Transaction类型指的就是System.Transactions.Transaction)上面应用的SerializableAttribute特性,并且实现了ISerializable接口,意味着一个Transaction对象是可以被序列化的。Transaction的这一特性在WCF整个分布式事务的实现意义重大,原因很简单:要让事务能够控制整个服务操作,必须实现事务的传播,而传播的前提就是事务可被序列化

2、如何登记事务参与者

在Transaction中,定义了五个EnlistXxx方法用于将涉及到的资源管理器登记到当前事务中。其中EnlistDurable和EnlistVolatile分别实现了对持久化资源管理器和易失资源管管理器的事务登记,而EnlistPromotableSinglePhase则针对的是可被提升的资源管理器(比如基于SQL Server 2005和SQL Server 2008)。

事务登记的目的是建立事务提交树,使得处于根节点的事务管理器能够在事务提交的时候能够沿着这棵树将相应的通知发送给所有的事务参与者。这种至上而下的通知机制依赖于具体采用事务提交协议,或者说某个资源要求参与到当前事务之中,必须满足基于协议需要的接收和处理相应通知的能力。System.Transactions将不同事务提交协议对参与者的要求定义在相应的接口中。其中IEnlistmentNotification和ISinglePhaseNotification分别是基于2PC和SPC(关于2PC和SPC,在上篇中有详细的介绍)。

如果我们需要为相应的资源开发能够参与到System.Transactions事务的资源管理器,需要事先实现IEnlistmentNotification接口,对基本的2PC协议提供支持。当满足SPC要求的时候,如果希望采用SPC优化协议,则需要实现ISinglePhaseNotification接口。如果希望像SQL Server 2005或者SQL Server 2008支持事务提升机制,则需要实现IPromotableSinglePhaseNotification接口。

3、环境事务(Ambient Transaction)

Transaction定义了一个类型为Transaction的Current静态属性(可读可写),表示当前的事务。作为当前事务的Transaction存储于当前线程的TLS(Thread Local Storage)中(实际上是定义在一个应用了ThreadStaticAttribute特性的静态字段上),所以仅对当前线程有效。如果进行异步调用,当前事务并不能自动事先跨线程传播,将异步操作纳入到当前事务,需要使用到另外一个事务:依赖事务。

这种基于当前线程的当前事务又称环境事务(Ambient Transaction),很多资源管理器都具有对环境事务的感知能力。也就是说,如果我们通过Current属性设置了环境事务,当对某个具有环境事务感知能力的资源管理器进行访问的时候,相应的资源管理器会自动登记到当前事务中来。我们将具有这种感知能力的资源管理器称为System.Transactions资源管理器。

4、事务标识

Transaction具有一个只读的TransactionInformation属性,表示事务一些基本的信息。属性的类型为TransactionInformation,定义如下:

2: {
public TransactionStatus Status { get; }
string LocalIdentifier { get; }
   8: }

TransactionInformation的CreationTime和Status表示创建事务的时间和事务的当前状态。事务具有活动(Active)、提交(Committed)、中止(Aborted)和未决(In-Doubt)四种状态,通过TransactionStatus枚举表示。

2: {
   4:     Committed,
   7: }

事务具有两个标识符,一个是本地标识,另一个是分布式标识,分别通过TransactionInformation的只读属性LocalIdentifier和DistributedIdentifier表示。本地标识由两部分组成:标识为本地应用程序域分配的轻量级事务管理器(LTM)的GUID和一个递增的整数(表示当前LMT管理的事务序号)。在下面的代码中,我们分别打印出三个新创建的可提交事务(CommittableTransaction,为Transaction的子类,我们后面会详细介绍)的本地标识。

using System.Transactions;
   4: {
   6:     {
   8:         Console.WriteLine(new CommittableTransaction().TransactionInformation.LocalIdentifier);
  11: }

输出结果:

AC48F192-4410-45fe-AFDC-8A890A3F5634:1
AC48F192-4410-45fe-AFDC-8A890A3F5634:2
AC48F192-4410-45fe-AFDC-8A890A3F5634:3

一旦本地事务提升到基于DTC的分布式事务,系统会为之生成一个GUID作为其唯一标识。当事务跨边界执行的时候,分布式事务标识会随着事务一并被传播,所以在不同的执行上下文中,你会得到相同的GUID。分布式事务标识通过TransactionInformation的只读属性DistributedIdentifier表示,我经常在审核(Audit)中使用该标识。

对于上面Transaction的介绍,细心的读者可能会发现两个问题:Transaction并没有提供公有的构造函数,意味着我们不能直接通过new操作符创建Transaction对象;Transaction只有两个重载的Rollback方法,并没有Commit方法,意味着我们直接通过Transaction进行事务提交。

在一个分布式事务中,事务初始化和提交只能有相同的参与者担当。也就是说只有被最初开始的事务才能被提交,我们将这种能被初始化和提交的事务称作可提交事务(Committable Transaction)。随着分布式事务参与者逐个登记到事务之中,它们本地的事务实际上依赖着这个最初开始的事务,所以我们称这种事务为依赖事务(Dependent Transaction)。

二、 可提交事务(CommittableTransaction)

只有可提交事务才能被直接初始化,对可提交事务的提交驱动着对整个分布式事务的提交。可提交事务通过CommittableTransaction类型表示。照例先来看看CommittableTransaction的定义:

public CommittableTransaction();
public CommittableTransaction(TransactionOptions options);
   8:     void Commit();
void EndCommit(IAsyncResult asyncResult);
object IAsyncResult.AsyncState { get; }
  14:     bool IAsyncResult.CompletedSynchronously { get; }
  16: }

1、可提交事务的超时时限和隔离级别

CommittableTransaction直接继承自Transaction,提供了三个公有的构造函数。通过TimeSpan类型的timeout参数指定事务的超时实现,自被初始化那一刻开始算起,一旦超过了该时限,事务会被中止。通过TransactionOptions类型的options可以同时指定事务的超时时限和隔离级别。TransactionOptions是一个定义在System.Transactions命名空间下的结构(Struct),定义如下,两个属性Timeout和IsolationLevel分别代表事务的超时时限和隔离级别。

struct TransactionOptions
//其他成员
public IsolationLevel IsolationLevel { get; set; }
<?xml version="1.0" encoding="utf-8" ?>
   3:   system.transactionsdefaultSettings timeout="00:01:00"/>
   6:   </   7: >

作为事务ACID四大属性之一的隔离性(Isolation),确保事务操作的中间状态的可见性仅限于事务内部。隔离机制通过对访问的数据进行加锁,防止数据被事务的外部程序操作,从而确保了数据的一致性。但是隔离机制在另一方面又约束了对数据的并发操作,降低数据操作的整体性能。为了权衡着两个互相矛盾的两个方面,我们可以根据具体的情况选择相应的隔离级别。

在System.Transactions事务体系中,为事务提供了7种不同的隔离级别。这7中隔离级别分别通过System.Transactions.IsolationLevel的7个枚举项表示。

3: Serializable,1)" id="lnum4"> 4: RepeatableRead,1)" id="lnum5"> 5: ReadCommitted,1)" id="lnum6"> 6: ReadUncommitted,1)" id="lnum7"> 7: Snapshot,1)" id="lnum8"> 8: Chaos,1)" id="lnum9"> 9: Unspecified
CREATE Procedure P_WITHDRAW
   3:         @id        VARCHAR(50),1)" id="lnum4">   4:         @amount FLOAT
   6: AS
BEGIN
,16,1)
END    
BEGIN
,1)
END
  18: UPDATE     [dbo].[T_ACCOUNT] SET Balance = Balance - @amount WHERE Id = @id
Procedure P_DEPOSIT
  11: SET Balance = Balance + @amount WHERE Id = @id
Procedure P_GET_BALANCE_BY_ID
   4:     )
WHERE Id = @id)
  10: SELECT BALANCE GO

为了执行存储过程的方便,我写了一个简单的工具类DbAccessUtil。ExecuteNonQuery和ExecuteScalar的作用于DbCommand同名方法相同。使用DbAccessUtil的这两个方法,只需要以字符串和字典的方式传入存储过程名称和参数即可。由于篇幅所限,关于具有实现不再多做介绍了,又兴趣的读者,可以参考《WCF技术剖析(卷1)》的最后一章,里面的DbHelper提供了相似的实现。

int ExecuteNonQuery(string procedureName,IDictionary<string,1)">object> parameters);
   5: }

借助于DbAccessUtil提供的辅助方法,我们定义两个方法Withdraw和Deposit分别实现提取和存储的操作,已近获取某个帐户当前余额。

3: Dictionary<object> parameters = new Dictionary<object>();
   5:     parameters.Add("amount",amount);
   7: } 
   9: {
  11:     parameters.Add(  12:     parameters.Add(  13:     DbAccessUtil.ExecuteNonQuery(  14: }
  16: {
using System.Collections.Generic;
class Program
   8:         {
  10:             string nonExistentAccount = Guid.NewGuid().ToString();            
  12:             Console.WriteLine("帐户"{0}"的当前余额为:¥{1}",accountFoo,GetBalance(accountFoo));
  14:             try
  16:                 Transfer(accountFoo,nonExistentAccount,1000);
  18:             catch (Exception ex)
  20:                 Console.WriteLine("转帐失败,错误信息:{0}",ex.Message);
  22:             //输出转帐后的余额
  24:         }
  26:         void Transfer(string accountFrom,1)">string accountTo,1)" id="lnum27">  27:         {
  31:     }
帐户"Foo"的当前余额为:¥5000
转帐失败,错误信息:帐户ID不存在
   3:     Transaction originalTransaction = Transaction.Current;
try
   8:         Withdraw(accountFrom,1)" id="lnum9">   9:         Deposite(accountTo,1)" id="lnum10">  10:         transaction.Commit();
  13:     {
throw;
  17:     finally
  19:         Transaction.Current = originalTransaction;
  21:     }
"Foo"的当前余额为:¥5000

下一篇中我们将重点介绍DependentTransaction和TransactionScope。

?

分布式事务系列:
谈谈分布式事务之一:SOA需要怎样的事务控制方式
谈谈分布式事务之二:基于DTC的分布式事务管理模型[上篇]
谈谈分布式事务之二:基于DTC的分布式事务管理模型[下篇]
谈谈分布式事务之三: System.Transactions事务详解[上篇]
谈谈分布式事务之三: System.Transactions事务详解[下篇]

作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读