前段時間一直在學習和研究.NET事務處理,慢慢的我發現可以使用事務處理來實現一種可逆的系統框架。這種框架在一些IT社區似乎還沒有見過,但是在我們日常開發中確實有這個需求。所以我花了點時間深入的研究了一下事務的原理和使用,實現了以事務為紐帶,以資源為操作對象的可逆框架。
這里我假設您對事務有了整體的認識,也對自定義事務管理器有過了解。[王清培版權所有,轉載請給出署名]
(可以參考本人的:.NET簡談事務本質論、.NET簡談自定義事務資源管理器)
1. 什么是可逆的程序框架
什么叫可逆的?程序的執行是可以被無限制回滾的。
什么叫可逆的框架?實現了對可逆功能的封裝,并能通過簡單的接口調用進行使用。框架可能有大有小,我想這么稱呼它是為了表達它的整體性和重要性。
那么到底可逆的需求在哪里?其實在我們開發程序的時候經常會使用事務來進行業務的控 制。比如刪除訂單,然后刪除訂單明細等等,對于這樣的要求很多,我們只能將邏輯控制在一個事務范圍內,不能在沒有事務性的邏輯代碼中編寫這種要求的業務功 能。等出現未知錯誤的時候在進行事務的回滾。
你也許會問,使用原來的事務處理不是也能進行回滾嗎?當然不是這么簡單的,我們使用事務回滾時只能將資源回滾到最初未進行事務處理前的狀態。(這里不僅僅指的是數據庫事務,而是全局的事務處理)我們用圖做個比較。[王清培版權所有,轉載請給出署名]
傳統的事務處理圖:
可逆的事務處理圖:
從這兩幅圖中我們可以很明顯的看出,傳統的事務處理在事務處理的過程當中無法控制中間數據,也就是說無法對事務處理進行分段,然后在進行統一的提交或回滾。
在可逆框架的事務處理里我們就可以控制事務的執行階段,在必要的時候我們只需提交或者回滾某一階段的數據。
1.1環境事務
在可逆框架的事務處理圖中,我們看到事務的開始,然后就進行下一步、下一步這樣的操作。在每進行一個下一步操作的時候,就是進入到了一個子事務里處理,在.NET中是可以進行事務的嵌套,其實也就是依賴事務Dependent Transaction實現。通過使用環境事務可以讓事務性感知代碼能自動的識別出您將要使用事務進行操作。所以在每進行下一步操作的時候,只有將當前環境事務切換為您將依賴的子事務才行。如果只是單純的使用依賴事務對象實例在使用,那么將無法進行諸多其他的事務處理。
2可逆框架的實現原理
由于我們只能控制自定義事務資源管理器的內部實現,所以我們在構建自己的數據處理時問題變的簡單多了。
實現可逆框架的核心技術就是使用依賴事務進行事務的克隆操作。將一個大的事務處理邏輯上切割成多了小的事務操作,然后在進行統一的提交或回滾。
在實現上其實就是將Committable Transaction對象進行包裝,實現簡單的調用接口。這里參照了環境代碼的概念,將對象的生命周期控制在代碼片段中。
2.1自定義資源管理器的實現
我們需要擴展IEnlistmentNotification接口的實現,加入對“上一步”、“下一步”的數據操作。
請看代碼:
- /***
- * author:深度訓練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Transactions;
- namespace ReversibleLib
- {
- /// <summary>
- /// 使代碼成為可逆框架的事務性代碼
- /// </summary>
- public class ReversibleManagerScope : IDisposable
- {
- /// <summary>
- /// 初始化ReversibleManagerScope新的實例
- /// </summary>
- public ReversibleManagerScope()
- {
- ReversibleManager._reversibleManager = new ReversibleManager();
- }
- /// <summary>
- /// 使用ReversibleManager對象構造ReversibleManagerScope使用范圍對象
- /// </summary>
- /// <param name="manager">ReversibleManager實例</param>
- public ReversibleManagerScope(ReversibleManager manager)
- {
- ReversibleManager._reversibleManager = manager;
- }
- /// <summary>
- /// 使用自定義資源管理器構造ReversibleManagerScope包裝的環境ReversibleManager.Current中的對象實例。
- /// </summary>
- /// <param name="source">IEnlistmentNotification資源管理器</param>
- public ReversibleManagerScope(IEnlistmentNotification source)
- {
- ReversibleManager._reversibleManager = new ReversibleManager(source);
- }
- /// <summary>
- /// 全局上下文ReversibleManager對象銷毀
- /// </summary>
- public void Dispose()
- {
- ReversibleManager._reversibleManager = null;
- }
- /// <summary>
- /// 完成整個操作的提交。該操作將提交事務棧中的所有依賴事務
- /// </summary>
- public void Completed()
- {
- ReversibleManager.Current.Commit();
- }
- }
- /// <summary>
- /// 可逆模塊的入口。
- /// ReversibleManager對事務對象的封裝,實現階段性的事務提交和回滾。
- /// </summary>
- public class ReversibleManager
- {
- #region 上下文靜態ReversibleManager實例
- /// <summary>
- /// 持有對可逆框架的對象引用
- /// </summary>
- internal static ReversibleManager _reversibleManager;
- /// <summary>
- /// 獲取當前上下文中可逆框架
- /// </summary>
- public static ReversibleManager Current
- {
- get { return _reversibleManager; }
- }
- #endregion
- #region 構造對象
- /// <summary>
- /// 默認構造函數
- /// </summary>
- public ReversibleManager() { }
- /// <summary>
- /// 表示可提交的事務(主事務)
- /// </summary>
- private CommittableTransaction _commiTransaction;
- /// <summary>
- /// 支持兩階段提交協議的資源管理器(主資源管理器)
- /// </summary>
- private IEnlistmentNotification _resourceManager;
- /// <summary>
- /// 重載構造函數,使用自定義資源管理器構造可逆模塊的開始。
- /// </summary>
- /// <param name="resource">IEnlistmentNotification接口對象</param>
- public ReversibleManager(IEnlistmentNotification resource)
- {
- _resourceManager = resource;
- InitLoad(IsolationLevel.Serializable);
- }
- /// <summary>
- /// 重載構造函數,使用自定義資源管理器、內部事務范圍的事務隔離級別構造可逆模型的開始。
- /// </summary>
- /// <param name="resource">IEnlistmentNotification接口對象</param>
- /// <param name="isolationlevel">IsolationLevel枚舉成員</param>
- public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel)
- {
- _resourceManager = resource;
- InitLoad(isolationlevel);
- }
- /// <summary>
- /// 事務初始化階段的參數對象
- /// </summary>
- TransactionOptions _options;
- /// <summary>
- /// 重載構造函數,使用自定義資源管理器、內部事務范圍的事務隔離級別、事務超時時間范圍構造可逆模塊的開始。
- /// </summary>
- /// <param name="resource">IEnlistmentNotification接口對象</param>
- /// <param name="isolationlevel">IsolationLevel枚舉成員</param>
- /// <param name="span">TimeSpan時間范圍</param>
- public ReversibleManager(IEnlistmentNotification resource, IsolationLevel isolationlevel, TimeSpan span)
- {
- _options = new TransactionOptions();
- _options.Timeout = span;
- InitLoad(isolationlevel);
- }
- /// <summary>
- /// 構造CommittableTransaction對象實例。
- /// </summary>
- /// <param name="level">事務隔離級別</param>
- private void InitLoad(IsolationLevel level)
- {
- if (_options == null)
- _options = new TransactionOptions();
- _options.IsolationLevel = level;
- _commiTransaction = new CommittableTransaction(_options);
- _commiTransaction.EnlistVolatile(_resourceManager, EnlistmentOptions.None);
- //作為事務棧的頭開始整個可逆結構。
- _tranStack.Push(_commiTransaction);//壓入事務棧
- _resourceStack.Push(_resourceManager);//壓入資源棧
- //設置環境事務,讓所有支持事務性感知框架的代碼都能執行。
- Transaction.Current = _commiTransaction;
- }
- #endregion
- /// <summary>
- /// 事務棧,依次存放事務。
- /// </summary>
- private System.Collections.Generic.Stack<Transaction> _tranStack = new Stack<Transaction>();
- /// <summary>
- /// 資源棧,依次存放事務使用的資源。
- /// </summary>
- private System.Collections.Generic.Stack<IEnlistmentNotification> _resourceStack = new Stack<IEnlistmentNotification>();
- /// <summary>
- /// 階段性事件委托
- /// </summary>
- /// <param name="tran">Transaction環境事務</param>
- public delegate void PhaseHanlder(System.Transactions.Transaction tran);
- /// <summary>
- /// 下一步事件
- /// </summary>
- public event PhaseHanlder NextEvent;
- /// <summary>
- /// 上一步事件
- /// </summary>
- public event PhaseHanlder PreviousEvent;
- /// <summary>
- /// 開始下一步操作
- /// </summary>
- /// <typeparam name="S">IEnlistmentNotification接口實現</typeparam>
- /// <param name="level">IsolationLevel事務的隔離級別(對全局事務處理設置)</param>
- /// <param name="source">下一步操作的自定義數據管理器</param>
- public void Next<S>(IsolationLevel level, S source)
- where S : class,IEnlistmentNotification, new()
- {
- Transaction tran = _tranStack.Peek();//獲取事務棧的頂端事務
- if (tran == null)
- tran = Transaction.Current;//主事務
- DependentTransaction depentran = tran.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
- //將本次事務處理的資源管理器壓入資源棧中
- depentran.EnlistVolatile(source, EnlistmentOptions.None);
- _tranStack.Push(depentran);
- _resourceStack.Push(source);
- //切換環境事務場景
- Transaction.Current = depentran;
- if (NextEvent != null)
- if (NextEvent.GetInvocationList().Length > 0)
- NextEvent(Transaction.Current);
- }
- /// <summary>
- /// 返回上一步操作
- /// </summary>
- /// <typeparam name="T">需要接受的數據對象類型</typeparam>
- /// <param name="refadd">需要接受的數據對象引用</param>
- public void Previous<T>(out T refadd) where T : class,new()
- {
- Transaction tran = _tranStack.Pop();
- if (tran == null)//頂層事務
- Transaction.Current.Rollback();
- // tran.Rollback();//回滾本事務,將觸發所有克隆事務的回滾。
- if (PreviousEvent != null)
- if (PreviousEvent.GetInvocationList().Length > 0)
- {
- //設置上一步數據對象
- refadd = (_resourceStack.Pop() as IReversibleGetResourceData<T>).GetPreviousData();
- PreviousEvent(Transaction.Current);
- return;
- }
- refadd = new T();//事務處理異常
- }
- /// <summary>
- /// 提交事物堆棧中的所有事物
- /// </summary>
- public void Commit()
- {
- if (Transaction.Current is DependentTransaction)
- (Transaction.Current as DependentTransaction).Complete();
- for (int i = 0; i < _tranStack.Count - 1; i++)
- {
- //依賴事務
- (_tranStack.Pop() as DependentTransaction).Complete();
- }
- //提交事務,主事務。必須進行克隆主體的提交才能完成所有階段的操作。
- (_tranStack.Pop() as CommittableTransaction).Commit();
- }
- /// <summary>
- /// 回滾事物堆棧中的所有事物
- /// </summary>
- public void RollBack()
- {
- if (Transaction.Current is DependentTransaction)
- (Transaction.Current as DependentTransaction).Rollback();
- for (int i = 0; i < _tranStack.Count - 1; i++)
- {
- //依賴事務
- (_tranStack.Pop() as DependentTransaction).Rollback();
- }
- //提交事務,主事務。必須進行克隆主體的提交才能完成所有階段的操作。
- (_tranStack.Pop() as CommittableTransaction).Rollback();
- }
- }
- }
2.2可逆框架的入口實現
我們需要簡單的調用就能方便的使用可逆功能,不能以一種新的方式使用。所以這里借鑒了Transaction Scope的設計思想。
請看代碼:
- /***
- * author:深度訓練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Transactions;
- namespace ReversibleLib
- {
- /// <summary>
- /// 可逆范圍內的資源管理器。
- /// 可以使用該類對易失性資源進行事務范圍內的管理。在事務操作范圍內進行可逆操作。
- /// </summary>
- /// <typeparam name="T">需要管理的資源類型</typeparam>
- /// <typeparam name="Xcopy">資源在使用、恢復過程中的數據復制對象。</typeparam>
- public class ReResourceManager<T, Xcopy> : IEnlistmentNotification, IReversibleGetResourceData<T>
- where T : class, new()
- where Xcopy : class
- {
- /// <summary>
- /// 私有字段。資源的持久引用。
- /// </summary>
- T _commitfrontvalue;
- /// <summary>
- /// 私有字段。事務性操作數據對象。
- /// </summary>
- T _rollbackfrontvalue = new T();
- /// <summary>
- /// 保存數據復制對象。
- /// </summary>
- Xcopy _copy;
- /// <summary>
- /// 泛型約束需要,內部使用。
- /// </summary>
- public ReResourceManager() { }
- /// <summary>
- /// 資源管理器內部名稱。便于追蹤
- /// </summary>
- public string Name { get; set; }
- /// <summary>
- /// 重載默認構造函數,使用資源類型和數據復制對象初始化資源管理器。
- /// </summary>
- public ReResourceManager(T t, Xcopy icopy)
- {
- (icopy as IResourceCopy<T>).Copy(_rollbackfrontvalue, t);
- _commitfrontvalue = t;
- _copy = icopy;
- }
- #region IEnlistmentNotification 成員
- public void Prepare(PreparingEnlistment preparingEnlistment)
- {
- preparingEnlistment.Prepared();
- }
- public void Commit(Enlistment enlistment)
- {
- enlistment.Done();
- }
- public void InDoubt(Enlistment enlistment)
- {
- enlistment.Done();
- }
- public void Rollback(Enlistment enlistment)
- {
- (_copy as IResourceCopy<T>).Copy(_commitfrontvalue, _rollbackfrontvalue);//回滾事務
- enlistment.Done();
- }
- #endregion
- #region IReversibleGetResourceData<T> 成員
- T IReversibleGetResourceData<T>.GetPreviousData()
- {
- T result = new T();
- (_copy as IResourceCopy<T>).Copy(result, _rollbackfrontvalue);
- return result;
- }
- T IReversibleGetResourceData<T>.GetNextData()
- {
- T result = new T();
- (_copy as IResourceCopy<T>).Copy(result, _commitfrontvalue);
- return result;
- }
- #endregion
- }
- }
3.示例
這里我使用了一個簡單的String Builder作為資源管理器需要管理的對象。
請看代碼:
- /***
- * author:深度訓練
- * blog:http://wangqingpei557.blog.51cto.com/
- * **/
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Data;
- using System.Transactions;
- using ReversibleLib;
- namespace ConsoleApplication1
- {
- class Program
- {
- static void Main(string[] args)
- {
- //構造數據
- StringBuilder strbuilder = new StringBuilder();
- strbuilder.Append("0");//初始數據為0
- //資源管理器
- ReResourceManager<StringBuilder, StringBuilderCopy> strResource =
- new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder, new StringBuilderCopy());
- strResource.Name = "0資源管理器";
- //開始進入可逆框架處理環境
- using (ReversibleManagerScope reversible = new ReversibleManagerScope(strResource))
- {
- try
- {
- ReversibleManager.Current.PreviousEvent += new ReversibleManager.PhaseHanlder(Current_PreviousEvent);
- ReversibleManager.Current.NextEvent += new ReversibleManager.PhaseHanlder(Current_NextEvent);
- strbuilder.Append("1");//首次修改數據為01
- //獲取下一步操作的數據
- StringBuilder strbuilder2 = (strResource as IReversibleGetResourceData<StringBuilder>).GetNextData();
- //構造下一步操作的自定義資源管理器
- ReResourceManager<StringBuilder, StringBuilderCopy> strResource2 =
- new ReResourceManager<StringBuilder, StringBuilderCopy>(strbuilder2, new StringBuilderCopy());
- strResource2.Name = "2資源管理器";
- ReversibleManager.Current.Next<ReResourceManager<StringBuilder, StringBuilderCopy>>(
- System.Transactions.IsolationLevel.Serializable, strResource2);
- strbuilder2.Append("2");//第二步修改數據為012
- //返回上一步,也就是回滾對數據進行“2”設置的前一個狀態
- StringBuilder strbuilder3;
- ReversibleManager.Current.Previous<StringBuilder>(out strbuilder3);//獲取上一步使用的數據,這里應該是01
- reversible.Completed();//提交所有操作
- Console.WriteLine(strbuilder3);
- }
- catch (Exception err)
- { Console.WriteLine(err.Message); ReversibleManager.Current.RollBack(); }
- }
- Console.ReadLine();
- }
- static void Current_NextEvent(Transaction tran)
- {
- Console.WriteLine("下一步:" + tran.TransactionInformation.LocalIdentifier);
- Console.WriteLine("下一步:" + tran.TransactionInformation.DistributedIdentifier);
- }
- static void Current_PreviousEvent(Transaction tran)
- {
- Console.WriteLine("上一步:" + tran.TransactionInformation.LocalIdentifier);
- Console.WriteLine("上一步:" + tran.TransactionInformation.DistributedIdentifier);
- }
- }
- }
這里我使用0作為資源的初始數據,然后進入到第一個環節,我將它附加了1,然后進入到第二個環節,我將它附加了2,這里應該是012了,但是下面我突然又返回到了上一步,所以最后的數據應該是01。如果我們需要使用復雜的數據對象,如常用的Data Table類型,我們一般都是用它來展現一組數據,然后對這組數據進行一系列的操作。
總結:
這篇文章主要是想介紹一下事務的另一種使用方式,對可逆框架的設計方向算是一個拋磚引玉吧,希望大家用的著。