閱讀516 返回首頁    go 阿裏雲 go 技術社區[雲棲]


如何讓普通變量也支持事務回滾?

有一次和人談起關於事務的話題,談到怎樣的資源才能事務型資源。除了我們經常使用的數據庫、消息隊列、事務型文件係統(TxF)以及事務性注冊表(TxR)等,還有那些資源直接可以納入事務進行狀態的管理呢?我說如果我們按照.NET事務模型的規範對相應的資源進行合理的封裝,原則上我們可以讓任何可編程的資源成為事務型資源。本篇文章中,我將通過簡單的編程將一個普通的變量變成支持事務,讓變量的值也可以回滾,以確保事務前後的數據一致性。

一、什麼是事務型的變量

本文中所說的事務型變量指的是這樣的變量:

  • 在事務開始前,變量的初始值會被保存;
  • 在事務中對變量的賦值隻有在事務被成功提交後才會真正賦值給變量;
  • 如果事務中止導致回滾,變量的值將會恢複到事務開始之前的狀態。

上麵的對事務型變量的描述可以通過下麵的程序來體現:變量v在初始化時被賦值為1。然後通過TransactionScope開始一個事務,並將變量納入該事務之中。在事務範圍內將值賦值為2,然後調用DoSomething方法,並提交事務。如果DoSomething執行過程中拋出異常,整個事務將會回滾。當整個事務中止回滾後,變量v的值回複到事務開始之前的狀態,即值為1。

   1: static void Main(string[] args)
   2: {
   3:     TransactionalVariable<int> v = new TransactionalVariable<int>(1);
   4:     try
   5:     {
   6:         using (TransactionScope transactionScope = new TransactionScope())
   7:         {
   8:             Transaction.Current.EnlistPromotableSinglePhase(v);
   9:             v.Value = 2;
  10:             DoSomething();
  11:             transactionScope.Complete();
  12:         }
  13:     }
  14:     catch
  15:     { }
  16:     Debug.Assert(v.Value == 1);
  17: }

二、簡單談談System.Transactions事務模型

事務型變量的性質已經說得很清楚了,現在根本的任務就是如何來定義這樣的一個事務性變量類型,即上麵實例程序中的TransactionalVariable<T>類型。不過在這之前,我們有必要簡單看談談System.Transactions的事務模型。對於所有的事務參與者,按照各自在整個事務生命周期各個階段所承擔的職能,大致扮演著如下三種角色:

  • 應用(Application)、服務(Service)或者組件(Component):代表用戶程序,或者是承載著某功能的服務(Service)或者組件(Component);
  • 資源管理器(RM:Resource Manager):代表用於管理具體事務型資源的軟件程序,比如數據庫或者隊列(MSMQ)等;
  • 事務管理器(TM: Transaction Manager):代表管理整個事務的中間件程序,為應用和資源管理器提供基本的事務控製服務。

關於System.Transactions具體的事務管理模型,可以參考我的文章《談談分布式事務之二:基於DTC的分布式事務管理模型[上篇]》,在這裏就不在贅言介紹了。總而言之,隻要我們能夠為變量編寫相應的“資源管理器”,我們就能夠將其納入到System.Transactions.Transaction之中。在System.Transactions體係中,編寫事務管理器是一件很簡單的事情,一種非常直接的方式就是實現IPromotableSinglePhaseNotification這麼一個接口。實例代碼中使用的TransactionalVariable<T>類型就是這麼定義的。

三、通過實現IPromotableSinglePhaseNotification接口定義TransactionalVariable<T>

在具體介紹TransactionalVariable<T>的定義之前,我們不妨來看看IPromotableSinglePhaseNotification接口是如何定義的。下麵的代碼片斷反映了IPromotableSinglePhaseNotification的定義:加上從父接口繼承下來的成員,整個IPromotableSinglePhaseNotification接口一共具有4個方法成員。Initialize方法會在資源納入事務的時候被調用,用於執行一些初始化操作。SinglePhaseCommit、Rollback和Promote用於通知事務正在被提交、回滾和提升。

   1: public interface IPromotableSinglePhaseNotification : ITransactionPromoter
   2: {    
   3:     void Initialize();
   4:     void Rollback(SinglePhaseEnlistment singlePhaseEnlistment);
   5:     void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment);
   6: }
   7: public interface ITransactionPromoter
   8: {   
   9:     byte[] Promote();
  10: }

TransactionalVariable<T>直接實現了IPromotableSinglePhaseNotification接口,下麵是全部的定義。TransactionalVariable<T>中定義了兩個數據成員,字段_originalValue和屬性Value代表變量的初始值和當前值。

  • Initialize:將當前值賦給初始值,此時兩者具有相同的值;
  • Rollback:將初始值賦給當前值,並調用SinglePhaseEnlistment的Aborted方法通知終止事務,這意味著事務過程中對變量的修改都將丟失;
  • SinglePhaseCommit:將當前值賦給初始值,並調用SinglePhaseEnlistment的Committed方法通知提交事務,相當於將事務中對變量的修改正式生效;
  • Promote:由於我們隻打算讓我們的事務型變量支持本地事務的場景,並不對分布式事務提供支持,在這裏直接拋出一個異常
   1: using System.Transactions;
   2: namespace Artech.TransactionalObjects
   3: {
   4:     public class TransactionalVariable<T> : IPromotableSinglePhaseNotification
   5:     {
   6:         private T _originalValue;
   7:         public T Value { get; set; }
   8:         
   9:  
  10:         public TransactionalVariable(T variable)
  11:         {
  12:             this.Value = variable;
  13:         }
  14:  
  15:         public void Initialize()
  16:         {
  17:             _originalValue = this.Value;
  18:         }
  19:  
  20:         public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
  21:         {
  22:             this.Value = _originalValue;
  23:             singlePhaseEnlistment.Aborted();
  24:         }
  25:  
  26:         public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
  27:         {
  28:             _originalValue = this.Value;
  29:             singlePhaseEnlistment.Committed();
  30:         }
  31:  
  32:         public byte[] Promote()
  33:         {
  34:             throw new TransactionException("TransactionalVariable just only support local transaction.");
  35:         }
  36:     }
  37: }

以上就是所有的實現,並沒有什麼特別之處,僅僅就是通過實現對初始值的緩存,進而實現在事務中止時能夠將值恢複到之前的狀態。你可以通過這裏下載該例子。不過,這個例子僅僅是一個簡單的模擬演示而已,還有很多不足之處。比如事務四大屬性的隔離性在TransactionalVariable<T>就不能體現出來。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 14:34:16

  上一篇:go  基於CallContextInitializer的WCF擴展導致的嚴重問題
  下一篇:go  Unity.Interception System (2.0)