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

一、用戶匯款場景

用戶 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 恢復事務。

來看張圖:

image-20230705173540921

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

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

五、本地事務

MySQL 中的事務也叫本地事務,有的人也譯爲局部事務

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

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

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

image-20230704140901046

​ (本地事務)

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

image-20230704143020383

對於本地事務,開發語言都會對數據庫提供的事務接口進行封裝,然後在應用。

在 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 個:

image-20230704151953404

​ (來自: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,開放式系統互聯事務處理)。

那就有五個了。

image-20230705114051559

​ (各組件和 XA 及 XA+ 接口,XA and XA+ Interfaces 在 XA+ Spec v2 有定義)

image-20230705024432006

​ (分佈式事務處理 (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資源管理器。)

image-20230706215000083

2PC 缺點

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

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

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

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

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

3PC 三階段提交

3PC 三階段提交介紹

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

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

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

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

image-20230707135118489

第一階段: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 執行操作。

image-20230707174959406

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 事務的時序圖:

image-20230708030513160

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

image-20230708031335071

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

TCC 優缺點

優點:

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

缺點:

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

本地消息表方案

本地消息表介紹

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

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

什麼是可靠消息呢?

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

上面可靠消息如果不加一個存儲“消息”的中間件,那麼跟 2PC 的操作步驟很相似,變成了一個同步的操作情況,消息發送後接收者直接接收,不存儲消息。如果加一個存儲消息的中間件,那麼就變成了異步讀寫方案了,解耦了消息發送方和消息接收方,天然就不是一個強一致性方案。

這個存儲消息的地方,可以是本地消息表(MySQL、Redis等)、消息隊列等各種存儲中間件。這裏介紹的是本地消息表方案。

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

image-20230710005321541

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

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

本地消息表優缺點

優點:

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

缺點:

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

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

八、參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章