1. 程式人生 > >.NET簡談事務、分散式事務處理

.NET簡談事務、分散式事務處理

今天這篇文章我們將使用.NET C#來進行事務性程式設計,從淺顯、簡單的本地事務開始,也就是我們用的最多的ADO.NET事務處理,然後我們逐漸擴大事務處理範圍,包括對分散式事務處理的使用,多執行緒事務處理的使用。 

一、資料庫事務處理

資料庫事務處理我們基本都很熟悉了,begin Transaction ……end Transaction,將要進行事務性的操作包在程式碼段裡,為了便於文章有條理的講解下去,我還是在這裡穿插一個簡單的小示例,便於與後面的程式碼進行對比分析。

1

我們在資料庫裡建兩張表,也就是很簡單一列資訊。

表1名:test

 

表2名:test2

 

目的是為了掩飾事務的特性,所以我們這裡給表1test的name列設定為主鍵,我們後面將通過有意的造成主鍵重複,導致事務自動回滾的效果。 我先來解釋一下這兩張表後面幹什麼用的。表test是用來有意造成事務內部處理出錯用的,表test2是用來在事務處理當中扮演著沒有錯誤的常規資料插入用的,我會在test2中先插入資料,然後在test中插入資料時觸發事務內部執行錯誤導致事務回滾。 好了我們進行T-SQL的編寫: 
insert into test values('222') --我們在表test中插入一條記錄  
go  
begin transaction tr  
begin 
try begin insert into test2 values('111') insert into test values('222') --該行插入會導致主鍵衝突,也就是我們要的效果 end commit transaction tr end try begin catch print '事務執行錯誤!' print error_number() rollback transaction tr end catch

我們執行看看結果:

在事務處理過程中,很明顯第一條插入語句執行成功了,但是由於第二條插入語句導致事務回滾所以資料是沒有變化的。

這個示例可能過於簡單,真正的企業級應用可能很複雜,但是我們的目的是看看事務的使用,越簡單越明瞭越好。[王清培版權所有,轉載請給出署名]

 二、ADO.NET事務處理

下面我們將事務在.NET的AOD.NET中實現看看效果。

例2:

public class Test  
    {  
        SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        public void Add()  
        {  
            conn.Open();  
            SqlCommand command = new SqlCommand("insert into test2 values(111)", conn);  
            try 
            {  
                command.Transaction = conn.BeginTransaction();  
                command.ExecuteNonQuery();  
                command.CommandText = "insert into test values(222)";  
                command.ExecuteNonQuery();  
                command.Transaction.Commit();  
            }  
            catch (Exception err)  
            {  
                Console.WriteLine(err);  
                command.Transaction.Rollback();  
            }  
        }  
    }  

這就是典型ADO.NET事務處理程式碼,其實和我們第一個例子中的T-SQL程式碼是差不多的,通過ADO.NET中的SqlConnection.BeginTransaction()獲取到對底層ODBC中的資料庫事務的引用,其實這裡還沒有真正的設計到.NET中的事務處理程式碼,這裡只是對資料庫管理系統的遠端呼叫,通過遠端處理的訊息通訊進行事務處理遠端化。

事務資訊顯示類,為了便於觀察事務的狀態資訊。 
public class DisplayTransactioninfo  
    {  
        public static void Display(System.Transactions.Transaction tr)  
        {  
            if (tr != null)  
            {  
                Console.WriteLine("Createtime:" + tr.TransactionInformation.CreationTime);  
                Console.WriteLine("Status:" + tr.TransactionInformation.Status);  
                Console.WriteLine("Local ID:" + tr.TransactionInformation.LocalIdentifier);  
                Console.WriteLine("Distributed ID:" + tr.TransactionInformation.DistributedIdentifier);  
                Console.WriteLine();  
            }  
        }  

三、CommittableTransaction事務處理

 

從這裡開始我們將接觸到.NET中的事務處理,將瞭解到.NET中事務是怎樣影響到遠端資料庫管理系統的事務處理的。

其實事務處理是一個非常複雜的技術領域,需要考慮很多可逆的技術實現,我們只是簡單的瞭解原理和掌握基本的運用。

在我的 .NET簡談事務本質論一文中說到了事務的傳遞原理,那麼事務傳遞意味著什麼。其實事務傳遞的大概意思是將事務的執行範圍通過網路傳輸的方式進行擴大到其他的機器上,比如我們在.NET中執行一項關於事務性的操作,那麼在這個操作裡面我們包含了對資料庫的操作,這個時候對資料庫的一系列操作都應該是屬於事務範圍內的,當事務回滾時還應該將資料庫中的資料進行回滾才對。但是我們不可能總是顯示的執行ADO.NET中的BeginTransaction,對於本地事務處理也就是單一資源管理器來說這也可以接受,那麼如果在事務範圍內涉及到多個資源管理器的操作,這就是分散式事務處理的範圍了。所以說事務處理需要跨越網路傳輸形成無縫的面向服務的事務處理,資料庫管理系統即有可能扮演者事務管理器的角色也有可能扮演著資源管理器的角色。太多的理論知識我這裡就不多扯了,我們還是來看程式碼吧。

接著上面的例項,我們現在通過.NET中的事務處理來進行自動化的資料庫事務處理。讓資料庫能自動的感知到我們正在進行事務性的操作。

3

我們利用Transaction類的子類CommittableTransaction可提交事務類來進行事務程式設計。 

public class Test3  
    {  
        SqlConnection conn;  
        CommittableTransaction committran = new CommittableTransaction();  
        public Test3()  
        {  
            conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
            DisplayTransactioninfo.Display(committran);  
        }  
        public void Add3()  
        {  
            conn.Open();  
            conn.EnlistTransaction(committran);//需要將本次的連線操作視為事務性的  
            SqlCommand command = new SqlCommand();  
            try 
            {  
                command.Connection = conn;  
                command.CommandText = "insert into test2 values(111)";  
                command.ExecuteNonQuery();  
                command.CommandText = "insert into test values(222)";  
                command.ExecuteNonQuery();  
                committran.Commit();  
            }  
            catch (Exception err) { committran.Rollback(); //出現出錯執行回滾操作}  
        }  
    }  

資料來源連線物件代表著遠端資料庫資源,所以在執行操作之前我們需要將資源管理器新增到本地事務管理器中進行後期的投票、提交管理。 

四、EnterpriseService(COM+)自動化事務處理

在.NET2.0中有一個程式集不是太被人重視,System.EnterpriseServices.dll,這個程式集是.NET中為了使用早起的COM+技術的託管程式集,我們可以使用這個程式集來編寫一些我們以前所不能編寫的COM+應用程式。至於COM+應用程式的介紹網上一大堆,隨便搜搜就有好多資料了,我印象中有一篇是潘愛明潘老師寫的一個文章蠻好的,就是介紹COM+的所有特性。

我們繼續來事務處理,下面我們看看怎麼借用System.EnterpriseServices.Transaction類來進行自動化的事務處理。

4 

[System.Runtime.InteropServices.ComVisible(true)]  
//COM+是在COM的基礎上發展起來的,需要將.NET程式集中的型別公開為COM元件。  
    [System.EnterpriseServices.Transaction(TransactionOption.Required)]//始終需要事務處理域  
    public class Test2 : ServicedComponent  
    {  
        public Test2() { }  
        SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        [AutoComplete(true)]  
        //如果在方法的執行體類沒有出現錯誤,那麼將自動設定事務處理的結果  
        public void Add2()  
        {  
            conn.Open();  
            SqlCommand command = new SqlCommand();  
            try 
            {  
                command.Connection = conn;  
                command.CommandText = "insert into test2 values(111)";  
                command.ExecuteNonQuery();  
                command.CommandText = "insert into test values(222)";  
                command.ExecuteNonQuery();  
            }  
            catch { System.EnterpriseServices.ContextUtil.SetAbort(); }  
        }  
    }  

五、DependentTransaction跨執行緒事務處理

我們在編寫高併發量程式時,都會用到多執行緒來進行處理,讓主執行緒能有時間來處理第一線的請求,然後將請求分發到各個子執行緒上進行後臺的處理。我們來看一幅圖:

我們假設上面這幅圖是我們系統的一個內部結構,主執行緒主要的任務就是接受外來的請求,然後將具體的任務完成放到一個到兩個子執行緒中去完成,但是子執行緒與子執行緒之間沒有必然的關係,由於事務的上下文是不誇執行緒的,那麼怎麼將兩個或者更多的執行緒串在一個事務裡。[王清培版權所有,轉載請給出署名]

我們來看看依賴事務處理,看程式碼:

5 

public class Test6  
    {  
        CommittableTransaction commit = new CommittableTransaction();  
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        public Test6()  
        {  
            conn1.Open();  
            conn1.EnlistTransaction(commit);  
        }  
        public void Add6()  
        {  
            try 
            {  
                DisplayTransactioninfo.Display(commit);  
                SqlCommand command = new SqlCommand("insert into test2 values(111)", conn1);  
                command.ExecuteNonQuery();  
                Thread thread = new Thread(Test6.CommitThread);  
            thread.Start(commit.DependentClone(DependentCloneOption.BlockCommitUntilComplete));  
                commit.Commit();  
            }  
            catch (Exception err) { commit.Rollback(); }  
        }  
        public static void CommitThread(object co)  
        {  
            DependentTransaction commit = co as DependentTransaction;  
            SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
            conn2.Open();  
            conn2.EnlistTransaction(commit as DependentTransaction);  
            DisplayTransactioninfo.Display(commit);  
            SqlCommand command = new SqlCommand("insert into test values(111)", conn2);  
            try 
            {  
                command.ExecuteNonQuery();  
                commit.Complete();  
            }  
            catch (Exception err) { Console.WriteLine(err); commit.Rollback(); }  
        }  
    }  

我們用一個子執行緒來執行另外的一個事務處理,由於是依賴事務處理,所以主事務處理完成後要等待子事務處理的結果。其實本例子已經是涉及到分散式事務處理的範圍了,當事務範圍內有一個以上的資源管理器時,本地事務管理器將自動提升為DTC管理器,下面我們來看看分散式事務處理。 

六、DTC(Distributed Transaction Coordinator分散式事務處理

分散式事務在開發中經常是被用到,也必須被用到。必須同步資料、上下文更新等等。

按照使用方式的不同分散式事務的複雜程度也不同,基於本地事務的多資源管理器和基於SOA的面向服務的多資源管理器。

由於本地事務處理是基於本地事務管理器的,所以它不能管理分散式的事務,一旦當我們處理的事務範圍要進行擴大時並且是誇機器的訪問時,那麼本地事務管理器將自動提升為分散式事務管理器也就是DTC(分散式事務協調器)。

6 

public class Test4  
    {  
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        CommittableTransaction committran = new CommittableTransaction();  
        public Test4()  
        {  
            DisplayTransactioninfo.Display(committran);  
            conn1.Open();  
            conn1.EnlistTransaction(committran);  
            conn2.Open();  
            conn2.EnlistTransaction(committran);  
            DisplayTransactioninfo.Display(committran);  
        }  
        public void Add4()  
        {  
            try 
            {  
                SqlCommand command1 = new SqlCommand("insert into test2 values(111)", conn1);  
                command1.ExecuteNonQuery();  
                SqlCommand command2 = new SqlCommand("insert into test values(222)", conn2);  
                command2.ExecuteNonQuery();  
            }  
            catch (Exception err) { Console.WriteLine(err); committran.Rollback(); }  
        }  
    }  

一旦我們開啟分散式事務處理就能在我們的電腦上的DTC管理器上看見到。

 

但是要記得檢查你的DTC服務是否開啟了。

 

七、基於WCF框架的分散式事務處理

其實基於WCF框架進行分散式事務開發真的很輕鬆,它能很好的感知到當前上下文是不是事務域,並能很好的將事務序列化到服務這邊來。但是設計一個高效能的分散式事務處理框架並非易事,需要很長時間的積累和實踐。我們來看一下WCF是如果進行分散式事務處理的。

7 

//服務契約(ServiceContract):  
[ServiceContract(SessionMode = SessionMode.Required)]  
    public interface IDistributedTransaction  
    {  
        [TransactionFlow(TransactionFlowOption.Allowed)]  
        [OperationContract]  
        void Add();  
    }  

 

//服務類1:  
public class DistributedTransactionService1 : IDistributedTransaction  
    {  
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        #region IDistributedTransaction  
        [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]  
        public void Add()  
        {  
            conn1.Open();  
            SqlCommand command = new SqlCommand("insert into test2 values(111)", conn1);  
            command.ExecuteNonQuery();  
            DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current);  
        }  
        #endregion  
    }  

 

//服務類2:  
public class DistributedTransactionService2 : IDistributedTransaction  
    {  
        SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");  
        #region IDistributedTransaction  
        [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]  
        public void Add()  
        {  
            conn2.Open();  
            SqlCommand command = new SqlCommand("insert into test values(222)", conn2);  
            try 
            {  
                DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current);  
                command.ExecuteNonQuery();  
            }  
            catch (Exception err) { throw err; }  
        }  
 

服務配置:

<service name="ServerConsole.Transaction.DistributedTransactionService1" behaviorConfiguration="metadatabehaviors">  
        <host>  
          <baseAddresses>  
            <add baseAddress="http://localhost:8027"/>  
            <add baseAddress="net.tcp://localhost:8028"/>  
          </baseAddresses>  
        </host>  
        <endpoint address="DistributedTransactionService1" binding="netTcpBinding" bindingConfiguration="tranbinding" 
                   contract="ServerConsole.Transaction.IDistributedTransaction"></endpoint>  
      </service>  
      <service name="ServerConsole.Transaction.DistributedTransactionService2" behaviorConfiguration="metadatabehaviors">  
        <host>  
          <baseAddresses>  
            <add baseAddress="http://localhost:8029"/>  
            <add baseAddress="net.tcp://localhost:8030"/>  
          </baseAddresses>  
        </host>  
        <endpoint address="DistributedTransactionService2" binding="netTcpBinding" bindingConfiguration="tranbinding" 
                contract="ServerConsole.Transaction.IDistributedTransaction"></endpoint>  
      </service> 
Binding配置:

<bindings>  
      <netTcpBinding>  
        <binding name="tranbinding" transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004">  
          <reliableSession enabled="true" ordered="true"/>  
        </binding>  
      </netTcpBinding>  
    </bindings> 

我們需要開啟Binding的事務流傳遞。

客戶端程式碼:

DistributedTransactionClient.DistributedTransactionClient tranclient = new DistributedTransactionClient.DistributedTransactionClient();  
            DistributedTransaction2Client.DistributedTransactionClient tranclient2 = new DistributedTransaction2Client.DistributedTransactionClient();  
            using (TransactionScope transcope = new TransactionScope())  
            {  
                try 
                {  
                    Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);  
                    tranclient.Add();  
                    tranclient2.Add();  
                    transcope.Complete();  
                }  
                catch (Exception err) { Transaction.Current.Rollback(); }  
            }  
 
static void Current_TransactionCompleted(object sender, TransactionEventArgs e)  
        {  
            if (e.Transaction.TransactionInformation.Status ==  
                System.Transactions.TransactionStatus.Committed)  
                Console.WriteLine(e.Transaction.TransactionInformation.DistributedIdentifier);  
        }  

客戶端使用TransactionScope類來進行環境事務的設定,這樣就很方便知道事務的執行範圍,在TransactionScope裡面我們可以通過Transaction.Current獲取到當前上下文的事務物件,由於事務物件是儲存線上程獨立儲存區裡的,所以跨執行緒訪問是沒用的,通過依賴事務進行傳遞。

  文章到這裡就講完了,從本地事務、多資源管理器分散式事務、SOA結構的分散式事務,我們都能進行基本的掌握。上面的例子都是經過嚴格測試的。