全面瞭解事務、分散式事務理論及其實現方案

2023-07-10 21:00:44

一、使用者匯款場景

使用者 A 的賬戶存有 2200 元,使用者 B 的賬戶存有 1600 元。現在使用者 A 給使用者 B 匯款 200 元,正確的執行步驟為:

  1. A 使用者:A存款 = 2200 - 200

  2. B 使用者:B存款 = 1600 + 200

  • 如果上面的匯款步驟正確執行完,那麼 A 使用者現在的存款數是 2000 元, B 使用者現在的存款數是 1800。

  • 如果上面的匯款步驟沒有正確執行完呢?比如遇到了異常情況, A 使用者存款數已經扣去了 200, B 使用者的存款數卻沒有加上 200。那麼現在 A 使用者存款數是 2000 元,B 使用者的儲存數還是 1600 元。此時,A 使用者存款數和 B 使用者存款數都出現了錯誤。

A 使用者匯款了存款數減少了,B 使用者卻沒有收到匯款。這種情況一定會給使用者造成困擾,不得不打電話去銀行詢問出了啥事情?

為什麼會出現這種情況?

在匯款操作的步驟中,有 2 個步驟:

  1. A 使用者匯款 2. B 使用者收款

日常理解來看這 2 個步驟沒多大問題,但是在計算機軟體執行操作中來看,A 扣款和 B 收款是2個不同動作,所以 A 扣款動作可能成功也可能失敗,B 同樣如此。因為程式和網路有可能出現各種異常情況。

這種分步驟的日常操作,在軟體中我們需要把它們當作一個整體來操作,操作結果是要麼都成功,要麼都失敗。

上面匯款來說,都成功就是,A 扣款減少和 B 收款增加都成功。都失敗就是,A 扣款減少和 B 收款增加都失敗,A 回滾到原來存款數,B 存款數不增不減。(A 的存款數 + B 的存款數)= 總資金數,總資金數在匯款前後是不變的。

A扣款操作和B收款操作當作是一個整體操作,一個不可分割的原子操作。在計算機軟體中,我們把這種操作叫做事務。

二、什麼是事務

上面討論的場景已經引出了什麼是事務?

事務是將程式中的多個操作(比如多個讀、寫等)"糅合"在一起成為一個整體的邏輯操作單元,整個邏輯操作單元中的所有讀寫操作是一個執行的整體,整體被視為一個不可分割的操作,這個就稱為事務。事務操作要麼成功,要麼失敗(終止或回滾)。

如果事務操作失敗了,不需要擔心事務裡的部分操作失敗的情況,部分失敗後可回滾,恢復到原來資料狀態。

三、事務的 ACID 特性

通常說到資料庫事務時,都會提到 ACID 這 4 個特性。這 4 個特性是 TheoHarder 和 Andreas Reuter 於1983年為精確描述資料庫的容錯機制而定義的。

但各家資料庫系統實現的 ACID 又不盡相同。有些系統說自己提供事務時或」相容ACID「時,其實我們無法確信它究竟提供了什麼樣的保證,需要仔細檢視該系統檔案或程式碼才知曉細節。

InnoDB 預設事務隔離級別是可重複讀,不滿足隔離性;Oracle 預設的事務隔離級別為讀已提交,不滿足隔離性。因為有時完全滿足就可能導致效能問題,有一個取捨平衡。

下面看看 ACID 具體含義:

  • C(Consistency) 一致性:當事務開始和結束時,資料處於一致的狀態。比如上面匯款場景,總資金數(資料)在匯款前後是不變的,保持了前後一致。

    有的人說一致性是 ACID 的目的,只要保證了原子性、隔離性、永續性,也就保證了資料的一致性。

    資料庫提供了某些一致性的約束,比如主鍵 ID,唯一索引等。對於業務資料一致性,資料庫並沒有提供很好的約束,業務資料的一致性需要應用程式來保證。

  • A(Atomicity) 原子性:原子通常指不可分割的最小粒度物質。事務中對資料的所有寫操作像一個單一的操作一樣執行,操作是不可分割的,要麼都成功,要麼都失敗(終止或回滾)。

    ACID 中的原子性並不是關於多個操作的並行性,它沒有描述多個執行緒存取相同資料會發生什麼情況,這種情況是由 ACID 中的隔離性定義。原子性描述的是使用者端發起一個包含多個寫操作請求時可能發生的情況,比如一部分寫入後,發生系統故障,包括程序崩潰,網路中斷,磁碟滿了等情況;這裡原子性是把多個寫操作作為一個原子操作,作為一個整體,萬一遇到故障導致沒能最終提交,事務會終止,資料庫必須丟棄或復原區域性完成的更改。

  • I(Isolation) 隔離性:在處理程式時,事務保證了各種資料操作相互獨立性,事務的中間狀態對其它事務是不可見的。

    這裡的隔離性意味著並行執行多個事務是相互獨立、相互隔離的。經典資料庫教材把隔離性定義為可序列化(一個一個的執行)。但是在實踐中,序列化有心效能問題。比如 Oracel 11g,聲稱有「序列化」功能,但它本質是快照隔離,比序列化更弱的保證。

  • D(Durability) 永續性:事務應該保證所有成功提交的資料都能被持久化,即使發生故障,也不會丟失資料。

    資料庫會保障一旦事務提交成功,即使硬體故障或資料庫崩潰,事務所寫入的資料也不會丟失。

四、MySQL中的事務

MySQL事務介紹

事務的概念其實最早是從資料庫系統中來的。

事務處理系統使應用程式設計師能夠集中精力去編寫業務程式碼,而不必關心事務管理的各種細節。

在 MySQL 等關係型資料庫中,事務是保證資料狀態一致性的一個重要手段。

在 MySQL 中,一個事務可能包含多條 SQL 語句的操作,這些語句作為一個整體要麼都執行成功,要麼都執行失敗。

MySQL 支援事務的儲存引擎有 InnoDB、NDB Cluster 等,其中 InnoDB 的使用最為廣泛,MyISAM 儲存引擎不支援事務。MySQL 服務層並不實現事務。

MySQL 事務處理使用下面語句:

START TRANSACTION/BEGIN :開啟一個事務

ROLLBACK : 回滾一個事務

COMMIT :提交事務

當然我們也可以設定 MySQL 事務自動提交模式:

  • SET AUTOCOMMIT=0 禁止自動提交
  • SET AUTOCOMMIT=1 開啟自動提交

MySQL事務ACID實現

在 MySQL 中,分析下 InnoDB 儲存引擎中 ACID 的實現。

  1. 隔離性

隔離性是通過不同鎖機制、MVCC(多版本並行控制)來實現事務間的隔離,實現安全並行。MVCC 解決了不可重複讀,或者實現了可重複讀。還有的使用快照或結合快照。

MySQL 中有 4 種隔離級別,分別是 READ UNCOMMITTED(讀未提交)READ COMMITTED(讀已提交)REPEATABLE READ(可重複讀)SERIALIZABLE(序列化)

事務隔離級別是要解決什麼問題?

  1. 髒讀

    髒讀是指讀到了其他事務未提交的資料。未提交的資料意味著資料可能會回滾,也就是最終可能不會存到資料庫裡,不存在的資料,這就是髒讀。

  2. 可重複讀

    在一個事務內,最開始讀到的資料,和事務結束前任意時候讀到的同一批資料是一致的。這個通常針對資料更新操作。

  3. 不可重複讀

    與上面的可重複讀形成對比,在同一事務內,不同的時刻讀到的同一批資料可能不一樣,因為這批資料可能會受到其它事務的影響。比如事務更改了這批資料並提交了。這個通常針對更新操作。

  4. 幻讀

    幻讀是針對資料插入操作。

    比如有 2 個事務 A 和 B,事務 A 對一批資料作了更改,但是未提交,此時事務 B 插入了與事務 A 更改前的資料記錄相同的記錄,並且事務 B 先於 事務 A 提交了,此時,在事務 A 中查詢,會發現剛剛更改的資料未起作用,看起來沒有修改過,但真實情況是,這是事務 B 插入進來的資料,這種情況感覺讓使用者產生了幻覺,這就是幻讀。

事務隔離就是為解決上面提到的髒讀、不可重複讀、幻讀這 3 個問題, 下表對 4 種隔離級別對解決這 3 個問題,可以和不可以:

隔離級別 髒讀 不可重複讀 幻讀
讀未提交 可能 可能 可能
讀已提交 不可能 可能 可能
可重複讀 不可能 不可能 可能
序列化 不可能 不可能 不可能

上圖說明:

可能 - 可能出現問題。比如 讀未提交 隔離級別可能出現髒讀問題。

不可能 - 表示不可能出現問題。比如 讀已提交 隔離級別不可能出現髒讀問題。

序列化的隔離級別最高,可以解決所有這 3 個問題 - 髒讀、不可重複讀、幻讀。其它隔離級別只能解決部分問題,甚至有的隔離級別都不能解決。

為下文作準備:InnoDB 儲存引擎提供了兩種事務紀錄檔:redo log(重做紀錄檔)和 undo log(回滾紀錄檔)。MySQL Server 提供了 binlog紀錄檔。

  1. 原子性

undo log 保證事務的原子性。它記錄了事務開始前需要回滾的一些資訊。事務失敗需要回滾時,可以從 undo log 紀錄檔復原已經執行的 SQL,回滾就是一個反向操作,事務提交是正向操作 ->,回滾就是反向操作 <-。

  1. 永續性

永續性就是事務操作最終要持久化到資料庫中,永續性是由 記憶體+redo log 來保證的,MySQL 的 InnoDB 儲存引擎,在修改資料的時候,會同時在記憶體和 redo log 中記錄這次操作,宕機的時候可以從 redo log 中恢復資料。

redo 紀錄檔記錄了事務 commit 後的資料,用來恢復未寫入 data file 的已成功事務更新的資料。

如果上面原子性、隔離性、永續性都實現了,那麼一致性也就實現了。

一個問題:redo log 和 undo log 區別是什麼?

  • undo log 記錄了事務開始前的資料狀態,記錄的是更新之前的值
  • redo log 記錄了事務完成後的資料狀態,記錄的是更新之後的值

事務提交之前發生了崩潰,重啟後會通過 undo log 回滾事務;事務提交之後發生了崩潰,重啟後會通過 redo log 恢復事務。

來看張圖:

上面就是對 MySQL 中 AID 的實現原理簡單介紹分析。

更詳細的原理理解可以看《鳳凰架構》- 本地事務

五、本地事務

MySQL 中的事務也叫本地事務,有的人也譯為區域性事務

後面隨著業務發展擴大,構建分散式系統時,會出現很多問題,為了構建容錯的分散式系統,保障容錯系統的技術之一就是分散式事務。下節會討論它。

其實還有其它事務概念,本地事務還對應著全域性事務。本地事務是指僅操作單一資料資源、不需要全域性事務管理器進行協調的事務。

本地事務是最基礎的一種事務解決方案,適用於單個服務的資料來源場景。

​ (本地事務)

分散式事務則要解決多服務的多個資料來源場景。比如常見的電商微服務架構中,購物服務(shopping-service)由訂單服務(order-service)和庫存服務(inventory-service)組成,這裡可能涉及到分散式事務:

對於本地事務,開發語言都會對資料庫提供的事務介面進行封裝,然後在應用。

在 Go 語言中,對 MySQL 事務操作提供了 3 個操作函數:

// 開始事務
func (db *DB) Begin() (*Tx, error)
// 回滾事務
func (tx *Tx) Rollback() error
// 提交事務
func (tx *Tx) Commit() error

簡單範例程式碼:

// sqlx 對事務的操作,github.com/jmoiron/sqlx
database, err := sqlx.Open()
conn, err := database.Begin()
res, err := conn.Exec()
if err != nil {
   conn.Rollback()
   return
}
res, err = conn.Exec()
conn.Commit()

在 JAVA 語言中,JDBC 對資料庫事務操作進行了封裝,JDBC 事務操作範例程式碼:

Connection conn = openConnection();
try {
    // 關閉自動提交:
    conn.setAutoCommit(false);
    // 執行多條SQL語句:
    insert(); update(); delete();
    // 提交事務:
    conn.commit();
} catch (SQLException e) {
    // 回滾事務:
    conn.rollback();
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

六、分散式事務介紹和分散式理論

分散式事務簡介

在分散式系統中,可能產生故障的地方太多了,比如伺服器故障,作業系統問題,網路包丟失、封包順序錯亂、封包延遲,應用程式出bug,還有時間偏差等各種各樣的錯誤出現。怎麼來解決這些問題?一是直接停止服務,這顯然會嚴重影響使用者使用。二是即使某些應用服務發生了故障,整個系統依然能夠對外提供服務。為此,我們要構建一個可容錯的系統,在某些應用服務發生故障時也能提供服務。

比如在微服務架構中,構建一個容錯的微服務系統,也叫服務治理,有哪些措施、哪些方案、哪些技術來保障系統容錯性?有如下部分措施:

  • 流量控制,有限流措施
  • 服務降級
  • 服務熔斷
  • 超時重試
  • 快速失敗
  • API 隔離,像船艙壁一樣
  • 負載均衡

當然上面的這些措施不僅在微服務中可以使用,其它應用服務也可以使用,只不過這些在微服務架構中使用得比較多。

上面說到的事務,其實也可以看作是構建容錯系統一種方案,只不過事務是在應用程式與資料庫之間保障資料一致性,通過事務可以有如下保障:

  • 事務出現故障可以回滾,這是原子性保障
  • 並行存取資料庫沒有問題,這是隔離性保障
  • 裝置故障時可恢復原資料,這是永續性保障

本地事務是解決單資料來源的資料一致性問題,這裡的一致性其實是一個抽象概括,要保證一致性,需要原子性、隔離性、永續性共同來保障。

分散式事務解決的是多資料來源的資料一致性問題。這個問題怎麼解決?

一致性問題是分散式系統中的一個主要問題之一。通過 CAP 定理(Consistency 一致性、Availability 可用性、Partition Tolerance 分割區容錯性),也可知道分散式系統的 3 大特徵。CAP 定理說明了這 3 大特徵只能同時兼顧其中 2 個。

分散式基礎理論

CAP定理

CAP 定理(Consistency 一致性、Availability 可用性、Partition Tolerance 分割區容錯性,CAP 由英文單詞首字母組成)是在 2000 年 7 月由加州大學伯克利分校的 Eric Brewer 教授提出,兩年後,麻省理工學院的 Seth Gilbert 和 Nancy Lynch 以嚴謹的數學推理證明了 CAP 猜想,CAP 正式從猜想變為分散式計算領域所公認的著名定理。

這個定理描述了在一個分散式系統中,涉及共用資料時,CAP 定理中的 3 個特徵最多隻能同時滿足其中 2 個:

​ (來自:https://en.wikipedia.org/wiki/CAP_theorem)

  • 一致性Consistency):任何使用者(使用者端)同時看到的資料都是一致相同的,無論是從分散式系統的哪個節點。

    在分散式系統中,資料一般會儲存到不同的節點中,當資料寫入一個節點時,要將此資料複製到分散式系統中所有節點中,都需要寫入成功。如果資料更新後要求所有使用者必須讀取到最新的相同資料,這種一致性叫強一致性(或嚴格一致性)。

  • 可用性Availability):可用性指任何使用者請求分散式節點中的資料時都能獲取響應,即使有一個或多個節點發生故障。

  • 分割區容錯性Partition Tolerance):分割區指的是分散式系統中部分節點因為網路故障導致彼此通訊中斷或延遲。分割區容錯性指的是即使分散式系統中部分節點因網路故障導致通訊中斷或延遲,分散式系統也能夠正常提供服務。

    發生網路分割區時,系統就不能在某一時限內達成資料一致性,因為網路通訊中斷或延遲,資料複製會延遲,多個節點的資料在一定時限內可能不一致,這種情況不能做到強一致性,但可以做到最終一致性。下面會講到另外一個理論 BASE,它裡面就特別說到最終一致性。

CAP 定理已經有嚴格的數學證明,只能 3 者取其 2。在一般情況下,會選擇 P,放棄 A 和 C 其中的一個。因為網路是永遠不可靠的。比如在多數 NoSQL 和 分散式快取框架都是 AP 系統。當然也有 CP 系統資料庫。

在分散式系統中,通常會犧牲掉 C 一致性,但是資料又追求一致性,這不是很矛盾?怎麼解決?如是人們又把一致性重新定義 - -!,把 CAP 和 ACID 中的一致性稱為強一致性,把犧牲掉 C 的 AP 系統儘可能獲取一致性資料的行為稱為弱一致性。在弱一致性裡,有一個特例,被稱為最終一致性,下面 BASE 理論會講到。

BASE理論

BASE 理論是 eBay 的系統架構師 Dan Pritchett 在 2008 年,在 ACM 發表的論文《Base: An Acid Alternative》中提出的,該論文總結了一種除了 ACID 的強一致性外,還有 BASE 理論來達成一致性目的 - 最終一致性。

BASE 理論:

  • 基本可用性(Basically Available):在分散式系統出現不可預知的故障時,允許系統部分節點失效。

  • 軟狀態(Soft State):有的也譯作柔性事務。指允許系統中的資料出現中間狀態,這個中間狀態不會影響系統的整體可用性。

  • 最終一致性(Eventually Consistent):系統中的所有節點資料(包括中間狀態的資料),在經過一段時間後,最終達到一個一致的狀態。最終一致性是系統保證資料最終能夠達到一致,而不需要實時保證資料強一致性。

BASE 理論允許資料在一段時間內是不一致性的,但最終會達到一致性狀態。

七、分散式事務實現方案

X/Open XA事務模式

XA 規範是 X/Open 組織(後併入The Open Group)定義的一套分散式事務處理(DTP)標準。XA 規範描述了全域性事務管理器和區域性的資源管理器之間的通訊介面。XA 規範定義了一組標準的介面函數,包括開始全域性事務、結束全域性事務、提交全域性事務、回滾全域性事務等。通過這些介面函數,應用程式可以實現分散式事務的提交和回滾,從而保證事務的一致性和可靠性。XA 規範裡面定義了各種介面在 DTP 功能模型元件之間進行通訊。

在 XA 規範中,各個功能元件使用規範的 API 進行存取。比如應用程式使用 API 介面與事務管理器進行互動,事務管理器使用 API 介面與各個資源管理器進行互動。詳細情況請繼續看下面說明。

XA 規範也制定了一個分散式事務處理模型(X/Open Distributed Transaction Processing Model,簡稱 X/Open DTP Model),該模型主要有 4 個功能元件(v2版本有4個,v1版本有3個):

  • AP(Application Program,應用程式):應用程式是實現業務功能的程式。應用程式定義了全域性事務的開始和結束,它通常決定每個事務分支的提交和回滾。
  • RM(Resource Manager,資源管理器):管理定義的部分公共資源,對這些資源存取需要使用 RM 提供的服務介面。比如資料庫就是常見的資源管理器。一個全域性事務會關聯多個 RM,每個 RM 上都會建立一個全域性事務分支 Branch。
  • TM(Transaction Manager,事務管理器):管理全域性事務,分配識別符號給事務,監控事務程序,協調所有資源管理器完成事務以及失敗恢復。應用程式通過全域性事務管理器定義全域性事務的開始和結束。
  • CRM(Communication Resource Manager,通訊資源管理器):控制一個事務或跨多個事務的應用程式之間的通訊。X/Open 提供了幾種比較流行的通訊模式:TxRPC、XATMI、Peer-to-Peer(P2P 對等網路)。

如果加上開放式互連事務處理,OSI TP(Open System Interconnection Transaction Process,開放式系統互聯事務處理)。

那就有五個了。

​ (各元件和 XA 及 XA+ 介面,XA and XA+ Interfaces 在 XA+ Spec v2 有定義)

​ (分散式事務處理 (DTP) 模型 v2,各元件和介面,以及介面存取方向)

從上圖可以看出有各種存取介面,共有 6 個介面,有單向也有雙向介面。

比如 (1)AP-RM 介面,該介面用於應用程式(AP)對資源管理器(RMs)的存取,介面存取是單向的。還有雙向介面,比如 (3)XA 介面,資源管理器(RMs)和事務管理器(TM)之間介面存取是雙向的。

在業務比較簡單時,單體應用就可以應付,這時事務就是一個應用程式(AP)存取一個資料庫(RM),沒有什麼遠端服務呼叫,只需要用一個 AP、一個 RM 和 TM 就可以實現,不需要 CRM 和 OSI TP。

在業務向前發展體量變大,業務和技術系統複雜度變高時,單體應用架構逐步向微服務架構演進,以前的大單體應用拆分成多個相對較小的應用,單體巨量資料庫也會隨著應用拆分而對應的拆分,變成多個資料庫。這時候的應用程式(AP)有多個,資料庫(資源管理器RM)也有多個,一個服務的呼叫,發生一個或多個 AP 存取一個或多個 RM,分散式事務可能產生。在分散式事務中,我們可以藉助 CRM 元件及 OSI TP 元件,使得 TM 能跨多個 AP、RM 來協調全域性事務。

2PC 兩階段提交

2PC 二階段提交介紹

2PC(二階段提交,Two-phase Commit)是指在計算機網路和資料領域內,為了使基於分散式系統下的所有節點進行事務提交時保持資料一致性而設計的一種演演算法。二階段提交也被稱為一種協定。

在分散式系統中,每個節點雖然可以知道自己操作是成功或失敗,卻無法知道其它節點的操作是成功或失敗。所以當一個事務跨越多個節點時,為了保持事務的 ACID 特性,需要引入一個作為協調者的元件來統一掌控所有節點(參與者)操作結果,並最終指示這些節點是否需要把操作結果進行提交。

二階段提交演演算法基本思路:

參與者將操作結果的成功或失敗情況通知協調者,再由協調者根據所有參與者的反饋情況,來決定各參與者是否要提交操作還是中止操作。

整個基本演演算法流程可以分為兩個階段:

  • 第一階段 - 準備提交階段(prepare phase)。

  • 第二階段 - 提交執行階段(commit phase)。

第一階段,準備提交階段過程:

  1. 詢問:協調者向所有參與者傳送訊息,詢問是否準備提交,然後開始等待各參與者響應訊息。
  2. 執行本地事務:參與者收到協調者的詢問訊息後,參與者執行本地事務操作,並將 Undo 和 Redo 紀錄檔資訊寫入到本地。此時事務還沒正式提交,預提交狀態。
  3. 回覆:各參與者響應協調者發起的詢問:如果參與者執行本地事務等操作成功,就給協調者回復一個「同意」訊息,表示我準備好了;如果事務執行失敗,則回覆「終止」的訊息。也就是給協調者傳送確認訊息。

這一階段又叫投票階段,用投票比較形象。這裡投票指的就是用訊息來投票。

上面過程中有一句「此時事務還沒提交」是什麼意思?這需要看看一個單事務提交的全過程簡圖才好理解,上文第四節的圖:

上圖中的 Commmit 提交 在這裡應該是一個預提交狀態,還沒有真正提交。真正提交是第二階段才做的事情。

第二階段,提交執行階段過程:

又分為 2 種情況,成功或失敗。

成功:當協調者獲得第一階段參與者返回的響應訊息 「同意」時:

  1. 傳送訊息:協調者收到了參與者的回覆訊息 「同意」,協調者向所有參與者傳送 「正式提交事務」 的訊息。
  2. 正式提交事務:參與者收到訊息 」正式提交事務「 後完成事務操作,並釋放整個事務佔用的資源。
  3. 回覆訊息:事務完成後,參與者向協調者傳送回復事務 「完成」 訊息,就是給協調者傳送確認資訊。
  4. 完成:協調者收到所有參與者回覆的「完成」訊息,就是確認事務完成,完成事務。

失敗:當協調者獲得第一階段返回的響應訊息 「終止」時:

  1. 傳送訊息:協調者向所有參與者傳送「回滾事務」的請求。
  2. 回滾事務:參與者利用之前寫入 Undo 紀錄檔資訊執行回滾,並釋放整個事務佔用的資源。
  3. 回覆訊息:參與者向協調者回復事務回滾「完成」訊息,就是傳送確認訊息。
  4. 完成:協調者收到所有參與者回覆的「回滾完成」訊息後,就是確認回滾完成,取消事務。

舉個例子來說明下:

  • X 準備組織一場觀看大片的活動,他用微信向朋友 A 和 B 發起邀請:「今天一起來看大片啊,我出電影票,來的話我馬上發電影票」。

    1. 假設第一種情況 A 馬上看到了資訊回覆 X:「好啊」。不一會 B 也回覆了 X:「好啊」。

    2. 假設第二種情況 A 馬上回復 X:「好啊」,B 半小時後才回復。在這半小時內 X 和 A 都必須等待 B 的回覆。這就是可能產生阻塞或延遲情況。這裡假設是第一種情況,幾乎都馬上回復了 X:好啊。

    3. 假設第三種情況 A 馬上回復 X:「好啊」,B 回覆了 X:」沒時間去啊「。

      (上面就是二階段提交的第一階段)

  • 如果是上面第一種情況,X 收到了 A 和 B 的肯定回覆,X 於是給 A 和 B 每人發了一張電影票的二維條碼,他們就出發和 X 匯合。如果是上面第三種情況,X 會再給 A 發訊息:「B 不去,那取消吧」,A 回覆:「好吧」。

    如果是上面第二種情況,那麼就得一直等待 B 的回覆,在執行上面的 2 種情況步驟。

二階段提交過程簡圖(成功的情況):

(在 XA 事務模式,協調者叫TM事務管理器,參與者叫RM資源管理器。)

2PC 缺點

  • 同步阻塞,在執行過程中,參與節點的事務都是阻塞的。所有節點都等待其它節點的響應,無法進行其它操作。

  • 效能問題,因為不論是第一階段還是第二階段,所有參與者和協調者的公共資源都是被鎖住的,只有當所有節點準備完畢,協調者才會通知進行全域性事務提交,參與者進行本地事務提交後才會釋放資源。這是一個比較長的過程。

  • 單點問題,協調者在 2PC 中有著很重要作用,如果協調者宕機了一直沒恢復,所有參與者會受到影響,會一直等待。這裡面還有一種情況會導致資料一致性問題,第二階段中當協調者向所有參與者傳送「正式提交」訊息之後,發生了區域性網路故障或協調者未傳送完全部的「正式提交」訊息突然宕機了,這時會導致只有部分參與者收到訊息,訊息投票協調一致出現了問題。

  • 容錯性,還有一個,就是任何一個節點失敗,整個事務就會失敗,沒有容錯性。

為了解決上面提到的部分問題,具體就是協調者的單點問題和準備階段的效能問題,後面又發展出了 3PC(Three-phase Commit,三階段提交)協定。

3PC 三階段提交

3PC 三階段提交介紹

3PC(Three-phase Commit,三階段提交)與 2PC 二階段提交相比有兩個改動點:

  1. 三階段提交把原本 2PC 的第一階段「準備階段」又細分為兩個階段 - CanCommit 和 PreCommit,把第二階段提交執行階段稱為 DoCommit 階段。第一階段做的事情比較多,回滾複雜,在拆分簡化下。

    CanCommit、PreCommit 和 DoCommit,這就是三階段提交協定的三個階段。

  2. 協調者和參與者同時引入超時機制。

第一階段:CanCommit 詢問階段

這一階段就是協調者和參與者簡單的訊息互動,本身再沒有做多餘的複雜操作。不像 2PC 第一階段除了訊息互動還要做 Undo/Redo 等操作。

  • 詢問:協調者向所有參與者傳送訊息詢問:「你們是否能夠比較順利完成事務?」
  • 回覆:各參與者如果認為自己能夠順利執行事務,回覆訊息:」是/Yes「;不能順利執行,回覆:「否/No」。就是向協調者傳送確認訊息。

第二階段:PreCommit 預提交階段

協調者獲得 CanCommit 階段中參與者回覆的訊息後,會執行下面 2 個不同操作步驟:事務預提交或中斷事務。

操作步驟1:事務預提交

  1. 傳送預提交訊息:收到 CanCommit 階段參與者「是」的確認訊息後,協調者向所有參與者傳送事務預提交的訊息,並進入 prepared 狀態。

  2. 事務預提交:參與者收到協調者預提交的訊息後,參與者執行事務操作,記錄 Undo、Redo 紀錄檔資訊。事務進入預提交狀態。

  3. 回覆訊息:參與者預提交狀態執行情況,執行成功,向協調者回復訊息:「是」;執行失敗回覆:「否」。都需要向協調者傳送確認訊息。

操作步驟2:中斷事務

  1. 傳送中斷訊息:收到 CanCommit 階段參與者「否」的確認訊息後,協調者向所有參與者傳送中斷事務的訊息。
  2. 中斷事務:參與者收到中斷事務的訊息後,中斷事務操作。

第三階段:DoCommit 正式提交階段

這個階段也有 2 種不同操作步驟,在上面 PreCommit 操作步驟1 中,事務預提交狀態執行情況回覆有 2 種。

操作步驟1:正式提交事務,PreCommit 預提交狀態回覆:「是」

  1. 傳送正式提交訊息:協調者收到了參與者事務預提交狀態確認訊息 「是」 後,協調者向參與者傳送「正式提交」事務的訊息
  2. 正式提交事務:參與者收到協調者傳送的「正式提交」的訊息後,從預提交狀態變成正式提交事務,事務提交執行完後釋放事務執行期間佔用的資源。
  3. 回覆訊息:參與者事務徹底完成後,向協調者傳送「完成」的確認訊息。
  4. 完成:協調者收到參與者「完成」事務的訊息後,確認完成事務

上面這一步驟過程與 2PC 的第二階段步驟過程幾乎相同。

操作步驟2:中斷事務,PreCommit 預提交狀態回覆:「否」

  1. 傳送中斷訊息:協調者收到了預提交狀態確認訊息 「否」 後,協調者向參與者傳送「中斷」事務執行的訊息。
  2. 回滾事務:參與者收到中斷事務的訊息之後,回滾事務,釋放事務佔用的資源
  3. 回覆訊息:回滾事務完成後,向協調者傳送確認訊息。
  4. 完成:協調者收到所有參與者的確認訊息後,中斷事務完成。

3PC 優缺點

這裡指的是相對於 2PC 的優缺點。

優點:

  • 1.協調者和參與者都引入了超時機制,優化了在 2PC 中長時間收不到訊息導致長時間阻塞的情況,一定時間內收不到訊息就超時處理。
  • 2.優化了阻塞範圍,3PC 把 2PC 第一階段細分成 2 個階段,這樣 3PC 第一階段就是很簡單的操作,這個階段不會阻塞。
  • 3.3PC 的 PreCommit 中中斷事務回滾的操作是一個輕量級操作,這個也是因為細分為 2 個階段緣故。
  • 4.由於增加了 CanCommit 詢問階段,事務成功提交的把握增大。即使不成功,這個階段回滾風險也變小。同上 3。
  • 5.協調者單點風險減小,在 3PC 中,如果 PreCommit 階段之後發生了協調者宕機,下一階段 DoCommit 無法執行了,但是 3PC 這時預設操作是提交事務而不是回滾事務或持續等待,就相當於避免了協調者單點問題。為什麼能這樣?因為 PreCommit 階段的存在。

3PC 對單點問題、阻塞問題和回滾時效能都有所改善。

缺點:

  • 資料不一致沒有改善,比如進入 PreCommit 之後,協調者發出的事務預提交狀態的訊息是:」否「 - 失敗的,剛好此時網路故障,有部分參與者直到超時都未能收到「否」的訊息,這些參與者將會錯誤提交事務,導致參與者之間資料不一致的問題。

SAGA 事務模式

saga 事務介紹

saga 事務介紹:

saga 事務是 Hector Garcaa-Molrna & Kenneth Salem 在 1987 年發表的論文 SAGAS 中提出的,內容是關於怎麼處理 long lived transactions(長事務)的方法,它的核心思想:

將大事務/長事務拆分為多個短事物,這些事務由 saga 事務協調器協調,如果每個短事務都成功提交完成,那麼全域性事務就成功完成;如果某個步驟失敗,那麼根據相反順序呼叫補償操作或進行重試操作。

每個 saga 事務由一系列的 sub-transaction Ti(i 表示數位)組成,每個 Ti 對應一個補償操作 Ci,補償操作用於復原 Ti 執行操作。

saga 事務恢復策略有2種:

重試和回滾補償

  1. forward recovery 向前恢復

    重試失敗的事務,假設每個短事務最終都會執行成功。

    比如事務執行順序是 T1、T2、T3,恢復順序也是按照這個順序執行,策略就是如果 T1 執行失敗,那麼重試 T1;如果 T2 執行失敗,那麼重試 T2,以此類推,哪個短事務執行失敗就重試哪個事務。

  2. backward recovery 向後恢復

    任一事務失敗,補償所有已完成的事務。

    比如事務執行順序是 T1、T2、T3,如果 T2 事務執行失敗了,那麼就從 C2 開始執行補償事務,然後在執行 C1,回滾整個 saga 事務執行結果。

saga 分散式事務協調有2種:編排和控制

  1. 編排 Choreography

    參與者(子事務)之間的呼叫、決策和排序,通過交換事件來進行,是一種去中心化的模式。參與者之間通過訊息進行通訊,通過監聽器監聽其它參與者發出的訊息,然後執行後續業務邏輯。

  2. 控制 Orchestration

    saga 提供一個控制類,它幫助協調參與者之間的工作。事務執行命令由控制類發起,按照邏輯順序請求 saga 的參與者,從參與者哪裡接收反饋訊息後,控制類在向其它參與者發起呼叫。這個模式有點像 2PC 和 3PC。

適用場景:

  • 業務流程長、業務流程多

saga 優缺點

saga 是一種弱一致性協定,整個事務執行過程中本地事務可以處於不一致性狀態,但是最終資料會達到一致。

優點:

  • 一階段提交的是本地事務,無鎖,高效能
  • 事件驅動架構,非同步執行,高吞吐
  • 簡單,補償服務易於實現

缺點:

  • 隔離性差,不保證隔離性
  • 補償事務有

TCC 事務

TCC 事務介紹

TCC 是 Try,Confirm,Cancel 是哪個單詞的首字母組成,最早是由 Pat Helland 於 2007 年發表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

它是一種分散式事務解決方案,可用於跨資料庫、跨業務操作的資料一致性問題。

在上面文章裡,XA 的 2PC 兩階段提交中資源管理器(RM)提供了 3 個操作:準備、提交、回滾。事務管理器(TM)分 2 個階段協調所有資源管理器:第一階段詢問資源管理器「準備」好了沒,如果所有資源準備成功了,則第二階段執行所有事務「提交」操作並釋放資源;否則在第二階段執行事務的「回滾」操作並釋放資源。

這裡的資源管理器有多種實現方式,TCC 就是基於業務邏輯實現的資源管理器,TCC 的 Try、Confirm、Cancel 3 個方法均由業務編碼實現。TCC 是一種業務侵入比較強的事務解決方案,業務處理過程分為 「嘗試執行業務」 和 「確認/取消執行業務」 兩個子過程。如 TCC 名稱,它有 3 個操作步驟:

  • Try:嘗試執行業務,完成所有業務可執行性的檢查(保障一致性),並且預留好全部需要用到的資源(保持隔離性)。

  • Confirm:確認執行業務,不進行業務檢查,直接使用 Try 步驟準備的資源來完成業務處理。Confirm 步驟可能會重複執行,因此這個步驟需要冪等性。

  • Cancel:取消執行業務,釋放 Try 步驟預留的業務資源。Cancel 可能會重複執行,因此也需要冪等性。

把上面步驟在分階段,可以把 TCC 看作是應用層實現的 2PC 兩階段提交:

  • 第一階段:Try 操作,確認資源是否可執行,同時對要用到的資源進行鎖定,

  • 第二階段:Confirm 操作 或 Cancle 操作。如果第一階段 Try 執行成功,那麼就開始真正執行業務,並釋放資源;如果第一階段 Try 執行失敗,那麼執行回滾操作,預留的資源取消,使資源回到初始狀態。

TCC 分散式事務的角色,有 3 個,與前面講到的 XA 事務角色優點類似:

  • AP 應用程式:發起全域性事務,定義全域性事務包含哪些

  • RM 資源管理器:負責分支事務各資源管理

  • TM 事務管理器:負責協調全域性事務執行,包括 Confirm,Cancel 的執行,並處理網路異常

一個成功執行 TCC 事務的時序圖:

​ (來源:https://www.dtm.pub/practice/tcc.html#tcc組成 DTM 事務框架)

​ (事務框架 seata 中 tcc 模型)

TCC 優缺點

優點:

  • TCC 隔離性好,適合用於需要強隔離的分散式事務
  • TCC 保證資料最終一致性,不是強一致性
  • 應用層實現,靈活性高。這是優點也是缺點。
  • 效能比較高

缺點:

  • 業務侵入性比較強
  • 開發成本高,需要動手開發程式碼
  • 不適合強一致性場景

本地訊息表方案

本地訊息表介紹

本地訊息表方案是可靠訊息最終一致性的一種。

最終一致性這個概念是由 ebay 架構師 Dan Pritchett 在 2008 年發表在 ACM 的論文 Base: An Acid Alternative。該論文總結了除了 ACID 的強一致性之外,還可以使用 BASE 來達成最終一致性。這些上面都有講。

什麼是可靠訊息呢?

可靠訊息指的是事務發起方(訊息傳送者)完成本地事務後,傳送訊息,事務參與方(訊息接收者)一定能夠接收到訊息然後成功處理事務。

上面可靠訊息如果不加一個儲存「訊息」的中介軟體,那麼跟 2PC 的操作步驟很相似,變成了一個同步的操作情況,訊息傳送後接收者直接接收,不儲存訊息。如果加一個儲存訊息的中介軟體,那麼就變成了非同步讀寫方案了,解耦了訊息傳送方和訊息接收方,天然就不是一個強一致性方案。

這個儲存訊息的地方,可以是本地訊息表(MySQL、Redis等)、訊息佇列等各種儲存中介軟體。這裡介紹的是本地訊息表方案。

本地訊息表實現事務的過程簡析:

  • 1.在事務發起方,新建一個儲存事務訊息表即是本地訊息表。
  • 2.事務發起方在一個事務中處理業務和把訊息狀態寫入訊息表,並行送訊息到訊息中介軟體。傳送訊息到訊息中介軟體失敗會重試傳送。
  • 3.事務參與方,從中介軟體中讀取訊息,然後在一個本地事務中完成自己的業務邏輯。如果本地事務處理成功,就返回一個處理結果訊息:成功。如果本地事務處理失敗,給事務發起方傳送一個業務補償的訊息,通知事務發起方進行回滾操作。
  • 4.事務發起方讀取中介軟體的處理結果,如果是成功的訊息,那麼更新訊息表狀態。如果是失敗的訊息,那麼進行事務回滾操作。
  • 5.定時掃描還沒處理的訊息或失敗訊息,在傳送一遍,進行事務處理。

以上是最簡單的流程,好多地方失敗的流程沒有寫出來。

本地訊息表優缺點

優點:

  • 訊息可靠性由應用開發保證,也就是說自己可以靈活實現
  • 執行效率有提高

缺點:

  • 與業務邏輯繫結
  • 需要設計訊息表,還需要定時後臺任務,自己實現複雜業務邏輯

還有基於MQ事務訊息的方案,也是最終一致性方案的一種。

八、參考