1. 程式人生 > 程式設計 >? 看了我的這編分散式事務還不會的帶你白嫖

? 看了我的這編分散式事務還不會的帶你白嫖

前言

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格

分散式事務

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程式產生分散式事務。

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

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

2.1 分散式事務的基本理論

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

2.1.1 CAP理論

CAP是 ConsistencyAvailabilityPartition tolerance三個詞語的縮寫,分別表示一致性、可用性、分割槽容忍 性。 下邊我們分別來解釋:

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

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

整體執行流程如下:

1、商品服務請求主資料庫寫入商品資訊(新增商品、修改商品、刪除商品)
2、主資料庫向商品服務響應寫入成功。
3、商品服務請求從資料庫讀取商品資訊。
複製程式碼

C - Consistency:

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

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

1、商品服務寫入主資料庫成功,則向從資料庫查詢新資料也成功。 
2、商品服務寫入主資料庫失敗,則向從資料庫查詢新資料也失敗。 
複製程式碼

如何實現一致性?

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

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

1、由於存在資料同步的過程,寫操作的響應會有一定的延遲。 
2、為了保證資料一致性會對資源暫時鎖定,待資料同步完成釋放鎖定資源。 3、如果請求資料同步失敗的結點則會返回錯誤資訊,一定不會返回舊資料。
複製程式碼

A - Availability :

可用性是指任何事務操作都可以得到響應結果,且不會出現響應超時或響應錯誤。 上圖中,商品資訊讀取滿足可用性就是要實現如下目標

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

如何實現可用性?

1、寫入主資料庫後要將資料同步到從資料庫。
2、由於要保證從資料庫的可用性,不可將從資料庫中的資源進行鎖定。
3、即時資料還沒有同步過來,從資料庫也要返回要查詢的資料,哪怕是舊資料,如果連舊資料也沒有則可以按照 約定返回一個預設資訊,但不能返回錯誤或響應超時。
複製程式碼

分散式系統可用性的特點:

1、 所有請求都有響應,且不會出現響應超時或響應錯誤。
複製程式碼

P - Partition tolerance :

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

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

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

如何實現分割槽容忍性?

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

分散式分割槽容忍性的特點:

1、分割槽容忍性分是布式系統具備的基本能力。
複製程式碼

2.1.2 CAP組合方式

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

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

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

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

1)主資料庫通過網路向從資料同步資料,可以認為主從資料庫部署在不同的分割槽,通過網路進行互動。 
2)當主資料庫和從資料庫之間的網路出現問題不影響主資料庫和從資料庫對外提供服務。 
3)其一個結點掛掉不影響另一個結點對外提供服務。
複製程式碼

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

如果要實現A則必須保證資料可用性,不管任何時候都可以向從資料查詢資料,則不會響應超時或返回錯誤資訊。

通過分析發現在滿足P的前提下C和A存在矛盾性。

2、CAP有哪些組合方式呢?

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

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

例如: 上邊的商品管理,完全可以實現AP,前提是隻要使用者可以接受所查詢的到資料在一定時間內不是最新的即可。 通常實現AP都會保證最終一致性,後面講的BASE理論就是根據AP來擴充套件的,一些業務場景 比如:訂單退款,今 日退款成功,明日賬戶到賬,只要使用者可以接受在一定時間內到賬即可。

2)CP:

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

3)CA:

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

上邊的商品管理,如果要實現CA則架構如下: ![](user-gold-cdn.xitu.io/2019/11/26/…

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

2.1.3 總結

通過上面我們已經學習了CAP理論的相關知識,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(兩階段提交)

3.1 什麼是2PC

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

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

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

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

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

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

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方案。
  • 以上三個角色之間的互動方式如下:
    • TM向AP提供 應用程式程式設計介面,AP通過TM提交及回滾事務。
    • 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個元件來協議分散式事務的處理過程:

  1. Transaction Coordinator (TC): 事務協調器,它是獨立的中介軟體,需要獨立部署執行,它維護全域性事務的運 行狀態,接收TM指令發起全域性事務的提交與回滾,負責與RM通訊協調各各分支事務的提交或回滾。
  2. Transaction Manager (TM): 事務管理器,TM需要嵌入應用程式中工作,它負責開啟一個全域性事務,並最終 向TC發起全域性提交或全域性回滾的指令。
  3. 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。

Seata實現2PC要點:

1、全域性事務開始使用 @GlobalTransactional標識 。 
2、每個本地事務方案仍然使用@Transactional標識。 
3、每個資料都需要建立undo_log表,此表是seata保證本地事務一致性的關鍵
複製程式碼

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階段真的出錯了,需引入重試機制或人工處理。
  4. TM事務管理器 TM事務管理器可以實現為獨立的服務,也可以讓全域性事務發起方充當TM的角色,TM獨立出來是為了成為公 用元件,是為了考慮系統結構和軟體複用

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

4.2 TCC 解決方案

目前市面上的TCC框架眾多比如下面這幾種: (以下資料採集日為2019年11月23日)

框架名稱 Gitbub地址 star數量
tcc-transaction github.com/changmingxi… 3850
Hmily github.com/yu199195/hm… 2407
ByteTCC github.com/liuyangming… 1947
EasyTransaction github.com/QNJR-GROUP/… 1690

上面講的Seata也支援TCC,但Seata的TCC模式對Spring Cloud並沒有提供支援。我們的目標是理解TCC的原 理以及事務協調運作的過程,因此更請傾向於輕量級易於理解的框架,因此最終確定了Hmily。

Hmily是一個高效能分散式事務TCC開源框架。基於Java語言來開發(JDK1.8),支援Dubbo,Spring Cloud等 RPC框架進行分散式事務。它目前支援以下特性:

- 支援巢狀事務(Nested transaction support).
- 採用disruptor框架進行事務日誌的非同步讀寫,與RPC框架的效能毫無差別
- 支援SpringBoot-starter 專案啟動,使用簡單
- RPC框架支援 : dubbo,motan,springcloud。
- 本地事務儲存支援 : redis,mongodb,zookeeper,file,mysql。
- 事務日誌序列化支援 :java,hessian,kryo,protostuff
- 採用Aspect AOP 切面思想與Spring無縫整合,天然支援叢集。
- RPC事務恢復,超時異常恢復等
複製程式碼

Hmily利用AOP對參與分散式事務的本地方法與遠端方法進行攔截處理,通過多方攔截,事務參與者能透明的 呼叫到另一方的Try、Confirm、Cancel方法;傳遞事務上下文;並記錄事務日誌,酌情進行補償,重試等。

Hmily不需要事務協調服務,但需要提供一個資料庫(mysql/mongodb/zookeeper/redis/file)來進行日誌存 儲。

Hmily實現的TCC服務與普通的服務一樣,只需要暴露一個介面,也就是它的Try業務。Confirm/Cancel業務 邏輯,只是因為全域性事務提交/回滾的需要才提供的,因此Confirm/Cancel業務只需要被Hmily TCC事務框架 發現即可,不需要被呼叫它的其他業務服務所感知。

官網介紹:dromara.org/website/zh-…

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賬戶在不同的服務

方案1:

賬戶A

    ```
    try:
        檢查餘額是否夠30元 
        扣減30元 
    confirm: 
        空 
    cancel:
        增加30元
    ```
複製程式碼

賬戶B

 ```
 try:
    增加30元 
 confirm: 
    空 
 cancel:
    減少30元
 ```
複製程式碼

方案1說明:

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

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

方案1的問題分析:

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.2.2 RocketMQ事務訊息方案

RocketMQ 是一個來自阿里巴巴的分散式訊息中介軟體,於 2012 年開源,並在 2017 年正式成為 Apache 頂級項 目。據瞭解,包括阿里雲上的訊息產品以及收購的子公司在內,阿里集團的訊息產品全線都執行在 RocketMQ 之 上,並且最近幾年的雙十一大促中,RocketMQ 都有搶眼表現。Apache RocketMQ 4.3之後的版本正式支援事務消 息,為分散式事務實現提供了便利性支援。

RocketMQ 事務訊息設計則主要是為瞭解決 Producer 端的訊息傳送與本地事務執行的原子性問題,RocketMQ 的 設計中 broker 與 producer 端的雙向通訊能力,使得 broker 天生可以作為一個事務協調者存在;而 RocketMQ 本身提供的儲存機制為事務訊息提供了持久化能力;RocketMQ 的高可用機制以及可靠訊息設計則為事務訊息在系 統發生異常時依然能夠保證達成事務的最終一致性。

在RocketMQ 4.3後實現了完整的事務訊息,實際上其實是對本地訊息表的一個封裝,將本地訊息表移動到了MQ 內部,解決 Producer 端的訊息傳送與本地事務執行的原子性問題。

執行流程如下: 為方便理解我們還以註冊送積分的例子來描述 整個流程。

Producer 即MQ傳送方,本例中是使用者服務,負責新增使用者。MQ訂閱方即訊息消費方,本例中是積分服務,負責 新增積分。

1、Producer 傳送事務訊息

Producer (MQ傳送方)傳送事務訊息至MQ Server,MQ Server將訊息狀態標記為Prepared(預備狀態),注 意此時這條訊息消費者(MQ訂閱方)是無法消費到的。 本例中,Producer 傳送 ”增加積分訊息“ 到MQ Server。

2、MQ Server迴應訊息傳送成功 MQ Server接收到Producer 傳送給的訊息則迴應傳送成功表示MQ已接收到訊息。

3、Producer 執行本地事務 Producer 端執行業務程式碼邏輯,通過本地資料庫事務控制 本例中,Producer 執行新增使用者操作。

4、訊息投遞 若Producer 本地事務執行成功則自動向MQServer傳送commit訊息,MQ Server接收到commit訊息後將”增加積 分訊息“ 狀態標記為可消費,此時MQ訂閱方(積分服務)即正常消費訊息; 若Producer 本地事務執行失敗則自動向MQServer傳送rollback訊息,MQ Server接收到rollback訊息後 將刪 除”增加積分訊息“ 。

MQ訂閱方(積分服務)消費訊息,消費成功則向MQ迴應ack,否則將重複接收訊息。這裡ack預設自動迴應,即 程式執行正常則自動迴應ack。

5、事務回查 如果執行Producer端本地事務過程中,執行端掛掉,或者超時,MQ Server將會不停的詢問同組的其他 Producer 來獲取事務執行狀態,這個過程叫事務回查。MQ Server會根據事務回查結果來決定是否投遞訊息。

以上主幹流程已由RocketMQ實現,對使用者側來說,使用者需要分別實現本地事務執行以及本地事務回查方法,因此 只需關注本地事務的執行狀態即可。

RoacketMQ提供RocketMQLocalTransactionListener介面:

public interface RocketMQLocalTransactionListener {
 /** 
 ‐ 傳送prepare訊息成功此方法被回撥,該方法用於執行本地事務 
 ‐ @param msg 回傳的訊息,利用transactionId即可獲取到該訊息的唯一Id 
 ‐ @param arg 呼叫send方法時傳遞的引數,當send時候若有額外的引數可以傳遞到send方法中,這裡能獲取到 
 ‐ @return 返回事務狀態,COMMIT:提交 ROLLBACK:回滾 UNKNOW:回撥 
 */
 RocketMQLocalTransactionState executeLocalTransaction(Message msg,Object arg); 
 /** 
 ‐ @param msg 通過獲取transactionId來判斷這條訊息的本地事務執行狀態 
 ‐ @return 返回事務狀態,COMMIT:提交 ROLLBACK:回滾 UNKNOW:回撥 
 */
 RocketMQLocalTransactionState checkLocalTransaction(Message msg); }

複製程式碼

傳送事務訊息:

以下是RocketMQ提供用於傳送事務訊息的API:

    TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup"); 
    producer.setNamesrvAddr("127.0.0.1:9876"); 
    producer.start();
   //設定TransactionListener實現 
   producer.setTransactionListener(transactionListener); 
   //傳送事務訊息 
    SendResult sendResult = producer.sendMessageInTransaction(msg,null);


複製程式碼

5.3 小結

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

RocketMQ主要解決了兩個功能:

1、本地事務與訊息傳送的原子性問題。 
2、事務參與方接收訊息的可靠性。 可靠訊息最終一致性事務適合執行週期長且實時性要求不高的場景。
引入訊息機制後,同步的事務操作變為基於消 息執行的非同步操作,避免了分散式事務中的同步阻塞操作的影響,並實現了兩個服務的解耦。
複製程式碼

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

6.1 什麼是最大努力通知

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

互動流程:

1、賬戶系統呼叫充值系統介面
2、充值系統完成支付處理向賬戶系統發起充值結果通知 若通知失敗,則充值系統按策略進行重複通知
3、賬戶系統接收到充值結果通知修改充值狀態。
4、賬戶系統未接收到通知會主動呼叫充值系統的介面查詢充值結果
複製程式碼

通過上邊的例子我們總結最大努力通知方案的目標:

目標:發起通知方通過一定的機制最大努力將業務處理結果通知到接收方。

具體包括:

1、有一定的訊息重複通知機制。 因為接收通知方可能沒有接收到通知,此時要有一定的機制對訊息重複通知。
2、訊息校對機制。 如果盡最大努力也沒有通知到接收方,或者接收方消費訊息後要再次消費,此時可由接收方主動向通知方查詢訊息 資訊來滿足需求。
複製程式碼

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

1、解決方案思想不同

可靠訊息一致性,發起通知方需要保證將訊息發出去,並且將訊息發到接收通知方,訊息的可靠性關鍵由發起通知 方來保證。

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

兩者的業務應用場景不同

可靠訊息一致性關注的是交易過程的事務一致,以非同步的方式完成交易。

最大努力通知關注的是交易後的通知事務,即將交易結果可靠的通知出去。

3、技術解決方向不同 可靠訊息一致性要解決訊息從發出到接收的一致性,即訊息發出並且被接收到。

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

6.2 解決方案

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

方案1:

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

1、發起通知方將通知發給MQ。使用普通訊息機制將通知發給MQ。 注意:如果訊息沒有發出去可由接收通知方主動請求發起通知方查詢業務執行結果。(後邊會講)
2、接收通知方監聽 MQ
3、接收通知方接收訊息,業務處理完成迴應ack
4、接收通知方若沒有迴應ack則MQ會重複通知。
5、接收通知方可通過訊息校對介面來校對訊息的一致性。
複製程式碼

方案2:

本方案也是利用MQ的ack機制,與方案1不同的是應用程式向接收通知方傳送通知,如下圖:

互動流程如下:

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的訊息後由通知程式通過網際網路介面協議呼叫接收 通知方。此方案主要應用於外部應用之間的通知,例如支付寶、微信的支付結果通知。

7 分散式事務對比分析:

在瞭解各種分散式事務的解決方案後,我們瞭解到各種方案的優缺點:

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

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

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

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

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

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

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

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

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !