1. 程式人生 > 實用技巧 >微服務中的分散式事務有這一篇就夠了,詳細從0到1深入淺出理解分散式事務及解決方案

微服務中的分散式事務有這一篇就夠了,詳細從0到1深入淺出理解分散式事務及解決方案

詳細從0到1深入淺出理解分散式事務及解決方案

1. 基礎概念

  1.1 什麼是事務

  1.2 本地事務

  1.3 分散式事務

  1.4 分散式事務產生的情景

2. 分散式事務基礎理論

  2.1 CAP理論

    2.1.1 理解CAP

      C - Consistency

      A - Availability

      P - Partition tolerance

    2.1.2 CAP組合方式CAP的組合方式

    2.1.3 總結

  2.2 BASE 理論

3. 分散式事務解決方案之 2PC

  3.1 什麼是 2PC3.2 解決方案

    3.2.1 XA 方案

    3.2.2 Seata 方案

  3.3 小結

4. 分散式事務解決方案之TCC

  4.1 什麼是TCC事務

  4.2 TCC 異常處理

  4.3 小結

5. 分散式事務解決方案之可靠訊息最終一致性

  5.1 什麼是可靠訊息最終一致性事務

  5.2 解決方案

    5.2.1 本地訊息表方案

  5.3 小結

6 分散式事務解決方案之最大努力通知

  6.1 什麼是最大努力通知

  6.2 解決方案

  6.3 小結

7 總結

1. 基礎概念

1.1 什麼是事務

事務可以看做是一次大的活動,它由不同的小活動組成,這些活動要麼全部成功,要麼全部失敗。

1.2 本地事務

在計算機系統中,更多的是通過關係型資料庫來控制事務,這是利用資料庫本身的事務特性來實現的,因此叫資料庫事務,由於應用主要靠關係資料庫來控制事務,而資料庫通常和應用在同一個伺服器,所以基於關係型資料庫的事務又被稱為本地事務。

資料庫事務的四大特性:ACID

A(Atomic):原子性,構成事務的所有操作,要麼都執行完成,要麼全部不執行,不可能出現部分成功部分失敗的情況。

C(Consistency):一致性,在事務執行前後,資料庫的一致性約束沒有被破壞。比如:張三向李四轉 100 元,轉賬前和轉賬後的資料是正確狀態這叫一致性,如果出現張三轉出 100 元,李四賬戶沒有增加 100 元這就出現了數 據錯誤,就沒有達到一致性。

I(Isolation):隔離性,資料庫中的事務一般都是併發的,隔離性是指併發的兩個事務的執行互不干擾,一個事務不能看到其他事務的執行過程的中間狀態。通過配置事務隔離級別可以比避免髒讀、重複讀問題。

D(Durability)

:永續性,事務完成之後,該事務對資料的更改會持久到資料庫,且不會被回滾。

資料庫事務在實現時會將一次事務的所有操作全部納入到一個不可分割的執行單元,該執行單元的所有操作要麼都成功,要麼都失敗,只要其中任一操作執行失敗,都將導致整個事務的回滾。

1.3 分散式事務

隨著網際網路的快速發展,軟體系統由原來的單體應用轉變為分散式應用,下圖描述了單體應用向微服務的演變:

分散式系統會把一個應用系統拆分為可獨立部署的多個服務,因此需要服務與服務之間遠端協作才能完成事務操作,這種分散式系統環境下由不同的服務之間通過網路遠端協作完成事務稱之為分散式事務,例如使用者註冊送積分事務、建立訂單減庫存事務,銀行轉賬事務等都是分散式事務。

我們知道本地事務依賴資料庫本身提供的事務特性來實現,因此以下邏輯可以控制本地事務:

begin transaction;
//1.本地資料庫操作:張三減少金額
//2.本地資料庫操作:李四增加金額
commit transation;

但是在分散式環境下,會變成下邊這樣:

begin transaction;
//1.本地資料庫操作:張三減少金額
//2.遠端呼叫:讓李四增加金額
commit transation;

可以設想,當遠端呼叫讓李四增加金額成功了,由於網路問題遠端呼叫並沒有返回,此時本地事務提交失敗就回滾了張三減少金額的操作,此時張三和李四的資料就不一致了。

因此在分散式架構的基礎上,傳統資料庫事務就無法使用了,張三和李四的賬戶不在一個數據庫中甚至不在一個應用系統裡,實現轉賬事務需要通過遠端呼叫,由於網路問題就會導致分散式事務問題。

1.4 分散式事務產生的情景

  1. 跨JVM程序產生分散式事務

    典型的場景就是微服務架構:微服務之間通過遠端呼叫完成事務操作。比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減少庫存。

  1. 跨資料庫例項產生分散式事務

    單體系統訪問多個數據庫例項當單體系統需要訪問多個數據庫(例項)時就會產生分散式事務。比如:使用者資訊和訂單資訊分別在兩個MySQL例項儲存,使用者管理系統刪除使用者資訊,需要分別刪除使用者資訊及使用者的訂單資訊,由於資料分佈在不同的資料例項,需要通過不同的資料庫連結去操作資料,此時產生分散式事務。

  1. 多服務訪問同一個資料庫例項

    訂單微服務和庫存微服務即使訪問同一個資料庫也會產生分散式事務,原因就是跨JVM程序,兩個微服務持有了不同的資料庫連結進行資料庫操作,此時產生分散式事務。

2. 分散式事務基礎理論

通過前面的學習,我們瞭解到了分散式事務的基礎概念。與本地事務不同的是,分散式系統之所以叫分散式,是因為提供服務的各個節點分佈在不同機器上,相互之間通過網路互動。不能因為有一點網路問題就導致整個系統無法提供服務,網路因素成為了分散式事務的考量標準之一。因此,分散式事務需要更進一步的理論支援,接下來,我們先來學習一下分散式事務的CAP理論。

2.1 CAP理論

2.1.1 理解CAP

CAP 是 Consistency、Availability、Partition tolerance 三個單詞的縮寫,分別表示一致性、可用性、分割槽容忍性。

下面為了方便對CAP理論的理解,我們結合電商系統中的一些業務場景來理解CAP。

如下圖,是商品資訊管理的執行流程:

整體執行流程如下

  1. 商品服務請求主資料庫寫入商品資訊(新增商品、修改商品、刪除商品)

  2. 主資料庫向商品服務響應寫入成功

  3. 商品服務請求從資料庫讀取商品資訊

C - Consistency

一致性是指寫操作後的讀操作可以讀取到最新的資料狀態,當資料分佈在多個節點上,從任意結點讀取到的資料都是最新的狀態。

上圖中,商品資訊的讀寫要滿足一致性就是要實現如下目標:

  1. 商品服務寫入主資料庫成功,則向從資料庫查詢新資料也成功。

  2. 商品服務寫入主資料庫失敗,則向從資料庫查詢新資料也失敗。

如何實現一致性?

  1. 寫入主資料庫後要將資料同步到從資料庫。

  2. 寫入主資料庫後,在向從資料庫同步期間要將從資料庫鎖定,待同步完成後再釋放鎖,以免在新資料寫入成功後,向從資料庫查詢到舊的資料。

分散式系統一致性的特點:

  1. 由於存在資料同步的過程,寫操作的響應會有一定的延遲。

  2. 為了保證資料一致性會對資源暫時鎖定,待資料同步完成釋放鎖定資源。

  3. 如果請求資料同步失敗的結點則會返回錯誤資訊,一定不會返回舊資料。

A - Availability

可用性是指任何事務操作都可以得到響應結果,且不會出現響應超時或響應錯誤。

上圖中,商品資訊讀取滿足可用性就是要實現如下目標:

1. 從資料庫接收到資料查詢的請求則立即能夠響應資料查詢結果。
  1. 從資料庫不允許出現響應超時或響應錯誤。

如何實現可用性

1. 寫入主資料庫後要將資料同步到從資料庫。
  1. 由於要保證從資料庫的可用性,不可將從資料庫中的資源進行鎖定。

  2. 即時資料還沒有同步過來,從資料庫也要返回要查詢的資料,哪怕是舊資料,如果連舊資料也沒有則可以按照約定返回一個預設資訊,但不能返回錯誤或響應超時。

分散式系統可用性的特點:所有請求都有響應,且不會出現響應超時或響應錯誤

P - Partition tolerance

通常分散式系統的各各結點部署在不同的子網,這就是網路分割槽,不可避免的會出現由於網路問題而導致結點之間通訊失敗,此時仍可對外提供服務,這叫分割槽容忍性。

上圖中,商品資訊讀寫滿足分割槽容忍性就是要實現如下目標:

1. 主資料庫向從資料庫同步資料失敗不影響讀寫操作。
  1. 其一個結點掛掉不影響另一個結點對外提供服務。

如何實現分割槽容忍性?

1. 儘量使用非同步取代同步操作,例如使用非同步方式將資料從主資料庫同步到從資料,這樣結點之間能有效的實現鬆耦合。
  1. 新增從資料庫結點,其中一個從結點掛掉其它從結點提供服務。

分散式分割槽容忍性的特點:分割槽容忍性分是布式系統具備的基本能力

2.1.2 CAP組合方式

上邊商品管理的例子是否同時具備 CAP 呢?

在所有分散式事務場景中不會同時具備 CAP 三個特性,因為在具備了P的前提下C和A是不能共存的

比如,下圖滿足了P即表示實現分割槽容忍:

本圖分割槽容忍的含義是:

  1. 主資料庫通過網路向從資料庫同步資料,可以認為主從資料庫部署在不同的分割槽,通過網路進行互動。

  2. 當主資料庫和從資料庫之間的網路出現問題不影響主資料庫和從資料庫對外提供服務。

  3. 其中一個節點掛掉不影響另一個節點對外提供服務。

如果要實現 C 則必須保證資料一致性,在資料同步的時候為防止向從資料庫查詢不一致的資料則需要將從資料庫資料鎖定,待同步完成後解鎖,如果同步失敗從資料庫要返回錯誤資訊或超時資訊。

如果要實現 A 則必須保證資料可用性,不管任何時候都可以向從資料查詢資料,則不會響應超時或返回錯誤資訊。通過分析發現在滿足P的前提下 C 和 A 存在矛盾性。

CAP的組合方式

所以在生產中對分散式事務處理時要根據需求來確定滿足 CAP 的哪兩個方面。

  1. AP

    放棄一致性,追求分割槽容忍性和可用性。這是很多分散式系統設計時的選擇。

    例如:上邊的商品管理,完全可以實現 AP,前提是隻要使用者可以接受所查詢到的資料在一定時間內不是最新的即可。

    通常實現 AP 都會保證最終一致性,後面將的BASE理論就是根據 AP 來擴充套件的,一些業務場景比如:訂單退款,今日退款成功,明日賬戶到賬,只要使用者可以接受在一定的時間內到賬即可。

  2. CP

    放棄可用性,追求一致性和分割槽容錯性,zookeeper 其實就是追求的強一致,又比如跨行轉賬,一次轉賬請求要等待雙方銀行系統都完成整個事務才算完成。

  3. CA

    放棄分割槽容忍性,即不進行分割槽,不考慮由於網路不通或結點掛掉的問題,則可以實現一致性和可用性。那麼系統將不是一個標準的分散式系統,最常用的關係型資料就滿足了 CA。

    上邊的商品管理,如果要實現 CA 則架構如下:

主資料庫和從資料庫中間不在進行資料同步,資料庫可以響應每次的查詢請求,通過事務隔離級別實現每個查詢請求都可以返回最新的資料。

2.1.3 總結

CAP 是一個已經被證實的理論,一個分散式系統最多隻能同時滿足:一致性(Consistency)、可用性(Availability)和分割槽容忍性(Partition tolerance)這三項中的兩項。它可以作為我們進行架構設計、技術選型的考量標準。對於多數大型網際網路應用的場景,結點眾多、部署分散,而且現在的叢集規模越來越大,所以節點故障、網路故障是常態,而且要保證服務可用性達到 N 個 9(99.99..%),並要達到良好的響應效能來提高使用者體驗,因此一般都會做出如下選擇:保證 P 和 A ,捨棄 C 強一致,保證最終一致性。

2.2 BASE 理論

  1. 強一致性和最終一致性

    CAP 理論告訴我們一個分散式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分割槽容忍性(Partition tolerance)這三項中的兩項,其中AP在實際應用中較多,AP 即捨棄一致性,保證可用性和分割槽容忍性,但是在實際生產中很多場景都要實現一致性,比如前邊我們舉的例子主資料庫向從資料庫同步資料,即使不要一致性,但是最終也要將資料同步成功來保證資料一致,這種一致性和 CAP 中的一致性不同,CAP 中的一致性要求 在任何時間查詢每個結點資料都必須一致,它強調的是強一致性,但是最終一致性是允許可以在一段時間內每個結點的資料不一致,但是經過一段時間每個結點的資料必須一致,它強調的是最終資料的一致性。

  2. Base 理論介紹

    BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。BASE 理論是對 CAP 中 AP 的一個擴充套件,通過犧牲強一致性來獲得可用性,當出現故障允許部分不可用但要保證核心功能可用,允許資料在一段時間內是不一致的,但最終達到一致狀態。滿足BASE理論的事務,我們稱之為“柔性事務”。

  • 基本可用:分散式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。如電商網站交易付款出現問題了,商品依然可以正常瀏覽。

  • 軟狀態:由於不要求強一致性,所以BASE允許系統中存在中間狀態(也叫軟狀態),這個狀態不影響系統可用性,如訂單的"支付中"、“資料同步中”等狀態,待資料最終一致後狀態改為“成功”狀態。

  • 最終一致:最終一致是指經過一段時間後,所有節點資料都將會達到一致。如訂單的"支付中"狀態,最終會變 為“支付成功”或者"支付失敗",使訂單狀態與實際交易結果達成一致,但需要一定時間的延遲、等待。

3. 分散式事務解決方案之 2PC

前面學習了分散式事務的基礎理論,以理論為基礎,針對不同的分散式場景業界常見的解決方案有 2PC、3PC、TCC、可靠訊息最終一致性、最大努力通知這幾種。

3.1 什麼是 2PC

2PC 即兩階段提交協議,是將整個事務流程分為兩個階段,準備階段(Prepare phase)、提交階段(commit phase),2 是指兩個階段,P 是指準備階段,C 是指提交階段。

舉例:張三和李四好久不見,老友約起聚餐,飯店老闆要求先買單,才能出票。這時張三和李四分別抱怨近況不如意,囊中羞澀,都不願意請客,這時只能AA。只有張三和李四都付款,老闆才能出票安排就餐。但由於張三和李四都是鐵公雞,形成了尷尬的一幕:

準備階段:老闆要求張三付款,張三付款。老闆要求李四付款,李四付款。

提交階段:老闆出票,兩人拿票紛紛落座就餐。

例子中形成了一個事務,若張三或李四其中一人拒絕付款,或錢不夠,店老闆都不會給出票,並且會把已收款退回。

整個事務過程由事務管理器和參與者組成,店老闆就是事務管理器,張三、李四就是事務參與者,事務管理器負責決策整個分散式事務的提交和回滾,事務參與者負責自己本地事務的提交和回滾。

在計算機中部分關係資料庫如 Oracle、MySQL 支援兩階段提交協議,如下圖:

  1. 準備階段(Prepare phase):事務管理器給每個參與者傳送 Prepare 訊息,每個資料庫參與者在本地執行事務,並寫本地的 Undo/Redo 日誌,此時事務沒有提交。(Undo 日誌是記錄修改前的資料,用於資料庫回滾,Redo 日誌是記錄修改後的資料,用於提交事務後寫入資料檔案)

  2. 提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時訊息時,直接給每個參與者傳送回滾(Rollback)訊息;否則,傳送提交(Commit)訊息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意:必須在最後階段釋放鎖資源

下圖展示了2PC的兩個階段,分成功和失敗兩個情況說明:

成功情況

失敗情況

3.2 解決方案

3.2.1 XA 方案

2PC的傳統方案是在資料庫層面實現的,如 Oracle、MySQL 都支援 2PC 協議,為了統一標準減少行業內不必要的對接成本,需要制定標準化的處理模型及介面標準,國際開放標準組織 Open Group 定義了分散式事務處理模型DTP(Distributed Transaction Processing Reference Model)。

為了讓大家更明確 XA 方案的內容,下面以新使用者註冊送積分為例來說明:

執行流程如下:

  1. 應用程式(AP)持有使用者庫和積分庫兩個資料來源。

  2. 應用程式(AP)通過 TM 通知使用者庫 RM 新增使用者,同時通知積分庫RM為該使用者新增積分,RM 此時並未提交事務,此時使用者和積分資源鎖定。

  3. TM 收到執行回覆,只要有一方失敗則分別向其他 RM 發起回滾事務,回滾完畢,資源鎖釋放。

  4. TM 收到執行回覆,全部成功,此時向所有 RM 發起提交事務,提交完畢,資源鎖釋放。

DTP 模型定義如下角色:

  • AP(Application Program):即應用程式,可以理解為使用 DTP 分散式事務的程式。

  • RM(Resource Manager):即資源管理器,可以理解為事務的參與者,一般情況下是指一個數據庫例項,通過資源管理器對該資料庫進行控制,資源管理器控制著分支事務。

  • TM(Transaction Manager):事務管理器,負責協調和管理事務,事務管理器控制著全域性事務,管理事務生命週期,並協調各個 RM。全域性事務是指分散式事務處理環境中,需要操作多個數據庫共同完成一個工作,這個工作即是一個全域性事務。

  • DTP 模型定義TM和RM之間通訊的介面規範叫XA,簡單理解為資料庫提供的 2PC 介面協議,基於資料庫的 XA 協議來實現 2PC 又稱為 XA 方案

以上三個角色之間的互動方式如下:

1. TM 向 AP 提供 應用程式程式設計介面,AP 通過 TM 提交及回滾事務。
  1. TM 交易中介軟體通過 XA 介面來通知 RM 資料庫事務的開始、結束以及提交、回滾等。

總結

整個 2PC 的事務流程涉及到三個角色 AP、RM、TM。AP 指的是使用 2PC 分散式事務的應用程式;RM 指的是資源管理器,它控制著分支事務;TM 指的是事務管理器,它控制著整個全域性事務。

(1)在準備階段RM 執行實際的業務操作,但不提交事務,資源鎖定

(2)在提交階段TM 會接受 RM 在準備階段的執行回覆,只要有任一個RM執行失敗,TM 會通知所有 RM 執行回滾操作,否則,TM 將會通知所有 RM 提交該事務。提交階段結束資源鎖釋放。

XA方案的問題

  1. 需要本地資料庫支援XA協議。

  2. 資源鎖需要等到兩個階段結束才釋放,效能較差。

3.2.2 Seata 方案

Seata 是由阿里中介軟體團隊發起的開源專案 Fescar,後更名為 Seata,它是一個是開源的分散式事務框架。

傳統 2PC 的問題在 Seata 中得到了解決,它通過對本地關係資料庫的分支事務的協調來驅動完成全域性事務,是工作在應用層的中介軟體。主要優點是效能較好,且不長時間佔用連線資源,它以高效並且對業務 0 侵入的方式解決微服務場景下面臨的分散式事務問題,它目前提供 AT 模式(即 2PC)及 TCC 模式的分散式事務解決方案。

Seata 的設計思想如下:

Seata 的設計目標其一是對業務無侵入,因此從業務無侵入的 2PC 方案著手,在傳統 2PC的基礎上演進,並解決 2PC 方案面臨的問題。

Seata 把一個分散式事務理解成一個包含了若干分支事務全域性事務。全域性事務的職責是協調其下管轄的分支事務達成一致,要麼一起成功提交,要麼一起失敗回滾。此外,通常分支事務本身就是一個關係資料庫的本地事務,下圖是全域性事務與分支事務的關係圖:

與傳統 2PC 的模型類似,Seata 定義了 3 個元件來協議分散式事務的處理過程:

  • Transaction Coordinator(TC):事務協調器,它是獨立的中介軟體,需要獨立部署執行,它維護全域性事務的執行狀態,接收 TM 指令發起全域性事務的提交與回滾,負責與 RM 通訊協調各各分支事務的提交或回滾。

  • Transaction Manager(TM): 事務管理器,TM 需要嵌入應用程式中工作,它負責開啟一個全域性事務,並最終向 TC 發起全域性提交或全域性回滾的指令。

  • Resource Manager(RM):控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器 TC 的指令,驅動分支(本地)事務的提交和回滾。

還拿新使用者註冊送積分舉例Seata的分散式事務過程:

具體的執行流程如下:

  1. 使用者服務的 TM 向 TC 申請開啟一個全域性事務,全域性事務建立成功並生成一個全域性唯一的 XID。

  2. 使用者服務的 RM 向 TC 註冊分支事務,該分支事務在使用者服務執行新增使用者邏輯,並將其納入 XID 對應全域性事務的管轄。

  3. 使用者服務執行分支事務,向用戶表插入一條記錄。

  4. 邏輯執行到遠端呼叫積分服務時(XID 在微服務呼叫鏈路的上下文中傳播)。積分服務的 RM 向 TC 註冊分支事務,該分支事務執行增加積分的邏輯,並將其納入 XID 對應全域性事務的管轄。

  5. 積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢後,返回使用者服務。

  6. 使用者服務分支事務執行完畢。

  7. TM 向 TC 發起針對 XID 的全域性提交或回滾決議。

  8. TC 排程 XID 下管轄的全部分支事務完成提交或回滾請求。

Seata實現2PC與傳統2PC的差別

架構層次方面:傳統 2PC 方案的 RM 實際上是在資料庫層,RM 本質上就是資料庫自身,通過 XA 協議實現,而 Seata 的 RM 是以 jar 包的形式作為中介軟體層部署在應用程式這一側的。

兩階段提交方面:傳統 2PC無論第二階段的決議是 commit 還是 rollback ,事務性資源的鎖都要保持到 Phase2 完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。

3.3 小結

本節講解了傳統 2PC(基於資料庫 XA 協議)和 Seata 實現 2PC 的兩種 2PC 方案,由於 Seata 的 0 侵入性並且解決了傳統 2PC 長期鎖資源的問題,推薦採用 Seata 實現 2PC。

4. 分散式事務解決方案之TCC

4.1 什麼是TCC事務

TCC 是 Try、Confirm、Cancel 三個詞語的縮寫,TCC 要求每個分支事務實現三個操作:預處理 Try、確認 Confirm、撤銷 Cancel。Try 操作做業務檢查及資源預留,Confirm 做業務確認操作,Cancel 實現一個與 Try 相反的操作即回滾操作。TM 首先發起所有的分支事務的 Try 操作,任何一個分支事務的Try操作執行失敗,TM 將會發起所有分支事務的 Cancel 操作,若 Try 操作全部成功,TM 將會發起所有分支事務的 Confirm 操作,其中 Confirm/Cancel 操作若執行失敗,TM 會進行重試。

分支事務成功情況:

分支事務失敗情況:

TCC 分為三個階段:

  1. Try階段是做完業務檢查(一致性)及資源預留(隔離),此階段僅是一個初步操作,它和後續的 Confirm 一起才能真正構成一個完整的業務邏輯。

  2. Confirm階段是做確認提交,Try 階段所有分支事務執行成功後開始執行 Confirm。通常情況下,採用 TCC 則認為 Confirm 階段是不會出錯的。即:只要 Try 成功,Confirm 一定成功。若 Confirm 階段真的出錯了,需引入重試機制或人工處理。

  3. Cancel階段是在業務執行錯誤需要回滾的狀態下執行分支事務的業務取消,預留資源釋放。通常情況下,採用 TCC 則認為 Cancel 階段也是一定成功的。若 Cancel 階段真的出錯了,需引入重試機制或人工處理。

TM 事務管理器

TM事務管理器可以實現為獨立的服務,也可以讓全域性事務發起方充當 TM 的角色,TM 獨立出來是為了成為公 用元件,是為了考慮系統結構和軟體複用。

TM 在發起全域性事務時生成全域性事務記錄,全域性事務 ID 貫穿整個分散式事務呼叫鏈條,用來記錄事務上下文, 追蹤和記錄狀態,由於 Confirm 和 Cancel 失敗需進行重試,因此需要實現為冪等,冪等性是指同一個操作無論請求多少次,其結果都相同。

4.2 TCC 異常處理

TCC需要注意三種異常處理分別是空回滾冪等懸掛

空回滾

在沒有呼叫 TCC 資源 Try 方法的情況下,呼叫了二階段的 Cancel 方法,Cancel 方法需要識別出這是一個空回滾,然後直接返回成功。

出現原因是當一個分支事務所在服務宕機或網路異常,分支事務呼叫記錄為失敗,這個時候其實是沒有執行 Try 階段,當故障恢復後,分散式事務進行回滾則會呼叫二階段的 Cancel 方法,從而形成空回滾。

解決思路是關鍵就是要識別出這個空回滾。思路很簡單就是需要知道一階段是否執行,如果執行了,那就是正常回滾;如果沒執行,那就是空回滾。前面已經說過 TM 在發起全域性事務時生成全域性事務記錄,全域性事務 ID 貫穿整個分散式事務呼叫鏈條。再額外增加一張分支事務記錄表,其中有全域性事務 ID 和分支事務 ID,第一階段 Try 方法裡會插入一條記錄,表示一階段執行了。Cancel 接口裡讀取該記錄,如果該記錄存在,則正常回滾;如果該記錄不存在,則是空回滾。

冪等

通過前面介紹已經瞭解到,為了保證 TCC 二階段提交重試機制不會引發資料不一致,要求 TCC 的二階段 Try、Confirm 和 Cancel 介面保證冪等,這樣不會重複使用或者釋放資源。如果冪等控制沒有做好,很有可能導致資料不一致等嚴重問題。

解決思路在上述"分支事務記錄"中增加執行狀態,每次執行前都查詢該狀態。

懸掛

懸掛就是對於一個分散式事務,其二階段 Cancel 介面比 Try 介面先執行。

出現原因是在 RPC 呼叫分支事務 Try 時,先註冊分支事務,再執行 RPC 呼叫,如果此時 RPC 呼叫的網路發生擁堵,通常 RPC 呼叫是有超時時間的,RPC 超時以後,TM 就會通知 RM 回滾該分散式事務,可能回滾完成後,RPC 請求才到達參與者真正執行,而一個 Try 方法預留的業務資源,只有該分散式事務才能使用,該分散式事務第一階段預留的業務資源就再也沒有人能夠處理了,對於這種情況,我們就稱為懸掛,即業務資源預留後沒法繼續處理。

解決思路是如果二階段執行完成,那一階段就不能再繼續執行。在執行一階段事務時判斷在該全域性事務下,"分支事務記錄"表中是否已經有二階段事務記錄,如果有則不執行 Try。

舉例,場景為 A 轉賬 30 元給 B,A 和 B 賬戶在不同的服務。

方案

賬戶 A

try:
檢查餘額是否夠30元
扣減30元

confirm:


cancel:
增加30元

賬戶 B

try:
增加30元

confirm:


cancel:
減少30元

方案說明

(1)賬戶 A,這裡的餘額就是所謂的業務資源,按照前面提到的原則,在第一階段需要檢查並預留業務資源,因此,我們在扣錢 TCC 資源的 Try 接口裡先檢查 A 賬戶餘額是否足夠,如果足夠則扣除 30 元。 Confirm 介面表示正式提交,由於業務資源已經在 Try 接口裡扣除掉了,那麼在第二階段的 Confirm 接口裡可以什麼都不用做。Cancel 介面的執行表示整個事務回滾,賬戶A回滾則需要把 Try 接口裡扣除掉的 30 元還給賬戶。

(2)賬號B,在第一階段 Try 接口裡實現給賬戶 B 加錢,Cancel 介面的執行表示整個事務回滾,賬戶 B 回滾則需要把 Try 接口裡加的 30 元再減去。

方案問題分析

  1. 如果賬戶 A 的 Try 沒有執行在 Cancel 則就多加了 30 元。

  2. 由於 Try、Cancel、Confirm 都是由單獨的執行緒去呼叫,且會出現重複呼叫,所以都需要實現冪等。

  3. 賬號 B 在 Try 中增加 30 元,當 Try 執行完成後可能會其它執行緒給消費了。

  4. 如果賬戶 B 的 Try 沒有執行在 Cancel 則就多減了 30 元。

問題解決

  1. 賬戶 A 的 Cancel 方法需要判斷 Try 方法是否執行,正常執行 Try 後方可執行 Cancel。

  2. Try、Cancel、Confirm方法實現冪等。

  3. 賬號 B 在 Try 方法中不允許更新賬戶金額,在 Confirm 中更新賬戶金額。

  4. 賬戶 B 的 Cancel 方法需要判斷 Try 方法是否執行,正常執行 Try 後方可執行 Cancel。

優化方案

賬戶 A

try:
try冪等校驗
try懸掛處理
檢查餘額是否夠30元
扣減30元

confirm:


cancel:
cancel冪等校驗
cancel空回滾處理
增加可用餘額30元

賬戶 B

try:

confirm:
confirm冪等校驗
正式增加30元
cancel:

4.3 小結

如果拿 TCC 事務的處理流程與 2PC 兩階段提交做比較,2PC 通常都是在跨庫的 DB 層面,而 TCC 則在應用層面的處理,需要通過業務邏輯來實現。這種分散式事務的實現方式的優勢在於,可以讓應用自己定義資料操作的粒度,使得降低鎖衝突、提高吞吐量成為可能

而不足之處則在於對應用的侵入性非常強,業務邏輯的每個分支都需要實現 Try、Confirm、Cancel 三個操作。此外,其實現難度也比較大,需要按照網路狀態、系統故障等不同的失敗原因實現不同的回滾策略。

5. 分散式事務解決方案之可靠訊息最終一致性

5.1 什麼是可靠訊息最終一致性事務

可靠訊息最終一致性方案是指當事務發起方執行完成本地事務後併發出一條訊息,事務參與方(訊息消費者)一定能夠接收訊息並處理事務成功,此方案強調的是隻要訊息發給事務參與方最終事務要達到一致。

此方案是利用訊息中介軟體完成,如下圖:

事務發起方(訊息生產方)將訊息發給訊息中介軟體,事務參與方從訊息中介軟體接收訊息,事務發起方和訊息中介軟體之間,事務參與方(訊息消費方)和訊息中介軟體之間都是通過網路通訊,由於網路通訊的不確定性會導致分散式事務問題。

因此可靠訊息最終一致性方案要解決以下幾個問題:

  1. 本地事務與訊息傳送的原子性問題

    本地事務與訊息傳送的原子性問題即:事務發起方在本地事務執行成功後訊息必須發出去,否則就丟棄訊息。即實現本地事務和訊息傳送的原子性,要麼都成功,要麼都失敗。本地事務與訊息傳送的原子性問題是實現可靠訊息最終一致性方案的關鍵問題。

    下面這種操作,先發送訊息,在操作資料庫:

    begin transaction;
    //1.傳送MQ
    //2.資料庫操作
    commit transation;

    這種情況下無法保證資料庫操作與傳送訊息的一致性,因為可能傳送訊息成功,資料庫操作失敗。 那麼第二種方案,先進行資料庫操作,再發送訊息:

    begin transaction;
    //1.資料庫操作
    //2.傳送MQ
    commit transation;

    這種情況下貌似沒有問題,如果傳送 MQ 訊息失敗,就會丟擲異常,導致資料庫事務回滾。但如果是超時異常,資料庫回滾,但 MQ 其實已經正常傳送了,同樣會導致不一致。

  2. 事務參與方接收訊息的可靠性

    事務參與方必須能夠從訊息佇列接收到訊息,如果接收訊息失敗可以重複接收訊息。

  3. 訊息重複消費的問題

    由於網路2的存在,若某一個消費節點超時但是消費成功,此時訊息中介軟體會重複投遞此訊息,就導致了訊息的重複消費。

    要解決訊息重複消費的問題就要實現事務參與方的方法冪等性。

5.2 解決方案

上節討論了可靠訊息最終一致性事務方案需要解決的問題,本節討論具體的解決方案。

5.2.1 本地訊息表方案

本地訊息表這個方案最初是 eBay 提出的,此方案的核心是通過本地事務保證資料業務操作和訊息的一致性,然後通過定時任務將訊息傳送至訊息中介軟體,待確認訊息傳送給消費方成功再將訊息刪除。

下面以註冊送積分為例來說明:下例共有兩個微服務互動,使用者服務和積分服務,使用者服務負責新增使用者,積分服務負責增加積分。

互動流程如下:

  1. 使用者註冊

    使用者服務在本地事務新增使用者和增加 "積分訊息日誌"。(使用者表和訊息表通過本地事務保證一致)

    begin transaction
    //1.新增使用者
    //2.儲存積分訊息日誌
    commit transation

    這種情況下,本地資料庫操作與儲存積分訊息日誌處於同一個事務中,本地資料庫操作與記錄訊息日誌操作具備原子性。

  2. 定時任務掃描日誌

    如何保證將訊息傳送給訊息佇列呢?

    經過第一步訊息已經寫到訊息日誌表中,可以啟動獨立的執行緒,定時對訊息日誌表中的訊息進行掃描併發送至訊息中介軟體,在訊息中介軟體反饋傳送成功後刪除該訊息日誌,否則等待定時任務下一週期重試。

  3. 消費訊息

    如何保證消費者一定能消費到訊息呢?

    這裡可以使用 MQ 的 ack(即訊息確認)機制,消費者監聽 MQ,如果消費者接收到訊息並且業務處理完成後向 MQ 傳送 ack(即訊息確認),此時說明消費者正常消費訊息完成,MQ 將不再向消費者推送訊息,否則消費者會不斷重試向消費者來發送訊息。

    積分服務接收到"增加積分"訊息,開始增加積分,積分增加成功後向訊息中介軟體迴應 ack,否則訊息中介軟體將重複投遞此訊息。

    由於訊息會重複投遞,積分服務的"增加積分"功能需要實現冪等性。

5.3 小結

可靠訊息最終一致性就是保證訊息從生產方經過訊息中介軟體傳遞到消費方的一致性:

  1. 本地事務與訊息傳送的原子性問題。

  2. 事務參與方接收訊息的可靠性。

可靠訊息最終一致性事務適合執行週期長且實時性要求不高的場景。引入訊息機制後,同步的事務操作變為基於訊息執行的非同步操作, 避免了分散式事務中的同步阻塞操作的影響,並實現了兩個服務的解耦。

6 分散式事務解決方案之最大努力通知

6.1 什麼是最大努力通知

最大努力通知也是一種解決分散式事務的方案,下邊是一個是充值的例子:

互動流程:

  1. 賬戶系統呼叫充值系統介面

  2. 充值系統完成支付處理向賬戶發起充值結果通知,若通知失敗,則充值系統按策略進行重複通知

  3. 賬戶系統接收到充值結果通知修改充值狀態

  4. 賬戶系統未接收到通知會主動呼叫充值系統的介面查詢充值結果

通過上邊的例子我們總結最大努力通知方案的目標:發起通知方通過一定的機制最大努力將業務處理結果通知到接收方

具體包括:

  1. 有一定的訊息重複通知機制。因為接收通知方可能沒有接收到通知,此時要有一定的機制對訊息重複通知

  2. 訊息校對機制。如果盡最大努力也沒有通知到接收方,或者接收方消費訊息後要再次消費,此時可由接收方主動向通知方查詢訊息資訊來滿足需求。

最大努力通知與可靠訊息一致性有什麼不同?

  1. 解決方案思想不同

    可靠訊息一致性,發起通知方需要保證將訊息發出去,並且將訊息發到接收通知方,訊息的可靠性關鍵由發起通知方來保證。最大努力通知,發起通知方盡最大的努力將業務處理結果通知為接收通知方,但是可能訊息接收不到,此時需要接 收通知方主動呼叫發起通知方的介面查詢業務處理結果,通知的可靠性關鍵在接收通知方。

  2. 兩者的業務應用場景不同

    可靠訊息一致性關注的是交易過程的事務一致,以非同步的方式完成交易。最大努力通知關注的是交易後的通知事務,即將交易結果可靠的通知出去。

  3. 技術解決方向不同

    可靠訊息一致性要解決訊息從發出到接收的一致性,即訊息發出並且被接收到。最大努力通知無法保證訊息從發出到接收的一致性,只提供訊息接收的可靠性機制。可靠機制是,最大努力的將訊息通知給接收方,當訊息無法被接收方接收時,由接收方主動查詢訊息(業務處理結果)

6.2 解決方案

通過對最大努力通知的理解,採用 MQ 的 ack 機制就可以實現最大努力通知。

方案1:

本方案是利用 MQ 的 ack 機制由 MQ 向接收通知方傳送通知,流程如下:

  1. 發起通知方將通知發給 MQ。使用普通訊息機制將通知發給MQ。

    注意:如果訊息沒有發出去可由接收通知方主動請求發起通知方查詢業務執行結果。(後邊會講)

  2. 接收通知方監聽 MQ。

  3. 接收通知方接收訊息,業務處理完成迴應 ack。

  4. 接收通知方若沒有迴應 ack 則 MQ 會重複通知。

    MQ會按照間隔 1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知間隔,直到達到通知要求的時間視窗上限。

  5. 接收通知方可通過訊息校對介面來校對訊息的一致性。

方案 2:

互動流程如下:

  1. 發起通知方將訊息發給 MQ。

    使用可靠訊息一致方案中的事務訊息保證本地事務和訊息的原子性,最終將通知先發給 MQ。

  2. 通知程式監聽 MQ,接收 MQ 的訊息。

    方案 1 中接收通知方直接監聽 MQ,方案 2 中由通知程式監聽 MQ。

    通知程式若沒有迴應 ack 則 MQ 會重複通知。

  3. 通知程式通過網際網路介面協議(如 http、webservice)呼叫接收通知方案介面,完成通知。

    通知程式呼叫接收通知方案介面成功就表示通知成功,即消費 MQ 訊息成功,MQ 將不再向通知程式投遞通知訊息。

  4. 接收通知方可通過訊息校對介面來校對訊息的一致性。

方案1和方案2的不同點

  1. 方案 1 中接收通知方與 MQ 介面,即接收通知方案監聽 MQ,此方案主要應用與內部應用之間的通知。

  2. 方案 2 中由通知程式與 MQ 介面,通知程式監聽 MQ,收到 MQ 的訊息後由通知程式通過網際網路介面協議呼叫接收通知方。此方案主要應用於外部應用之間的通知,例如支付寶、微信的支付結果通知。

6.3 小結

最大努力通知方案是分散式事務中對一致性要求最低的一種,適用於一些最終一致性時間敏感度低的業務;最大努力通知方案需要實現如下功能:

  1. 訊息重複通知機制

  2. 訊息校對機制

7 總結

分散式事務對比分析

2PC最大的詬病是一個阻塞協議。RM 在執行分支事務後需要等待 TM 的決定,此時服務會阻塞並鎖定資源。由於其阻塞機制和最差時間複雜度高,因此,這種設計不能適應隨著事務涉及的服務數量增加而擴充套件的需要,很難用於併發較高以及子事務生命週期較長(long-running transactions) 的分散式服務中。

如果拿TCC事務的處理流程與2PC兩階段提交做比較,2PC 通常都是在跨庫的 DB 層面,而 TCC 則在應用層面的處理,需要通過業務邏輯來實現。這種分散式事務的實現方式的優勢在於,可以讓應用自己定義資料操作的粒度,使得降低鎖衝突、提高吞吐量成為可能。而不足之處則在於對應用的侵入性非常強,業務邏輯的每個分支都需要實現 Try、Confirm、Cancel 三個操作。此外,其實現難度也比較大,需要按照網路狀態、系統故障等不同的失敗原因實 現不同的回滾策略。典型的使用場景:滿減,登入送優惠券等。

可靠訊息最終一致性事務適合執行週期長且實時性要求不高的場景。引入訊息機制後,同步的事務操作變為基於訊息執行的非同步操作, 避免了分散式事務中的同步阻塞操作的影響,並實現了兩個服務的解耦。典型的使用場景:註冊送積分,登入送優惠券等。

最大努力通知是分散式事務中要求最低的一種,適用於一些最終一致性時間敏感度低的業務;允許發起通知方處理業務失敗,在接收通知方收到通知後積極進行失敗處理,無論發起通知方如何處理結果都會不影響到接收通知方的後續處理;發起通知方需提供查詢執行情況介面,用於接收通知方校對結果。典型的使用場景:銀行通知、支付結果通知等。

2PCTCC可靠訊息最大努力通知
一致性 強一致性 最終一致性 最終一致性 最終一致性
吞吐量
實現複雜度

總結

在條件允許的情況下,我們儘可能選擇本地事務單資料來源,因為它減少了網路互動帶來的效能損耗,且避免了資料弱一致性帶來的種種問題。若某系統頻繁且不合理的使用分散式事務,應首先從整體設計角度觀察服務的拆分是否 合理,是否高內聚低耦合?是否粒度太小?分散式事務一直是業界難題,因為網路的不確定性,而且我們習慣於拿分散式事務與單機事務 ACID 做對比。

無論是資料庫層的 XA、還是應用層 TCC、可靠訊息、最大努力通知等方案,都沒有完美解決分散式事務問題,它們不過是各自在效能、一致性、可用性等方面做取捨,尋求某些場景偏好下的權衡。