分佈式事務模型詳解
- 分佈式基礎理論
1.1 CAP 理論
CAP 理論可以表述爲,一個分佈式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition Tolerance)這三項中的兩項。
一致性是指 “所有節點同時看到相同的數據”,即更新操作成功並返回客戶端完成後,所有節點在同一時間的數據完全一致,等同於所有節點擁有數據的最新版本。
可用性是指 “任何時候,讀寫都是成功的”,即服務一直可用,而且是正常響應時間。我們平時會看到一些 IT 公司的對外宣傳,比如系統穩定性已經做到 3 個 9、4 個 9,即 99.9%、99.99%,這裏的 N 個 9 就是對可用性的一個描述,叫做 SLA,即服務水平協議。比如我們說月度 99.95% 的 SLA,則意味着每個月服務出現故障的時間只能佔總時間的 0.05%,如果這個月是 30 天,那麼就是 21.6 分鐘。
分區容錯性具體是指 “當部分節點出現消息丟失或者分區故障的時候,分佈式系統仍然能夠繼續運行”,即系統容忍網絡出現分區,並且在遇到某節點或網絡分區之間網絡不可達的情況下,仍然能夠對外提供滿足一致性和可用性的服務。
分佈式系統所關注的,就是在 Partition Tolerance 的前提下,如何實現更好的 A 和更穩定的 C。業務上對一致性的要求會直接反映在系統設計中,典型的就是 CP 和 AP 架構。
- CP 架構:對於 CP 來說,放棄可用性,追求一致性和分區容錯性。
- AP 架構:對於 AP 來說,放棄強一致性,追求分區容錯性和可用性,這是很多分佈式系統設計時的選擇,後面的 Base 也是根據 AP 來擴展的。
對於多數大型互聯網應用的場景,結點衆多、部署分散,而且現在的集羣規模越來越大,所以節點故障、網絡故障是常態,而且要保證服務可用性達到 N 個 9(99.99..%),並要達到良好的響應性能來提高用戶體驗,因此一般都會做出如下選擇:保證 P 和 A,捨棄 C 強一致,保證最終一致性。
1.2 BASE 理論
Base 是三個短語的簡寫,即基本可用(Basically Available)、軟狀態(Soft State)和最終一致性(Eventually Consistent)。
Base 理論的核心思想是最終一致性,即使無法做到強一致性(Strong Consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual Consistency)。
基本可用
基本可用比較好理解,就是不追求 CAP 中的「任何時候,讀寫都是成功的」,而是系統能夠基本運行,一直提供服務。基本可用強調了分佈式系統在出現不可預知故障的時候,允許損失部分可用性,相比正常的系統,可能是響應時間延長,或者是服務被降級。
舉個例子,在雙十一秒殺活動中,如果搶購人數太多超過了系統的 QPS 峯值,可能會排隊或者提示限流,這就是通過合理的手段保護系統的穩定性,保證主要的服務正常,保證基本可用。
軟狀態
軟狀態可以對應 ACID 事務中的原子性,在 ACID 的事務中,實現的是強一致性,要麼全做要麼不做,所有用戶看到的數據一致。其中的原子性(Atomicity)要求多個節點的數據副本都是一致的,強調數據的一致性。
原子性可以理解爲一種 “硬狀態”,軟狀態則是允許系統中的數據存在中間狀態,並認爲該狀態不影響系統的整體可用性,即允許系統在多個不同節點的數據副本存在數據延時。
最終一致性
數據不可能一直是軟狀態,必須在一個時間期限之後達到各個節點的一致性,在期限過後,應當保證所有副本保持數據一致性,也就是達到數據的最終一致性。
在系統設計中,最終一致性實現的時間取決於網絡延時、系統負載、不同的存儲選型、不同數據複製方案設計等因素。
CAP 及 Base 的關係
Base 理論是在 CAP 上發展的,CAP 理論描述了分佈式系統中數據一致性、可用性、分區容錯性之間的制約關係,當你選擇了其中的兩個時,就不得不對剩下的一個做一定程度的犧牲。
Base 理論則是對 CAP 理論的實際應用,也就是在分區和副本存在的前提下,通過一定的系統設計方案,放棄強一致性,實現基本可用,這是大部分分佈式系統的選擇,比如 NoSQL 系統、微服務架構。
- 分佈式事務模型
2.1 強一致性模型
強一致性事務主要用於對數據一致性要求比較高, 在任意時
刻都能查詢出最新寫入的數據的場景,比如跨行轉賬。
DTP 模型
DTP 模型是 X/Open 組織定義的一套分佈式事務標準。這套標準主要定義了實現分佈式事務的規範和 API 接口,具體的實現則交給相應的廠商來實現。
在 DTP 模型中定義了 3 個核心組件:
-
AP(Application Program)
應用程序,AP 組件定義了分佈式事務(也即全局事務)的邊界(即事務的開始和結束)以及組成事務的具體操作(Actions);
-
RM(Resource Managers)
資源管理器,RM 指的是諸如 MySQL、Oracle 這樣的數據庫或者相應的數據庫驅動或可訪問的文件系統或者打印機服務器,用以提供訪問數據庫資源的接口;
-
TM(Transaction Manager)
事務管理器,TM 是分佈式事務的協調者,其負責爲分佈式事務分配事務 ID,監控事務的執行過程,負責事務的完成和容錯工作。TM 管理的分佈式事務可以跨多個 RM,TM 還管理 2PC 協議,協調分佈式事務的提交 / 回滾決策。
在 DTP 模型中使用了 2 種通信規範:
- TX 規範,其定義了用於在 AP 組件和 TM 組件之間的通訊 API 規範,如 tx_begin()、tx_end()、tx_info() 等接口用以開啓、結束和查詢分佈式事務;
- XA(eXtended Architecture) 規範,其定義了 TM 和 RM 之間通信的 API 規範。XA 協議中規定了 DTP 模型中 RM 需要提供 prepare、commit、rollback 接口給 TM 調用,以實現兩階段提交。
XA 規範
XA 規範是 X/Open 組織針對二階段提交協議的實現做的規範。目前幾乎所有的主流數據庫都對 XA 規範提供了支持。XA 規範的最主要的作用是,就是定義了 RM-TM 的交互接口。
兩階段提交協議(Two Phase Commit)不是在 XA 規範中提出,但是 XA 規範對其進行了優化。XA 規範對兩階段提交協議有 2 點優化:
- 只讀斷言
在 Phase 1 中,RM 可以斷言 “我這邊不涉及數據增刪改” 來答覆 TM 的 prepare 請求,從而讓這個 RM 脫離當前的全局事務,從而免去了 Phase 2。
- 一階段提交
如果需要增刪改的數據都在同一個 RM 上,TM 可以使用一階段提交——跳過兩階段提交中的 Phase 1,直接執行 Phase 2。
執行流程
DTP 中的分佈式事務(即全局事務)的執行流程如下:
(1)首先,在系統中的所有 RM 通過 XA 規範提供的 API 向 TM 註冊,即 RM 涉及的分支事務處理將納入到 TM 統一管理;
(2)然後,AP 開啓全局事務,AP 通過 TX 規範提供的 API 向 TM 申請全局事務的開啓,此時,全局事務正式開啓。TM 會返回其爲本次全局事務分配的全局事務 ID,即 XID,給提出全局事務的 AP;
(3)接着,AP 根據獲取到的 XID 開始其操作序列,具體的操作就是向不同的 RM 發送 SQL 操作請求;
(4)然後,RM 根據接受的來自 AP 的 SQL 操作請求進行本地數據庫操作(即執行本地事務)。RM 在執行本地事務時會通過 TM 提供的 XA 規範 API 提交分支事務請求(請求中攜帶 XID)。TM 在接收到 RM 的分支事務請求後,分配分支事務 ID 並將其反饋給相應的 RM;
(5)TM 與 RM 進行基於 2PC 協議的事務預提交,及進行 Prepare 操作;
(6)當 TM 根據 RM Prepare 操作的執行情況決策出現有分支事務已經全部完成 Prepare 操作,如果是,那麼 TM 向 AP 發送 Commit 決策結果;否則,那麼 TM 向 AP 發送 Rollback 決策結果。當進行 2PC 協議時,如果 RM 出現故障,TM 負責協調故障處理,保證其決策可進行;
(7)當 AP 接收到 TM 的決策時,如果後續還有分支事務需要執行,那麼 AP 繼續步驟③;否則,AP 決策分佈式事務的結果是 Commit 還是 Rollback。
(8)如果 AP 決定 Commit/Rollback 分佈式事務,那麼它將通過 TX 規範 API 通知 TM,最終的執行決定由 TM 通過 2PC 協議通知 RM 完成;
(9)最後,RM 向 TM 提出註銷申請,TM 註銷已註冊的 RM;AP 向 TM 提交分佈式事務結束請求,TM 結束分佈式事務。
2PC
兩階段提交(Two Phase Commit),就是將提交 (commit) 過程劃分爲 2 個階段(Phase):
階段 1:
TM(事務管理器)通知各個 RM(資源管理器)準備提交它們的事務分支。如果 RM 判斷自己進行的工作可以被提交,那就對工作內容進行持久化,再給 TM 肯定答覆;要是發生了其他情況,那給 TM 的都是否定答覆。
以 mysql 數據庫爲例,在第一階段,事務管理器向所有涉及到的數據庫服務器發出 prepare"準備提交" 請求,數據庫收到請求後執行數據修改和日誌記錄等處理,處理完成後只是把事務的狀態改成 "可以提交", 然後把結果返回給事務管理器。
階段 2
TM 根據階段 1 各個 RM prepare 的結果,決定是提交還是回滾事務。如果所有的 RM 都 prepare 成功,那麼 TM 通知所有的 RM 進行提交;如果有 RM prepare 失敗的話,則 TM 通知所有 RM 回滾自己的事務分支。
以 mysql 數據庫爲例,如果第一階段中所有數據庫都 prepare 成功,那麼事務管理器向數據庫服務器發出 "確認提交" 請求,數據庫服務器把事務的 "可以提交" 狀態改爲 "提交完成" 狀態,然後返回應答。如果在第一階段內有任何一個數據庫的操作發生了錯誤,或者事務管理器收不到某個數據庫的迴應,則認爲事務失敗,回撤所有數據庫的事務。數據庫服務器收不到第二階段的確認提交請求,也會把 "可以提交" 的事務回撤。
兩階段提交方案下全局事務的 ACID 特性,是依賴於 RM 的。一個全局事務內部包含了多個獨立的事務分支,這一組事務分支要麼都成功,要麼都失敗。各個事務分支的 ACID 特性共同構成了全局事務的 ACID 特性。也就是將單個事務分支支持的 ACID 特性提升一個層次到分佈式事務的範疇。
2PC 存在的問題
二階段提交看起來確實能夠提供原子性的操作,但是不幸的是,二階段提交還是有幾個缺點的:
- 同步阻塞問題
2PC 中的參與者是阻塞的。在第一階段收到請求後就會預先鎖定資源,一直到 commit 後纔會釋放。
- 單點故障
由於協調者的重要性,一旦協調者 TM 發生故障,參與者 RM 會一直阻塞下去。尤其在第二階段,協調者發生故障,那麼所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。
- 數據不一致
若協調者第二階段發送提交請求時崩潰,可能部分參與者收到 commit 請求提交了事務,而另一部分參與者未收到 commit 請求而放棄事務,從而造成數據不一致的問題。
3PC
三階段提交(3PC),是二階段提交(2PC)的改進版本。
與兩階段提交不同的是,三階段提交有兩個改動點:
- 引入超時機制。同時在協調者和參與者中都引入超時機制。
- 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參與節點的狀態是一致的。也就是說,除了引入超時機制之外,3PC 把 2PC 的準備階段再次一分爲二,這樣三階段提交就有 CanCommit、PreCommit、DoCommit 三個階段。
- CanCommit 階段
3PC 的 CanCommit 階段其實和 2PC 的準備階段很像。協調者向參與者發送 commit 請求,參與者如果可以提交就返回 Yes 響應,否則返回 No 響應。
1. 事務詢問 協調者向參與者發送 CanCommit 請求。詢問是否可以執行事務提交操作。然後開始等待參與者的響應。
2. 響應反饋 參與者接到 CanCommit 請求之後,正常情況下,如果其自身認爲可以順利執行事務,則返回 Yes 響應,並進入預備狀態。否則反饋 No
- PreCommit 階段
協調者根據參與者的反應情況來決定是否可以記性事務的 PreCommit 操作。根據響應情況,有以下兩種可能。
假如協調者從所有的參與者獲得的反饋都是 Yes 響應,那麼就會執行事務的預執行。
1. 發送預提交請求 協調者向參與者發送 PreCommit 請求,並進入 Prepared 階段。
2. 事務預提交 參與者接收到 PreCommit 請求後,會執行事務操作,並將 undo 和 redo 信息記錄到事務日誌中。
3. 響應反饋 如果參與者成功的執行了事務操作,則返回 ACK 響應,同時開始等待最終指令。
假如有任何一個參與者向協調者發送了 No 響應,或者等待超時之後,協調者都沒有接到參與者的響應,那麼就執行事務的中斷。
1. 發送中斷請求 協調者向所有參與者發送 abort 請求。
2. 中斷事務 參與者收到來自協調者的 abort 請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。
- doCommit 階段
該階段進行真正的事務提交,也可以分爲以下兩種情況。
- Case 1:執行提交
1. 發送提交請求 協調接收到參與者發送的 ACK 響應,那麼他將從預提交狀態進入到提交狀態。並向所有參與者發送 doCommit 請求。
2. 事務提交 參與者接收到 doCommit 請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。
3. 響應反饋 事務提交完之後,向協調者發送 Ack 響應。
4. 完成事務 協調者接收到所有參與者的 ack 響應之後,完成事務。
- Case 2:中斷事務 協調者沒有接收到參與者發送的 ACK 響應(可能是接受者發送的不是 ACK 響應,也可能響應超時),那麼就會執行中斷事務。
1. 發送中斷請求 協調者向所有參與者發送 abort 請求
2. 事務回滾 參與者接收到 abort 請求之後,利用其在階段二記錄的 undo 信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。
3. 反饋結果 參與者完成事務回滾之後,向協調者發送 ACK 消息
4. 中斷事務 協調者接收到參與者反饋的 ACK 消息之後,執行事務的中斷。
相對於 2PC,3PC 主要解決的單點故障問題,並減少阻塞,因爲一旦參與者無法及時收到來自協調者的信息之後,他會默認執行 commit。而不會一直持有事務資源並處於阻塞狀態。但是這種機制也會導致數據一致性問題,因爲,由於網絡原因,協調者發送的 abort 響應沒有及時被參與者接收到,那麼參與者在等待超時之後執行了 commit 操作。這樣就和其他接到 abort 命令並執行回滾的參與者之間存在數據不一致的情況。
瞭解了 2PC 和 3PC 之後,我們可以發現,無論是二階段提交還是三階段提交都無法徹底解決分佈式的一致性問題。
2.2 最終一致性模型
TCC
TCC 是 Try-Confirm-Cancel 的簡稱:
Try 階段:調用 Try 接口,嘗試執行業務,完成所有業務檢查,預留業務資源。
Confirm 或 Cancel 階段:兩者是互斥的,只能進入其中一個,並且都滿足冪等性,允許失敗重試。
-
Confirm 操作:對業務系統做確認提交,確認執行業務操作,不做其他業務檢查,只使用 Try 階段預留的業務資源。
-
Cancel 操作:在業務執行錯誤,需要回滾的狀態下執行業務取消,釋放預留資源。
Try 階段失敗可以 Cancel,如果 Confirm 和 Cancel 階段失敗了怎麼辦?
TCC 中會添加事務日誌,如果 Confirm 或者 Cancel 階段出錯,則會進行重試,所以這兩個階段需要支持冪等;如果重試失敗,則需要人工介入進行恢復和處理等。
TCC VS XA
XA 是資源層面的分佈式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。TCC 是業務層面的分佈式事務,最終一致性,不會一直持有資源的鎖。
1) 在階段 1:
在 XA 中,各個 RM 準備提交各自的事務分支,事實上就是準備提交資源的更新操作 (insert、delete、update 等);而在 TCC 中,是主業務活動請求(try) 各個從業務服務預留資源。
2) 在階段 2:
XA 根據第一階段每個 RM 是否都 prepare 成功,判斷是要提交還是回滾。如果都 prepare 成功,那麼就 commit 每個事務分支,反之則 rollback 每個事務分支。
TCC 中,如果在第一階段所有業務資源都預留成功,那麼 confirm 各個從業務服務,否則取消 (cancel) 所有從業務服務的資源預留請求。
TCC VS DTP
-
TCC 模型中的主業務服務 相當於 DTP 模型中的 AP,TCC 模型中的從業務服務 相當於 DTP 模型中的 RM
-
TCC 模型中,從業務服務提供的 try、confirm、cancel 接口, 相當於 DTP 模型中 RM 提供的 prepare、commit、rollback 接口
DTP 模型和 TCC 模型中都有一個事務管理器。不同的是:
-
在 DTP 模型中,階段 1 的 (prepare) 和階段 2 的(commit、rollback),都是由 TM 進行調用的。
-
在 TCC 模型中,階段 1 的 try 接口是主業務服務調用 (綠色箭頭),階段 2 的(confirm、cancel 接口) 是事務管理器 TM 調用(紅色箭頭)。
可靠消息最終一致性
異步化在分佈式系統設計中隨處可見,基於消息隊列的最終一致性就是一種異步事務機制,在業務中廣泛應用。
本地消息表
本地消息表的方案最初是由 ebay 的工程師提出,核心思想是將分佈式事務拆分成本地事務進行處理,通過消息日誌的方式來異步執行。
本地消息表是一種業務耦合的設計,消息生產方需要額外建一個事務消息表,並記錄消息發送狀態,消息消費方需要處理這個消息,並完成自己的業務邏輯,另外會有一個異步機制來定期掃描未完成的消息,確保最終一致性。
下面我們用下單減庫存業務來簡單模擬本地消息表的實現過程:
(1)系統收到下單請求,將訂單業務數據存入到訂單庫中,並且同時存儲該訂單對應的消息數據,比如購買商品的 ID 和數量,消息數據與訂單庫爲同一庫,更新訂單和存儲消息爲一個本地事務,要麼都成功,要麼都失敗。
(2)庫存服務通過消息中間件收到庫存更新消息,調用庫存服務進行業務操作,同時返回業務處理結果。
(3)消息生產方,也就是訂單服務收到處理結果後,將本地消息表的數據刪除或者設置爲已完成。
(4)設置異步任務,定時去掃描本地消息表,發現有未完成的任務則重試,保證最終一致性。
RocketMQ 事務消息
RocketMQ 事務消息是一種支持分佈式事務的消息。它通過引入 prepare、commit 和 rollback 三個階段,來確保事務消息的一致性。
-
prepare 階段:消息發送方發送半消息,此時消息的狀態爲 “待提交”。
-
commit 階段:消息發送方向 RocketMQ 發送 commit 消息請求,RocketMQ 判斷此時半消息是否被確認,如果半消息已被確認,則將消息標記爲 “可消費” 並提交事務。如果半消息未被確認,則將消息標記爲 “不可消費” 並終止事務。
-
rollback 階段:消息發送方向 RocketMQ 發送 rollback 消息請求,RocketMQ 將半消息標記爲 “不可消費” 並回滾事務。
最大努力通知
最大努力通知型 (Best-effort delivery) 是最簡單的一種柔性事務,適用於一些最終一致性時間敏感度低的業務,且被動方處理結果 不影響主動方的處理結果。典型的使用場景:如銀行通知、商戶通知等。最大努力通知型的實現方案,一般符合以下特點:
1、不可靠消息:業務活動主動方,在完成業務處理之後,向業務活動的被動方發送消息,直到通知 N 次後不再通知,允許消息丟失 (不可靠消息)。
2、定期校對:業務活動的被動方,根據定時策略,向業務活動主動方查詢 (主動方提供查詢接口),恢復丟失的業務消息。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cmYkYuN-86pDR5IdsGzwsw