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


WCF技術剖析之三十三:你是否了解WCF事務框架體係內部的工作機製?[上篇]

WCF事務編程主要涉及到這麼三個方麵:通過服務(操作)契約確定TransactionFlow的策略;通過事務綁定實現事務流轉;通過服務操作行為控製事務的自動登記(Enlistment)行為,以及對事務超時時限、隔離級別和實例行為的設定。那麼,在WCF內部這三者之間究竟是如何通過相互協作實現分布式事務的呢?這就是本篇文章所要講述的內容,先來看看應用於服務契約中的某個操作的TransactionFlowAttribute到底為我們作了什麼。

一、TransactionFlowAttribute:將TransactionFlow選項存入綁定上下文(BindingContext)

通過前麵的介紹,我們知道了通過將TransactionFlowAttribute特性應用於服務契約的某個操作,實際上是指定了基於該操作事務流轉的策略(NotAllowed、Allowed和Mandatory)。綁定最終需要根據設置的TransactionFlow選項,決定是否對事務實施流轉,即客戶端是否需要將當前事務進行序列化並嵌入到出棧消息(Outgoing Message)中發送出去;服務端是否需要從入棧消息(Incoming Message)中提取事務相關信息並重新構建事務。那麼兩者是如何聯係在一起的呢?

在介紹TransactionFlowAttribute特性的時候,我們說過TransactionFlowAttribute並不是一個簡單的特性,它是一個實現了IOperationBehavior接口的操作行為。IOperationBehavior接口定義了4個方法(ApplyClientBehavior、ApplyDispatchBehavior、Validate和AddBindingParameters),處理AddBindingParameters,其餘三個都是空操作。大體上,TransactionFlowAttribute的定義可以通過下麵的偽代碼表示。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public sealed class TransactionFlowAttribute : Attribute, IOperationBehavior
   3: {    
   4:     public TransactionFlowAttribute(TransactionFlowOption transactions);
   5:     void IOperationBehavior.AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
   6:     {
   7:         //將TransactionFlow選項(NotAllwed、Allowed和Mandatory)存入綁定上下文
   8:         AddTransactionFlowOptionInBindingContext();
   9:     }
  10:     void IOperationBehavior.ApplyClientBehavior(OperationDescription description, ClientOperation proxy){ }
  11:     void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch) { }
  12:     void IOperationBehavior.Validate(OperationDescription description) { }
  13:  
  14:     public TransactionFlowOption Transactions { get; }
  15: }

也就是通過AddBindingParameters這個方法,將設置的TransactionFlow選項傳入了綁定上下文(Binding Context),使得通過綁定創建的信道(Channel)可以獲取該選項,並根據相應的值控製自身消息處理的行為。在這裏,真正使用到該TransactionFlow選項的信道,就是通過事務綁定的TransactionFlowBindingElement創建的事務信道。關於綁定、綁定元素和信道之間的關係,在《WCF技術剖析(卷1)》的第3章有詳細的介紹。

二、 事務綁定:實現事務的流轉

由於消息交換是WCF進行通信的唯一手段,所以事務的流轉最終需要將事務本身作為消息的一部分進行傳輸。WCF采用不同的事務處理協議(OleTx和WS-AT),反映在消息交換上就是采用怎樣的格式對事務進行格式化,以及將格式化的事務信息與消息(主要是SOAP)進行綁定。在WCF的整個事務處理體係結構中,事務的格式化和消息綁定的操作通過事務綁定實現。

我們所說的事務綁定就是包含有TransactionFlowBindingElement綁定元素,並且TransactionFlow開關被開啟的綁定。對於客戶端來說,TransactionFlowBindingElement創建事務信道工廠(TransactionChannelFactory),而基於不同的信道形狀(Channel Shape)和對會話的支持,事務信道工廠會創建相應的事務信道,比如事務輸出信道(TransactionOutputChannel)、事務請求信道(TransactionRequestChannel)和事務雙工信道(TransactionDuplexChannel)等等。對於服務端來說,TransactionFlowBindingElement會創建事務信道監聽器(TransactionChannelListener),與客戶端類似,事務信道監聽器也會創建基於不同信道形狀(Channel Shape)和對會話的支持的事務信道,比如事務輸入信道(TransactionInputChannel)、事務回複信道(TransactionReplyChannel)和事務雙工信道(TransactionDuplexChannel)等。事務流轉相關的綁定元素、綁定管理器(信道工廠和信道監聽器)和信道之間的關係如圖1所示。

image

圖1 事務流轉相關的綁定元素、信道管理器和信道結構

客戶端的事務信道需要將當前事務寫入消息,而服務端的事務信道則需要將流入的事務從服務中讀出來。WCF將事務的讀寫操作定義在一個稱為TransactionFormatter的類型中。不過,這是一個內部(Internal)類型不能直接使用。TransactionFormatter的定義大體上如下麵的代碼所示,其中ReadTransaction和WriteTransaction分別實現對事務的讀取和寫入操作。事務通過TransactionInfo對象的形式被讀取出來,TransactionInfo是也是一個內部對象,我們可以通過調用UnmarshalTransaction得到真正的Transaction對象。

   1: internal abstract class TransactionFormatter
   2: {   
   3:     //其他成員
   4:     public abstract TransactionInfo ReadTransaction(Message message);
   5:     public abstract void WriteTransaction(Transaction transaction, Message message);
   6: }
   7: internal abstract class TransactionInfo
   8: {
   9:     //其他成員
  10:     public abstract Transaction UnmarshalTransaction();
  11: }

如果采用不同事務處理協議,相同的事務需要按照不同的方式進行格式化,所以WCF事務體係內部創建了繼承自TransactionFormatter的三個具體的TransactionFormatter類型:OleTxTransactionFormatter、WsatTransactionFormatter10和WsatTransactionFormatter11,它們分別對應於WCF支持的三種事務處理協議:OleTx、WS-AT 1.0和WS-AT 1.1。

我想很多人很想知道一個Transaction對象被不同的TransactionFormatter寫入到Message對象後,Message具有怎樣的格式呢?接下來我們通過一個簡單的實例來演示。

三、實例演示:通過TransactionFormatter進行事務的寫入

本實例是一個簡單的控製台應用,我們將用它來演示模擬事務綁定是如何將當前事務寫入消息。由於上麵提到的TransactionFormatter和TransactionInfo都是內部類型,我們隻能通過反射的方式使用它們。為此,我寫了一個簡單的工具類型ReflectUtil,用於通過反射的方式創建對象和調用某個方法,原理很簡單,在這裏就不多作介紹了。

   1: internal static class ReflectUtil
   2: {
   3:     public static object CreateInstance(string typeAssemblyQName, params object[] parameters)
   4:     {
   5:         Type typeofInstance = Type.GetType(typeAssemblyQName);
   6:         BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
   7:         return Activator.CreateInstance(typeofInstance, bindingFlags, null, parameters, null);
   8:     }
   9:     public static object Invoke(string methodName, object targetInstance, params object[] parameters)
  10:     {
  11:         BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
  12:         return targetInstance.GetType().GetMethod(methodName, bindingFlags).Invoke(targetInstance, parameters);
  13:     }
  14: }

然後我們創建我們自己的TransactionFormatter類型,它具有相同的方法ReadTransaction和WriteTransaction,ReadTransaction的返回類型直接使Transaction。相應的事務協議通過構造函數指定,事務協議決定了最終創建的真正TransactionFormatter的類型。真正的TransactionFormatter通過ReflectUtil創建,相應的方法也通過ReflectUtil調用。

   1: public class TransactionFormatter
   2: {
   3:     const string OleTxFormatterType = "System.ServiceModel.Transactions.OleTxTransactionFormatter,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   4:     const string Wsat10FormatterType = "System.ServiceModel.Transactions.WsatTransactionFormatter10,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   5: const string Wsat11FormatterType = "System.ServiceModel.Transactions.WsatTransactionFormatter11,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   6:     
   7:     public object InternalFormatter
   8: { get; private set; }
   9:  
  10:     public TransactionFormatter(TransactionProtocol transactionProtocol)
  11:     {
  12:         if (transactionProtocol == TransactionProtocol.OleTransactions)
  13:         {
  14:             this.InternalFormatter = ReflectUtil.CreateInstance(OleTxFormatterType);
  15:         }
  16:         else if (transactionProtocol == TransactionProtocol.WSAtomicTransactionOctober2004)
  17:         {
  18:             this.InternalFormatter = ReflectUtil.CreateInstance(Wsat10FormatterType);
  19:         }
  20:         else
  21:         {
  22:             this.InternalFormatter = ReflectUtil.CreateInstance(Wsat11FormatterType);
  23:         }
  24:     }
  25:  
  26:     public Transaction ReadTransaction(Message message)
  27:     {
  28:         object transInfo = ReflectUtil.Invoke("ReadTransaction", this.InternalFormatter, message);
  29:         return ReflectUtil.Invoke("UnmarshalTransaction", transInfo) as Transaction;
  30:     }
  31:  
  32:     public void WriteTransaction(Transaction transaction, Message message)
  33:     {
  34:         ReflectUtil.Invoke("WriteTransaction", this.InternalFormatter, transaction, message);
  35:     }
  36: }

然後我們基於三種不同的事務處理協議創建了相應的TransactionFormatter對象,並將相同的Transaction對象寫入到一個Message對象中,並且Message的主體部分為Transaction對象本身。最終的Message對象被寫入到3個XML文文件中。

   1: static void Main(string[] args)
   2: {
   3:     using (TransactionScope transactionScope = new TransactionScope())
   4:     {
   5:         WriteTransaction(TransactionProtocol.OleTransactions, Transaction.Current, "oletx.xml");
   6:         WriteTransaction(TransactionProtocol.WSAtomicTransactionOctober2004, Transaction.Current, "wsat10.xml");
   7:         WriteTransaction(TransactionProtocol.WSAtomicTransaction11, Transaction.Current, "wsat11.xml");
   8:     }
   9: }
  10:  
  11: static void WriteTransaction(TransactionProtocol transactionProtocol, Transaction transaction,  string fileName)
  12: {
  13:     string action = string.Format("https://www.artech.com/transactionformat/{0}", transactionProtocol.GetType().Name);
  14:     Message message = Message.CreateMessage(MessageVersion.Default, action, Transaction.Current);
  15:     TransactionFormatter formatter = new TransactionFormatter(transactionProtocol);
  16:     formatter.WriteTransaction(Transaction.Current, message);
  17:     using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
  18:     {
  19:         message.WriteMessage(writer);
  20:     }
  21:     Process.Start(fileName);
  22: }

程序成功運行後,你將會得到三個表示Message對象的XML文件,它們的內容如下。有興趣的讀者可以結合相應事務處理協議規範,認真分析一下對應消息的結構,相信可以加深你對事務處理協議的理解。

OleTx.xml(OleTx)

   1: <s:Envelope xmlns:a="https://www.w3.org/2005/08/addressing" xmlns:s="https://www.w3.org/2003/05/soap-envelope">
   2:   <s:Header>
   3:     <a:Action s:mustUnderstand="1">https://www.artech.com/transactionformat/OleTransactionsProtocol</a:Action>
   4:     <OleTxTransaction s:mustUnderstand="1" d3p1:Expires="59392" xmlns:d3p1="https://schemas.xmlsoap.org/ws/2004/10/wscoor" xmlns="https://schemas.microsoft.com/ws/2006/02/tx/oletx">
   5:       <PropagationToken>AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgBiWWwMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAAAAAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAAAAABAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</PropagationToken>
   6:     </OleTxTransaction>
   7:   </s:Header>
   8:   <s:Body>
   9:     <Transaction xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:x="https://www.w3.org/2001/XMLSchema" xmlns:d3p3="https://schemas.datacontract.org/2004/07/System.Transactions.Oletx" z:FactoryType="d3p3:OletxTransaction" xmlns:z="https://schemas.microsoft.com/2003/10/Serialization/" xmlns="https://schemas.datacontract.org/2004/07/System.Transactions">
  10:       <OletxTransactionPropagationToken i:type="x:base64Binary" xmlns="">AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgDRiGgMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAAAAAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAAAAABAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</OletxTransactionPropagationToken>
  11:     </Transaction>
  12:   </s:Body>
  13: </s:Envelope>

Wsat10.xml(WS-AT 1.0):

   1: <s:Envelope xmlns:a="https://www.w3.org/2005/08/addressing" xmlns:s="https://www.w3.org/2003/05/soap-envelope">
   2:   <s:Header>
   3:     <a:Action s:mustUnderstand="1">https://www.artech.com/transactionformat/ WSAtomicTransactionOctober2004Protocol</a:Action>
   4:     <CoordinationContext s:mustUnderstand="1" xmlns:mstx="https://schemas.microsoft.com/ws/2006/02/transactions" xmlns="https://schemas.xmlsoap.org/ws/2004/10/wscoor">
   5:       <wscoor:Identifier xmlns:wscoor="https://schemas.xmlsoap.org/ws/2004/10/wscoor">urn:uuid:ff769ce4-4cbf-40cc-a843-6477ec174635</wscoor:Identifier>
   6:       <Expires>59392</Expires>
   7:       <CoordinationType>https://schemas.xmlsoap.org/ws/2004/10/wsat</CoordinationType>
   8:       <RegistrationService>
   9:         <Address xmlns="https://schemas.xmlsoap.org/ws/2004/08/addressing">https://jinnan-vista/WsatService/Registration/Coordinator/ /</Address>
  10:         <ReferenceParameters xmlns="https://schemas.xmlsoap.org/ws/2004/08/addressing">
  11:           <mstx:RegisterInfo>
  12:             <mstx:LocalTransactionId>ff769ce4-4cbf-40cc-a843-6477ec174635</mstx:LocalTransactionId>
  13:           </mstx:RegisterInfo>
  14:         </ReferenceParameters>
  15:       </RegistrationService>
  16:       <mstx:IsolationLevel>0</mstx:IsolationLevel>
  17:       <mstx:LocalTransactionId>ff769ce4-4cbf-40cc-a843-6477ec174635</mstx:LocalTransactionId>
  18:       <PropagationToken xmlns="https://schemas.microsoft.com/ws/2006/02/tx/oletx">AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgDtiGgMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAAAAAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAAAAABAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</PropagationToken>
  19:     </CoordinationContext>
  20:   </s:Header>
  21:   <s:Body>
  22:     <Transaction xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:x="https://www.w3.org/2001/XMLSchema" xmlns:d3p3="https://schemas.datacontract.org/2004/07/System.Transactions.Oletx" z:FactoryType="d3p3:OletxTransaction" xmlns:z="https://schemas.microsoft.com/2003/10/Serialization/" xmlns="https://schemas.datacontract.org/2004/07/System.Transactions">
  23:       <OletxTransactionPropagationToken i:type="x:base64Binary" xmlns="">AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgAAAAAMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAJVwAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAA//8BAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</OletxTransactionPropagationToken>
  24:     </Transaction>
  25:   </s:Body>
  26: </s:Envelope>

Wsat11.xml(WS-AT 1.1)

   1: <s:Envelope xmlns:a="https://www.w3.org/2005/08/addressing" xmlns:s="https://www.w3.org/2003/05/soap-envelope">
   2:   <s:Header>
   3:     <a:Action s:mustUnderstand="1">https://www.artech.com/transactionformat/WSAtomicTransaction11Protocol</a:Action>
   4:     <CoordinationContext s:mustUnderstand="1" xmlns:mstx="https://schemas.microsoft.com/ws/2006/02/transactions" xmlns="https://docs.oasis-open.org/ws-tx/wscoor/2006/06">
   5:       <wscoor:Identifier xmlns:wscoor="https://docs.oasis-open.org/ws-tx/wscoor/2006/06">urn:uuid:ff769ce4-4cbf-40cc-a843-6477ec174635</wscoor:Identifier>
   6:       <Expires>59392</Expires>
   7:       <CoordinationType>https://docs.oasis-open.org/ws-tx/wsat/2006/06</CoordinationType>
   8:       <RegistrationService>
   9:         <a:Address>https://jinnan-vista/WsatService/Registration/Coordinator11</a:Address>
  10:         <a:ReferenceParameters>
  11:           <mstx:RegisterInfo>
  12:             <mstx:LocalTransactionId>ff769ce4-4cbf-40cc-a843-6477ec174635</mstx:LocalTransactionId>
  13:           </mstx:RegisterInfo>
  14:         </a:ReferenceParameters>
  15:       </RegistrationService>
  16:       <mstx:IsolationLevel>0</mstx:IsolationLevel>
  17:       <mstx:LocalTransactionId>ff769ce4-4cbf-40cc-a843-6477ec174635</mstx:LocalTransactionId>
  18:       <PropagationToken xmlns="https://schemas.microsoft.com/ws/2006/02/tx/oletx">AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgAAMAAMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAAGwAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAAbgABAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</PropagationToken>
  19:     </CoordinationContext>
  20:   </s:Header>
  21:   <s:Body>
  22:     <Transaction xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:x="https://www.w3.org/2001/XMLSchema" xmlns:d3p3="https://schemas.datacontract.org/2004/07/System.Transactions.Oletx" z:FactoryType="d3p3:OletxTransaction" xmlns:z="https://schemas.microsoft.com/2003/10/Serialization/" xmlns="https://schemas.datacontract.org/2004/07/System.Transactions">
  23:       <OletxTransactionPropagationToken i:type="x:base64Binary" xmlns="">AQAAAAMAAADknHb/v0zMQKhDZHfsF0Y1AAAQAAAAAACEAAAAAMW2bRzFtm00W6xnBOMYAHfXuW00W6xnTOQYADi1JADAu1YAkOMYADg5YjE0NzZkLTE2ZmYtNGI4MS05ZjEwLTE5MDE3ZTkwYjU1MgDRiGgMAAAAZM1kzSEAAABKSU5OQU4tVklTVEEAAAAAHAAAAEoASQBOAE4AQQBOAC0AVgBJAFMAVABBAAAAAAABAAAAAAAAABMAAAB0aXA6Ly9KaW5uYW4tVmlzdGEvAA==</OletxTransactionPropagationToken>
  24:     </Transaction>
  25:   </s:Body>
  26: </s:Envelope>

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

最後更新:2017-10-27 16:04:25

  上一篇:go  WCF技術剖析之三十二:一步步創建一個完整的分布式事務應用
  下一篇:go  WCF 技術剖析之三十三:你是否了解WCF事務框架體係內部的工作機製?[下篇]