谈谈分布式事务之三: System.Transactions事务详解[下篇]
在前面一篇給出的Transaction的定義中,信息的讀者應(yīng)該看到了一個(gè)叫做DepedentClone的方法。該方法對(duì)用于創(chuàng)建基于現(xiàn)有Transaction對(duì) 象的“依賴事務(wù)(DependentTransaction)”。不像可提交事務(wù)是一個(gè)獨(dú)立的事務(wù)對(duì)象,依賴事務(wù)依附于現(xiàn)有的某個(gè)事務(wù)(可能是可提交事 務(wù),也可能是依賴事務(wù))。依賴事務(wù)可以幫助我們很容易地編寫(xiě)一些事務(wù)型操作,當(dāng)環(huán)境事務(wù)不存的時(shí)候,可以確保操作在一個(gè)獨(dú)立的事務(wù)中執(zhí)行;當(dāng)環(huán)境事務(wù)存在 的時(shí)候,則自動(dòng)加入其中。
一、依賴事務(wù)(Dependent Transaction)
依賴事務(wù)通過(guò)DependentTransaction類型表示,DependentTransaction定義如下。和CommittableTransaction一樣,DependentTransaction也是Transaction的子類。既然DependentTransaction依賴于現(xiàn)有的Transaction對(duì)象而存在,相當(dāng)于被依賴事務(wù)的子事務(wù),所以無(wú)法執(zhí)行對(duì)事務(wù)的提交,也自然不會(huì)定義Commit方法。但是,DependentTransaction具有一個(gè)唯一的方法成員:Complete。調(diào)用這個(gè)方法意味著向被依賴事務(wù)發(fā)送通知,表明所有與依賴事務(wù)相關(guān)的操作已經(jīng)完成。
1: [Serializable] 2: public sealed class DependentTransaction : Transaction 3: { 4: public void Complete(); 5: }1、通過(guò)DependentTransaction將異步操所納入現(xiàn)有事務(wù)
通過(guò)Transaction的 靜態(tài)屬性Current表示的環(huán)境事務(wù)保存在TLS(Thread Local Storage)中,所以環(huán)境事務(wù)是基于當(dāng)前線程的。這就意味著,即使環(huán)境事務(wù)存在,通過(guò)異步調(diào)用的操作也不可能自動(dòng)加入到當(dāng)前事務(wù)之中,因?yàn)樵诋惒骄€程 中感知不到環(huán)境事務(wù)的存在。在這種情況下,我們需要做的就是手工將當(dāng)前事務(wù)傳遞到另一個(gè)線程中,作為它的環(huán)境事務(wù)。通過(guò)依賴事務(wù)我們很容易實(shí)現(xiàn)這一點(diǎn)。
DependentTransaction通過(guò)Transaction的DependentClone方法創(chuàng)建,該方法具有一個(gè)DependentCloneOption枚舉類型的參數(shù),體現(xiàn)了被依賴的事務(wù)再上尚未接受到依賴事務(wù)的通知(調(diào)用Complete或者Rollback方法)得情況下,提交或者完成所采取的事務(wù)控制行為。DependentCloneOption提供了兩個(gè)選項(xiàng),BlockCommitUntilComplete表示被依賴事務(wù)會(huì)一直等待接收到依賴事務(wù)的通知或者超過(guò)事務(wù)設(shè)定的超時(shí)時(shí)限;而RollbackIfNotComplete則會(huì)直接將被依賴的事務(wù)回滾,并拋出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: }下面的代碼演示了如果通過(guò)依賴事務(wù),采用異步的方式進(jìn)行銀行轉(zhuǎn)賬操作。借助于組件ThreadPool,將主線程環(huán)境事務(wù)的依賴事務(wù)傳遞給異步操作代理,開(kāi)始異步操作的時(shí)候?qū)⒋艘蕾囀聞?wù)作為當(dāng)前的環(huán)境事務(wù),那么之后的操作將自動(dòng)在當(dāng)前事務(wù)下進(jìn)行。
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("轉(zhuǎn)帳失敗,錯(cuò)誤信息:{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: }由于在調(diào)用DependentClone方法創(chuàng)建依賴事務(wù)時(shí)指定的參數(shù)為 DependentCloneOption.BlockCommitUntilComplete,所以主線程在調(diào)用Commit方法提交事務(wù)的時(shí)候,由于 依賴事務(wù)尚未結(jié)束(調(diào)用Complete或者Rollback方法),在這里會(huì)一直等待。如果依賴事務(wù)的Complete或者Rollback一直沒(méi)有調(diào) 用,那么被依賴的事務(wù)會(huì)一直等到超出事務(wù)設(shè)置的超時(shí)時(shí)限。所以,對(duì)于基于BlockCommitUntilComplete選項(xiàng)創(chuàng)建的依賴事務(wù)來(lái)說(shuō),應(yīng)該 及時(shí)地調(diào)用Complete或者Rollback方法。
2、通過(guò)DependentTransaction實(shí)現(xiàn)事務(wù)型方法
這里所說(shuō)的事務(wù)型方法是指方法的執(zhí)行總是在事務(wù)中執(zhí)行。具體來(lái)講,有兩種不同的事務(wù)應(yīng)用場(chǎng)景:如果當(dāng)前不存在環(huán)境事務(wù),那么方法的執(zhí)行將在一個(gè)獨(dú)立的事務(wù)中執(zhí)行;反之,如果存在環(huán)境事務(wù),在方法執(zhí)行會(huì)自動(dòng)加入到環(huán)境事務(wù)之中。
比如說(shuō),存儲(chǔ)(Deposit)和提取(Withdraw)就是典型的事務(wù)型操作。對(duì)于單純的存取款的場(chǎng)景,應(yīng)該創(chuàng)建一個(gè)新的事務(wù)來(lái)控制存儲(chǔ)和提取 操作的執(zhí)行,以確保單一帳戶款項(xiàng)的數(shù)據(jù)一致性。如果在轉(zhuǎn)賬的場(chǎng)景中,應(yīng)在在轉(zhuǎn)賬開(kāi)始之前就創(chuàng)建一個(gè)新的事務(wù),讓提取和存儲(chǔ)的操作自動(dòng)加入到這個(gè)事務(wù)之中。
我們現(xiàn)在就結(jié)合可提交事務(wù)和依賴事務(wù)將Deposit和Withdraw兩個(gè)方法定義成事務(wù)型方法,為了相同代碼的重復(fù),在這里把事務(wù)控制部分定義在如下一個(gè)InvokeInTransaction靜態(tài)方法中:
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方法的參數(shù)是一個(gè)Action類型的代理(Delegate),表示具體的業(yè)務(wù)操作。在開(kāi)始的時(shí)候記錄下當(dāng) 前的環(huán)境事務(wù),當(dāng)整個(gè)操作結(jié)束之后應(yīng)該環(huán)境事務(wù)恢復(fù)成該值。如果存在環(huán)境事務(wù),則創(chuàng)建環(huán)境事務(wù)的依賴事務(wù),反之直接創(chuàng)建可提交事務(wù)。并將新創(chuàng)建的依賴事務(wù) 或者可提交事務(wù)作為當(dāng)前的環(huán)境事務(wù)。將目標(biāo)操作的執(zhí)行(action)放在try/catch中,當(dāng)目標(biāo)操作順利執(zhí)行后,調(diào)用依賴事務(wù)的Complete 方法或者可提交事務(wù)的Commit方法。如果拋出異常,則調(diào)用環(huán)境事務(wù)的Rollback進(jìn)行回滾。在finally塊中將環(huán)境事務(wù)恢復(fù)到之前的狀態(tài),并 調(diào)用Dispose方法對(duì)創(chuàng)建的事務(wù)進(jìn)行回收。
借助于InvokeInTransaction這個(gè)輔助方法,我們以事務(wù)型方法的形式定義了如下的兩個(gè)方法:Withdraw和Deposit,分別實(shí)現(xiàn)提取和存儲(chǔ)的操作。
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
在上面一節(jié),我結(jié)合可提交事務(wù)和依賴事務(wù),以及環(huán)境事務(wù)的機(jī)制提供了對(duì)事務(wù)型操作的實(shí)現(xiàn)。實(shí)際上,如果借助TransactionScope,相應(yīng)的代碼將會(huì)變得非常簡(jiǎn)單。下面的代碼中,通過(guò)TransactionScope對(duì)InvokeInTransaction進(jìn)行了改寫(xiě),從執(zhí)行效果來(lái)看這和原來(lái)的代碼完全一致。
1: static void InvokeInTransaction(Action action) 2: { 3: using (TransactionScope transactionScope = new TransactionScope()) 4: { 5: action(); 6: transactionScope.Complete(); 7: } 8: }通過(guò)InvokeInTransaction方法前后代碼的對(duì)比,我們可以明顯看到TransactionScope確實(shí)能夠使我們的事務(wù)控制變得非常的簡(jiǎn)單。實(shí)際上,在利用System.Transactions事務(wù)進(jìn)行編程的時(shí)候,我們一般不會(huì)使用到可提交事務(wù),對(duì)于依賴事務(wù)也只有在異步調(diào)用的時(shí)候會(huì)使用到,基于TransactionScope的事務(wù)編程方式才是我們推薦的。
正如其名稱所表現(xiàn)的一樣,TransactionScope就是為一組事務(wù)型操作創(chuàng)建一個(gè)執(zhí)行范圍,而這個(gè)范圍始于TransactionScope創(chuàng)建之時(shí),結(jié)束于TransactionScope被回收(調(diào)用Dispose方法)。在對(duì)TransactionScope進(jìn)行深入介紹之前,照例先來(lái)看看它的定義:
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實(shí)現(xiàn)了IDisposable接口,除了Dispose方法之外,僅僅具有一個(gè)唯一的方法:Complete。但是TransactionScope卻有一組豐富的構(gòu)造函數(shù)。我們先來(lái)看看這些構(gòu)造函數(shù)相應(yīng)的參數(shù)如何影響TransactionScope對(duì)事務(wù)控制的行為。
1、TransactionScopeOption
實(shí)際上前面一節(jié)中提供的InvokeInTransaction方法基本上體現(xiàn)了TransactionScope的內(nèi)部實(shí)現(xiàn)。也就是說(shuō),TransactionScope也是通過(guò)創(chuàng)建可提交事務(wù)或者依賴事務(wù),并將其作為事務(wù)范圍內(nèi)的環(huán)境事務(wù),從而將范圍的所有操作納入到一個(gè)事務(wù)之中。
通過(guò)在構(gòu)造函數(shù)中指定TransactionScopeOption類型的scopeOption參數(shù),控制TransactionScope當(dāng)環(huán)境事務(wù)存在的時(shí)候應(yīng)該采取怎樣的方式執(zhí)行事務(wù)范圍內(nèi)的操作。具有來(lái)講,具有三種不同的方式:
- 如果已經(jīng)存在環(huán)境事務(wù),則使用該環(huán)境事務(wù)。否則,在進(jìn)入范圍之前創(chuàng)建新的事務(wù);
- 總是為該范圍創(chuàng)建新事務(wù);
- 環(huán)境事務(wù)上下文在創(chuàng)建范圍時(shí)被取消。范圍中的所有操作都在無(wú)環(huán)境事務(wù)上下文的情況下完成。
TransactionScopeOption是一個(gè)枚舉,三個(gè)枚舉值Required、RequiresNew和Suppress依次對(duì)應(yīng)上面的三種行為。
1: public enum TransactionScopeOption 2: { 3: Required, 4: RequiresNew, 5: Suppress 6: }對(duì)于Required選項(xiàng),如果當(dāng)前存在環(huán)境事務(wù)TransactionScope會(huì) 創(chuàng)建環(huán)境事務(wù)的依賴事務(wù),負(fù)責(zé)創(chuàng)建可提交事務(wù),然后將創(chuàng)建的環(huán)境事務(wù)或者可提交事務(wù)作為事務(wù)范圍的環(huán)境事務(wù)。如對(duì)于RequiresNew選 項(xiàng),TransactionScope總是會(huì)創(chuàng)建可提交事務(wù)并將其作為事務(wù)范圍的環(huán)境事務(wù),意味著控制事務(wù)范圍內(nèi)操作的事務(wù)也當(dāng)前的環(huán)境事務(wù)已經(jīng)沒(méi)有任何 關(guān)系。如果Suppress選項(xiàng),TransactionScope會(huì)將事務(wù)范圍內(nèi)的環(huán)境事務(wù)設(shè)為空,意味著事務(wù)范圍內(nèi)的操作并不受事務(wù)的控制。
Required是默認(rèn)選項(xiàng),意味著事務(wù)范圍內(nèi)的事務(wù)將會(huì)作為當(dāng)前環(huán)境事務(wù)的一部分。如果你不希望某個(gè)操作被納入當(dāng)前的環(huán)境事務(wù),但是相應(yīng)的操作也 需要事務(wù)的控制以確保所操作數(shù)據(jù)的一致性。比如,當(dāng)業(yè)務(wù)邏輯失敗導(dǎo)致異常拋出,需要對(duì)相應(yīng)的錯(cuò)誤信息進(jìn)行日志記錄。對(duì)于日記的操作就可以放入基于RequiresNew選項(xiàng)創(chuàng)建TransactionScope中。對(duì)于一些不重要的操作(操作的錯(cuò)誤可被忽略),并且不需要通過(guò)事務(wù)來(lái)控制的操作,比如發(fā)送一些不太重要的通知,就可以采用Suppress選項(xiàng)。
2、TransactionOptions和EnterpriseServicesInteropOption
TransactionOptions在前面已經(jīng)提及,用于控制事務(wù)的超時(shí)時(shí)限和隔離級(jí)別。對(duì)于超時(shí)時(shí)限,你也可以選擇 TransactionScope相應(yīng)能夠的構(gòu)造函數(shù)以TimeSpan的形式指定。而對(duì)于事務(wù)的隔離級(jí)別,需要著重強(qiáng)調(diào)一點(diǎn):當(dāng)選擇 TransactionScopeOption.Required選項(xiàng)時(shí),TransactionScope指定的隔離級(jí)別必須與環(huán)境事務(wù)(如果有)相匹 配。
比如下面的例子中,我定義兩個(gè)嵌套的TransactionScope,外部的TransactionScope采用默認(rèn)的隔離級(jí)別,內(nèi)部在采用ReadCommitted隔離級(jí)別,當(dāng)執(zhí)行這段代碼的時(shí)候,會(huì)拋出如圖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: //事務(wù)型操作 7: innerScope.Complete(); 8: } 9: //事務(wù)型操作 10: outerScope.Complete(); 11: }圖1 隔離級(jí)別不一致導(dǎo)致的異常
實(shí)際上在System.Transactions事務(wù)機(jī)制被引入之前,像Enterprise Service主要依賴于基于COM+的分布式事務(wù)。TransactionScope通過(guò) EnterpriseServicesInteropOption控制System.Transactions事務(wù)如何與COM+的分布式事務(wù)進(jìn)行互操 作。具有來(lái)講具有如下三種互操作選項(xiàng),分別和EnterpriseServicesInteropOption三個(gè)枚舉值相對(duì)應(yīng):
- None:Transaction 和 Current 之間不同步;
- Automatic:搜索現(xiàn)有的 COM+ 上下文并與之同步(如該上下文存在);
- Full:System.EnterpriseServices 上下文(可通過(guò)調(diào)用 ContextUtil 類的靜態(tài)方法 Transaction 來(lái)檢索)和 System.Transactions 環(huán)境事務(wù)(可通過(guò)調(diào)用 Transaction 類的靜態(tài)方法 Current 來(lái)檢索)始終保持同步。這將引入性能損失,因?yàn)榭赡苄枰獎(jiǎng)?chuàng)建新的 System.EnterpriseServices 上下文。
3、事務(wù)提交和回滾
對(duì)于事務(wù)范圍中的事務(wù),無(wú)論是事務(wù)的提交(對(duì)于可提交事務(wù))、完成(依賴事務(wù))和回滾都是在Dispose方法中執(zhí)行的。 TransactionScope中定一個(gè)個(gè)私有的布爾類型字段(complete)表示事務(wù)是否正常結(jié)束。該成員的默認(rèn)值為False,當(dāng)調(diào)用 TransactionScope的Complete方法的時(shí)候會(huì)將此字段設(shè)置成True。當(dāng)Dispose執(zhí)行的時(shí)候,如果該字段的值為False,會(huì) 調(diào)用事務(wù)的Rollback方法對(duì)該事務(wù)實(shí)施回滾;否則會(huì)調(diào)用Commit方法(對(duì)于可提交事務(wù))對(duì)事務(wù)進(jìn)行提交或者調(diào)用Complete方法(依賴事 務(wù))通知被依賴的事務(wù)本地事務(wù)已經(jīng)正常完成。
除了執(zhí)行事務(wù)的提交、完成或者回滾之外,TransactionScope的Dispose方法還負(fù)責(zé)將環(huán)境事務(wù)回復(fù)到事務(wù)范圍開(kāi)始之前的狀態(tài)。在 調(diào)用Complete和Dispose之前,環(huán)境事務(wù)處于不可用的狀態(tài),如果此時(shí)試圖獲取環(huán)境事務(wù),會(huì)拋出異常。比如在下面的代碼中,在事務(wù)范圍內(nèi)部調(diào)用 Complete方法后,通過(guò)Transaction的Current靜態(tài)屬性獲取當(dāng)前環(huán)境事務(wù),會(huì)拋出圖2所示的InvalidOpertionException異常。
1: using (TransactionScope transactionScope = new TransactionScope()) 2: { 3: //其他事務(wù)操作 4: transactionScope.Complete(); 5: Transaction ambientTransaction = Transaction.Current; 6: }?圖2 在TransactionScope完成之后獲取環(huán)境事務(wù)導(dǎo)致的異常
總結(jié)
以上是生活随笔為你收集整理的谈谈分布式事务之三: System.Transactions事务详解[下篇]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PHP查看PECL模块包含的函数
- 下一篇: Errors occurred duri