閱讀43 返回首頁    go 技術社區[雲棲]


談談分布式事務之一:SOA需要怎樣的事務控製方式

在一個基於SOA架構的分布式係統體係中,服務(Service)成為了基本的功能提供單元,無論與業務流程無關的基礎功能,還是具體的業務邏輯,均實現在相應的服務之中。服務對外提供統一的接口,服務之間采用標準的通信方式進行交互,各個單一的服務精又有效的組合、編排成為一個有機的整體。在這樣一個分布式係統中某個活動(Activity)的實現往往需要跨越單個服務的邊界,如何協調多個服務之間的關係使之為活動功能的實現服務,涉及到SOA一個重要的課題:服務協作(Service Coordination)。而具體來講,一個分布式的活動可能會執行幾秒鍾,比如銀行轉帳;也可能執行幾分鍾、幾個小時、幾天甚至更長,比如移民局處理移民的申請。事務,無疑是屬於短暫運行服務協作(Short-Running Service Coordination)的範疇。

一、 什麼是事務(Transaction)

事務提供一種機製將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作隻有在所有操作均能正常執行的情況下方能提交,隻要其中任一操作執行失敗,都將導致整個事務的回滾。簡單地說,事務提供一種“要麼什麼都不做,要麼做全套(All or Nothing)”機製。事務具有如下四個屬性,根據其首字母,我們一般將其稱為事務的ACID四大屬性:

  • 原子性(Atomicity):“原子”這個詞的本義就是不可分割的意思,事務的原子性的含義是:一個事務的所有操作被捆綁成一個整體,所有的操作要麼全部執行,要麼都不執行;
  • 一致性(Consistence):事務的原子性確保一個事務不會破環數據的一致性,如果事務成功提交,數據的狀態是組成事務的所有操作按照事先編排的方式執行的結果,數據狀態具有一致性;如果事務任何一個中間步驟出錯,整個事務回滾並將數據回複到原來的狀態,數據狀態仍然具有一致性。所以,事務隻會將數據狀態從一個一致性狀態轉換到另一個一致性狀態;
  • 隔離性(Isolation):從事務的外部來看,事務的一致性實現了數據在兩個一致性狀態之間的轉換,但是從事務內部來看,組成事務的各個操作是按照一定的邏輯順序執行的,所以數據具有位於兩個一致性狀態的“中間狀態”。但是,這種中間狀態被隔離於事務內部,對於事務外部是不可見的;
  • 持久性(Durability):持久性的意思是一旦成功提交,基於持久化資源(比如數據庫)的數據將會被持久化,對數據的改變是永久性的。

事務最初來源於數據庫管理係統(DBMS),反映的是對存儲於數據庫中的數據操作。除了主流的關係型數據庫管理係統,比如SQL Server,Oracle和DB2等提供對事務的支持,基於事務的數據操作方式也可以應用到其他一些數據存儲資源,比如MSMQ。自Windows Vista開始將文件係統(NTFS)以至於注冊表納入了事務型資源(Transactional Resource)的範疇。

二、 事務的顯式控製

雖然事務型資源家族成員越來越多,但是不可否認的是,數據庫還是我們使用頻率最高的事務型資源。對於稍微有一定經驗的開發人員,應該都在存儲過程(Stored Procedure)中編寫過基於事務的SQL,或者編寫過基於ADO.NET事務的代碼,對事務的進一步介紹就從這裏說起。

1、SQL中的事務處理

無論是基於SQL Server的T-SQL,抑或是基於Oracle的PL-SQL都對事務提供了原生的支持,有意思的是T-SQL中的T本身指的就是事務(Transaction)。以T-SQL為例,我們可以通過如下三個SQL語句實現事務的啟動、提交與回滾:

  • BEGIN TRANSACTION: 開始一個事務;
  • COMMIT TRANSACTION提交所有位於BEGIN TRANSACTION和COMMIT TRANSACTION之間的操作;
  • ROLLBACK TRANSACTION回滾所有位於BEGIN TRANSACTION和COMMIT TRANSACTION之間的操作。

我們舉一個很典型的基於事務型操作的例子:銀行轉帳,而且這個例子將會貫穿於本章的始終。為此,我們先創建一個最為簡單的用於存儲帳戶的數據表:T_ACCOUNT,整個表近僅僅包括三個字段(ID、NAME和BALANCE),它們分別代表銀行帳號的ID、名稱和餘額。創建該表的T-SQL如下:

   1: CREATE TABLE [dbo].[T_ACCOUNT](
   2:     [ID]        VARCHAR(50)     PRIMARY KEY,
   3:     [NAME]      NVARCHAR(50)    NOT NULL,
   4:     [BALANCE]   FLOAT           NOT NULL)
   5: GO

銀行轉帳是一個簡單的複合型操作,由兩個基本的操作構成:存儲和提取,即從一個帳戶中提取相應金額出入另一個帳戶。對數據完整性的要求是我們必須將這兩個單一的操作納入同一個事務。如果我們通過一個存儲過程來完成整個轉帳的流程,具體的SQL應該采用下麵的寫法:

   1: CREATE Procedure P_TRANSFER
   2:     (
   3:         @fromAccount    VARCHAR(50),
   4:         @toAccount      VARCHAR(50),
   5:         @amount         FLOAT
   6:     )
   7: AS
   8:  
   9: --確保帳戶存在性
  10: IF NOT EXISTS(SELECT * FROM [dbo].[T_ACCOUNT] WHERE ID = @fromAccount)
  11:     BEGIN
  12:         RAISERROR ('AccountNotExists',16,1) 
  13:         RETURN
  14:     END    
  15: IF NOT EXISTS(SELECT * FROM [dbo].[T_ACCOUNT] WHERE ID = @toAccount)
  16:     BEGIN
  17:         RAISERROR ('AccountNotExists',16,1) 
  18:         RETURN
  19:     END
  20: --確保餘額充足性
  21: IF NOT EXISTS(SELECT * FROM [dbo].[T_ACCOUNT] WHERE ID = @fromAccount AND BALANCE >= @amount)
  22:     BEGIN
  23:         RAISERROR ('LackofBalance',16,1)
  24:         RETURN
  25:     END
  26: --轉帳
  27: BEGIN TRANSACTION
  28:     UPDATE     [dbo].[T_ACCOUNT] SET BALANCE = BALANCE - @amount WHERE ID = @fromAccount
  29:     IF @@ERROR <> 0 
  30:         BEGIN
  31:             ROLLBACK TRANSACTION
  32:         END
  33:     UPDATE     [dbo].[T_ACCOUNT] SET BALANCE = BALANCE + @amount WHERE ID = @toAccount
  34:     IF @@ERROR <> 0 
  35:         BEGIN
  36:             ROLLBACK TRANSACTION
  37:         END
  38: COMMIT TRANSACTION
  39: GO

2、 ADO.NET事務控製

無論是T-SQL,還是PL-SQL,抑或是其他數據庫管理係統對標準SQL的擴展,不僅僅是提供基於標準SQL的DDL(Data Definition Language)和DML(Data Manipulation Language),還提供了對函數、存儲過程和流程控製的支持。SQL Server至2005起,甚至實現了與CLR(Common Language Runtime)的集成,使開發人員可以使用任何一種.NET語言編寫編寫函數或者存儲過程。毫無誇張地說,你可以通過SQL實現任何業務邏輯。

但是,在大多數情況我們並不這麼做,我們更多地還是將SQL作為最基本的數據操作語言在使用。對於.NET開發者來說,我們還是習慣將複雜的邏輯和流程控製實現在通過C#或者VB.NET這樣的麵相對象編程語言編寫的程序中。究其原因,我覺得主要有兩點:

  • 麵相對象的語言更能容易地實現複雜的邏輯:較之SQL這種基於集合記錄的語言,麵相對象的語言更加接近於我們真實的世界,通過麵相對象的方式模擬具體的邏輯更加貼近於人類的思維方式。此外,通過麵相對語言本身的一些特性,我們可以更加容易地應用各種設計模式和思想;
  • 將太多邏輯運算的執行放在數據庫中不利於應用的擴展:從部屬的角度來講,數據操作運算負載到具體的服務器中,以一個典型的分布式Web應用為例,Web服務器(承載Web應用)、應用服務器(承載各種服務)和數據庫服務器均可以承載最終對邏輯的運算。但是,從可擴展性(或者可伸縮性)上考慮,將主要的計算放在前兩者比放在數據庫更具優勢。如果我們將密集的運算(這種運算需要占用更多的CPU時間和內存)遷移到Web服務器或者應用服務器,我們可以通過負載均衡(Load Balance)將其分流到多台服務器上麵,這個服務器機群可以根據負載情況進行動態地配置。但是,數據庫服務器對負載均衡的支持就不那麼容易。

正因為如此,對於事務的控製,較之采用SQL的實現方式,我們使用得最多的還是采用基於麵相對象語言編程的方式。對於.NET開發人員,我們可以直接利用ADO.NET將基於單個數據庫連接的多個操作納入同一個事務之中。同樣以上麵的銀行轉帳事務為例,這次我們將整個轉帳作為一個服務(BankingService)的一個操作(Transfer)。下麵的代碼通過一種與具體數據庫類型無關的ADO.NET編程模式實現了整個銀行轉帳操作,最終的轉帳通過調用一個存儲過程實現:

   1: public class BankingService : IBankingService
   2: {
   3:     //其他操作
   4:     public void Transfer(string fromAccountId, string toAccountId, double amount)
   5:     {
   6:         string connectionStringName = "BankingDb";
   7:         string connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
   8:         string providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
   9:         DbProviderFactory dbProviderFactory = DbProviderFactories.GetFactory(providerName);
  10:         using (DbConnection connection = dbProviderFactory.CreateConnection())
  11:         {
  12:             connection.ConnectionString = connectionString;
  13:             DbCommand command = connection.CreateCommand();
  14:             command.CommandText = "P_TRANSFER";
  15:             command.CommandType = CommandType.StoredProcedure;
  16:  
  17:             DbParameter parameter = dbProviderFactory.CreateParameter();
  18:             parameter.ParameterName = BuildParameterName("fromAccount");
  19:             parameter.Value = fromAccountId;
  20:             command.Parameters.Add(parameter);
  21:  
  22:             parameter = dbProviderFactory.CreateParameter();
  23:             parameter.ParameterName = BuildParameterName("toAccount");
  24:             parameter.Value = toAccountId;
  25:             command.Parameters.Add(parameter);
  26:  
  27:             parameter = dbProviderFactory.CreateParameter();
  28:             parameter.ParameterName = BuildParameterName("amount");
  29:             parameter.Value = amount;
  30:             command.Parameters.Add(parameter);
  31:  
  32:             connection.Open();
  33:             using (DbTransaction transaction = connection.BeginTransaction())
  34:             {
  35:                 command.Transaction = transaction;
  36:                 try
  37:                 {
  38:                     command.ExecuteNonQuery();
  39:                     transaction.Commit();
  40:                 }
  41:                 catch
  42:                 {
  43:                     transaction.Rollback();
  44:                     throw;
  45:                 }
  46:             }
  47:         }
  48:     }
  49: }

注:為了使上麵一段代碼能夠同時用於不同的數據庫類型,比如SQL Server和Oracle,我通過提取連接字符串配置中的數據庫提供者(DbProvider)名稱,借此創建相應的DbProviderFactory對象。所有ADO.NET對象,包括DbConnection、DbCommand、DbParameter以及DbTransaction均通過DbProviderFactory創建,所以並不和具體的數據庫類型綁定在一起。此外,基於不同數據庫類型的存儲過程的參數命名各不相同,比如 SQL Server的參數會添加”@”前綴,為此我將對參數名稱的解析實現在一個單獨的方法(BuildParameterName)之中。

3、事務的顯式控製限定於對單一資源的訪問

通過在SQL中進行事務的控製,隻能將基於某一段SQL語句的操作納入到一個單一的事務中;如果采用基於ADO.NET的數據控製,被納入到同一個事務的操作僅僅限於某個數據庫連接。換句話說,上麵介紹的這兩種對事務的顯式控製僅僅限於對單一的本地資源的控製。

我們將事務的概念引入服務,倘若我們將一個單一的服務操作作為一個事務,如果采用上述的顯式事務控製的方式,那麼整個服務操作隻能涉及一個單一的事務資源。服務於存取的資源關係如圖1所以。

image

圖1 本地事務對單一資源的控製

上述的這種基於某個服務單一本地資源的訪問的事務,被稱為本地事務(Local Transaction),在一個基於SOA分布式應用環境下,我們需要的同時能將多個資源、多個服務進行統一協作的分布式事務(Distributed Transaction)。接下來,我們來介紹幾種典型的分布式事務應用的場景。

三、分布式事務(Distributed Transaction)應用場景

對於一個分布式事務(Distributed Transaction)來講,事務的參與者分布於網絡環境中的不同的節點。也就是說,我們可以將多個事務資源納入到一個單一的事務之中,並且這些事務資源可以分布到不同的機器上。這些承載分布式資源的機器可能是出於同一個網絡中,也可能處於不同的網絡中。甚至說,某個事務資源本質上就是一個通過HTTP訪問的單純的Internet資源。

站在SOA的角度來看分布式事務,意味著將服務的某個服務操作視為一個單一的事務。該服務操作可能會訪問不止一個事務資源(比如訪問兩個不同的數據庫服務器),也可能調用另一個服務。下麵介紹了三個典型的分布式事務應用場景,先從最簡單的說起。

1、將對多個資源的訪問納入同一事務

第一個分布式事務應用場景最簡單,即一個服務操作並不會調用另一個服務,但是服務操作涉及到對多個事務資源的訪問。當一個服務操作訪問不同的數據庫服務器,比如兩台SQL Server,或者一台SQL Server和一台Oracle Server;當一個服務操作訪問的是相同數據庫,但是相應的數據庫訪問時基於不同的數據連接;當一個服務操作處理訪問數據庫資源,還需要訪問其他份數據庫的事務資源,就需要采用分布式事務來對所有的事務參與者進行協作了。圖2反映了這樣的分布式應用場景。

image

圖2 單一服務對多個事務資源的訪問

2、將對各個服務的調用納入同一事務

對於上麵介紹的分布式應用場景,盡管一個服務操作會訪問多個事務資源,但是畢竟整個事務還是控製在單一的服務內部。如果一個服務操作需要調用另外一個服務,這是的事務就需要跨越多個服務了。在這種情況下,起始於某個服務的事務在調用另外一個服務的時候,需要以某種機製流轉到另外一個服務,以使被調用的服務訪問的資源自動加入進來。圖3反映了這樣一個跨越多個服務的分布式事務。

image 圖3 跨越多個服務的事務

3、 將對多個資源和服務的訪問納入同一個事務

如果將上麵這兩種場景(一個服務可以調用多個事務資源,也可以調用其他服務)結合在一起,對此進行延伸,整個事務的參與者將會組成如圖4所示的樹形拓撲結構。在一個基於分布式事務的服務調用中,事務的發起者和提交均係同一個,它可以是整個調用的客戶端,也可以是客戶端最先調用的那個服務。

image 圖4 基於SOA分布式事務拓撲結構

較之基於單一資源訪問的本地事務,分布式事務的實現機製要複雜得多。Windows平台提供了基於DTC分布式事務基礎架構,下一篇文章中我將對針對該架構模型詳細介紹分布式事務時如何工作的。

 

分布式事務係列:
談談分布式事務之一:SOA需要怎樣的事務控製方式
談談分布式事務之二:基於DTC的分布式事務管理模型[上篇]
談談分布式事務之二:基於DTC的分布式事務管理模型[下篇]
談談分布式事務之三: System.Transactions事務詳解[上篇]
談談分布式事務之三: System.Transactions事務詳解[下篇]

作者:Artech出處:https://artech.cnblogs.com本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。


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

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

  上一篇:go  WCF技術剖析之三十:一個很有用的WCF調用編程技巧[下篇]
  下一篇:go  談談分布式事務之二:基於DTC的分布式事務管理模型[上篇]