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 分布式事務產生的情景
-
跨JVM進程產生分布式事務
典型的場景就是微服務架構:微服務之間通過遠程調用完成事務操作。比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減少庫存。
-
跨數據庫實例產生分布式事務
單體系統訪問多個數據庫實例當單體系統需要訪問多個數據庫(實例)時就會產生分布式事務。比如:用戶信息和訂單信息分別在兩個MySQL實例存儲,用戶管理系統刪除用戶信息,需要分別刪除用戶信息及用戶的訂單信息,由於數據分布在不同的數據實例,需要通過不同的數據庫鏈接去操作數據,此時產生分布式事務。
-
多服務訪問同一個數據庫實例
訂單微服務和庫存微服務即使訪問同一個數據庫也會產生分布式事務,原因就是跨JVM進程,兩個微服務持有了不同的數據庫鏈接進行數據庫操作,此時產生分布式事務。
2. 分布式事務基礎理論
通過前面的學習,我們了解到了分布式事務的基礎概念。與本地事務不同的是,分布式系統之所以叫分布式,是因為提供服務的各個節點分布在不同機器上,相互之間通過網絡交互。不能因為有一點網絡問題就導致整個系統無法提供服務,網絡因素成為了分布式事務的考量標准之一。因此,分布式事務需要更進一步的理論支持,接下來,我們先來學習一下分布式事務的CAP理論。
2.1 CAP理論
2.1.1 理解CAP
CAP 是 Consistency、Availability、Partition tolerance 三個單詞的縮寫,分別表示一致性、可用性、分區容忍性。
下面為了方便對CAP理論的理解,我們結合電商系統中的一些業務場景來理解CAP。
如下圖,是商品信息管理的執行流程:
整體執行流程如下
-
商品服務請求主數據庫寫入商品信息(添加商品、修改商品、刪除商品)
-
主數據庫向商品服務響應寫入成功
-
商品服務請求從數據庫讀取商品信息
C - Consistency
一致性是指寫操作后的讀操作可以讀取到最新的數據狀態,當數據分布在多個節點上,從任意結點讀取到的數據都是最新的狀態。
上圖中,商品信息的讀寫要滿足一致性就是要實現如下目標:
-
商品服務寫入主數據庫成功,則向從數據庫查詢新數據也成功。
-
商品服務寫入主數據庫失敗,則向從數據庫查詢新數據也失敗。
如何實現一致性?
-
寫入主數據庫后要將數據同步到從數據庫。
-
寫入主數據庫后,在向從數據庫同步期間要將從數據庫鎖定,待同步完成后再釋放鎖,以免在新數據寫入成功后,向從數據庫查詢到舊的數據。
分布式系統一致性的特點:
-
由於存在數據同步的過程,寫操作的響應會有一定的延遲。
-
為了保證數據一致性會對資源暫時鎖定,待數據同步完成釋放鎖定資源。
-
如果請求數據同步失敗的結點則會返回錯誤信息,一定不會返回舊數據。
A - Availability
可用性是指任何事務操作都可以得到響應結果,且不會出現響應超時或響應錯誤。
上圖中,商品信息讀取滿足可用性就是要實現如下目標:
1. 從數據庫接收到數據查詢的請求則立即能夠響應數據查詢結果。
-
從數據庫不允許出現響應超時或響應錯誤。
如何實現可用性
1. 寫入主數據庫后要將數據同步到從數據庫。
-
由於要保證從數據庫的可用性,不可將從數據庫中的資源進行鎖定。
-
即時數據還沒有同步過來,從數據庫也要返回要查詢的數據,哪怕是舊數據,如果連舊數據也沒有則可以按照約定返回一個默認信息,但不能返回錯誤或響應超時。
分布式系統可用性的特點:所有請求都有響應,且不會出現響應超時或響應錯誤
P - Partition tolerance
通常分布式系統的各各結點部署在不同的子網,這就是網絡分區,不可避免的會出現由於網絡問題而導致結點之間通信失敗,此時仍可對外提供服務,這叫分區容忍性。
上圖中,商品信息讀寫滿足分區容忍性就是要實現如下目標:
1. 主數據庫向從數據庫同步數據失敗不影響讀寫操作。
-
其一個結點掛掉不影響另一個結點對外提供服務。
如何實現分區容忍性?
1. 盡量使用異步取代同步操作,例如使用異步方式將數據從主數據庫同步到從數據,這樣結點之間能有效的實現松耦合。
-
添加從數據庫結點,其中一個從結點掛掉其它從結點提供服務。
分布式分區容忍性的特點:分區容忍性分是布式系統具備的基本能力
2.1.2 CAP組合方式
上邊商品管理的例子是否同時具備 CAP 呢?
在所有分布式事務場景中不會同時具備 CAP 三個特性,因為在具備了P的前提下C和A是不能共存的
比如,下圖滿足了P即表示實現分區容忍:
本圖分區容忍的含義是:
-
主數據庫通過網絡向從數據庫同步數據,可以認為主從數據庫部署在不同的分區,通過網絡進行交互。
-
當主數據庫和從數據庫之間的網絡出現問題不影響主數據庫和從數據庫對外提供服務。
-
其中一個節點掛掉不影響另一個節點對外提供服務。
如果要實現 C 則必須保證數據一致性,在數據同步的時候為防止向從數據庫查詢不一致的數據則需要將從數據庫數據鎖定,待同步完成后解鎖,如果同步失敗從數據庫要返回錯誤信息或超時信息。
如果要實現 A 則必須保證數據可用性,不管任何時候都可以向從數據查詢數據,則不會響應超時或返回錯誤信息。通過分析發現在滿足P的前提下 C 和 A 存在矛盾性。
CAP的組合方式
所以在生產中對分布式事務處理時要根據需求來確定滿足 CAP 的哪兩個方面。
-
AP
放棄一致性,追求分區容忍性和可用性。這是很多分布式系統設計時的選擇。
例如:上邊的商品管理,完全可以實現 AP,前提是只要用戶可以接受所查詢到的數據在一定時間內不是最新的即可。
通常實現 AP 都會保證最終一致性,后面將的 BASE 理論就是根據 AP 來擴展的,一些業務場景比如:訂單退款,今日退款成功,明日賬戶到賬,只要用戶可以接受在一定的時間內到賬即可。
-
CP
放棄可用性,追求一致性和分區容錯性,zookeeper 其實就是追求的強一致,又比如跨行轉賬,一次轉賬請求要等待雙方銀行系統都完成整個事務才算完成。
-
CA
放棄分區容忍性,即不進行分區,不考慮由於網絡不通或結點掛掉的問題,則可以實現一致性和可用性。那么系統將不是一個標准的分布式系統,最常用的關系型數據就滿足了 CA。
上邊的商品管理,如果要實現 CA 則架構如下:
主數據庫和從數據庫中間不在進行數據同步,數據庫可以響應每次的查詢請求,通過事務隔離級別實現每個查詢請求都可以返回最新的數據。
2.1.3 總結
CAP 是一個已經被證實的理論,一個分布式系統最多只能同時滿足:一致性(Consistency)、可用性(Availability)和分區容忍性(Partition tolerance)這三項中的兩項。它可以作為我們進行架構設計、技術選型的考量標准。對於多數大型互聯網應用的場景,結點眾多、部署分散,而且現在的集群規模越來越大,所以節點故障、網絡故障是常態,而且要保證服務可用性達到 N 個 9(99.99..%),並要達到良好的響應性能來提高用戶體驗,因此一般都會做出如下選擇:保證 P 和 A ,舍棄 C 強一致,保證最終一致性。
2.2 BASE 理論
-
強一致性和最終一致性
CAP 理論告訴我們一個分布式系統最多只能同時滿足一致性(Consistency)、可用性(Availability)和分區容忍性(Partition tolerance)這三項中的兩項,其中AP在實際應用中較多,AP 即舍棄一致性,保證可用性和分區容忍性,但是在實際生產中很多場景都要實現一致性,比如前邊我們舉的例子主數據庫向從數據庫同步數據,即使不要一致性,但是最終也要將數據同步成功來保證數據一致,這種一致性和 CAP 中的一致性不同,CAP 中的一致性要求 在任何時間查詢每個結點數據都必須一致,它強調的是強一致性,但是最終一致性是允許可以在一段時間內每個結點的數據不一致,但是經過一段時間每個結點的數據必須一致,它強調的是最終數據的一致性。
-
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 支持兩階段提交協議,如下圖:
-
准備階段(Prepare phase):事務管理器給每個參與者發送 Prepare 消息,每個數據庫參與者在本地執行事務,並寫本地的 Undo/Redo 日志,此時事務沒有提交。(Undo 日志是記錄修改前的數據,用於數據庫回滾,Redo 日志是記錄修改后的數據,用於提交事務后寫入數據文件)
-
提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時消息時,直接給每個參與者發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意:必須在最后階段釋放鎖資源。
下圖展示了2PC的兩個階段,分成功和失敗兩個情況說明:
成功情況
失敗情況
3.2 解決方案
3.2.1 XA 方案
2PC的傳統方案是在數據庫層面實現的,如 Oracle、MySQL 都支持 2PC 協議,為了統一標准減少行業內不必要的對接成本,需要制定標准化的處理模型及接口標准,國際開放標准組織 Open Group 定義了分布式事務處理模型DTP(Distributed Transaction Processing Reference Model)。
為了讓大家更明確 XA 方案的內容,下面以新用戶注冊送積分為例來說明:
執行流程如下:
-
應用程序(AP)持有用戶庫和積分庫兩個數據源。
-
應用程序(AP)通過 TM 通知用戶庫 RM 新增用戶,同時通知積分庫RM為該用戶新增積分,RM 此時並未提交事務,此時用戶和積分資源鎖定。
-
TM 收到執行回復,只要有一方失敗則分別向其他 RM 發起回滾事務,回滾完畢,資源鎖釋放。
-
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 提交及回滾事務。
-
TM 交易中間件通過 XA 接口來通知 RM 數據庫事務的開始、結束以及提交、回滾等。
總結
整個 2PC 的事務流程涉及到三個角色 AP、RM、TM。AP 指的是使用 2PC 分布式事務的應用程序;RM 指的是資源管理器,它控制着分支事務;TM 指的是事務管理器,它控制着整個全局事務。
(1)在准備階段 RM 執行實際的業務操作,但不提交事務,資源鎖定
(2)在提交階段 TM 會接受 RM 在准備階段的執行回復,只要有任一個RM執行失敗,TM 會通知所有 RM 執行回滾操作,否則,TM 將會通知所有 RM 提交該事務。提交階段結束資源鎖釋放。
XA方案的問題
-
需要本地數據庫支持XA協議。
-
資源鎖需要等到兩個階段結束才釋放,性能較差。
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的分布式事務過程:
具體的執行流程如下:
-
用戶服務的 TM 向 TC 申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的 XID。
-
用戶服務的 RM 向 TC 注冊分支事務,該分支事務在用戶服務執行新增用戶邏輯,並將其納入 XID 對應全局事務的管轄。
-
用戶服務執行分支事務,向用戶表插入一條記錄。
-
邏輯執行到遠程調用積分服務時(XID 在微服務調用鏈路的上下文中傳播)。積分服務的 RM 向 TC 注冊分支事務,該分支事務執行增加積分的邏輯,並將其納入 XID 對應全局事務的管轄。
-
積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢后,返回用戶服務。
-
用戶服務分支事務執行完畢。
-
TM 向 TC 發起針對 XID 的全局提交或回滾決議。
-
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 分為三個階段:
-
Try 階段是做完業務檢查(一致性)及資源預留(隔離),此階段僅是一個初步操作,它和后續的 Confirm 一起才能真正構成一個完整的業務邏輯。
-
Confirm 階段是做確認提交,Try 階段所有分支事務執行成功后開始執行 Confirm。通常情況下,采用 TCC 則認為 Confirm 階段是不會出錯的。即:只要 Try 成功,Confirm 一定成功。若 Confirm 階段真的出錯了,需引入重試機制或人工處理。
-
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 元再減去。
方案問題分析
-
如果賬戶 A 的 Try 沒有執行在 Cancel 則就多加了 30 元。
-
由於 Try、Cancel、Confirm 都是由單獨的線程去調用,且會出現重復調用,所以都需要實現冪等。
-
賬號 B 在 Try 中增加 30 元,當 Try 執行完成后可能會其它線程給消費了。
-
如果賬戶 B 的 Try 沒有執行在 Cancel 則就多減了 30 元。
問題解決
-
賬戶 A 的 Cancel 方法需要判斷 Try 方法是否執行,正常執行 Try 后方可執行 Cancel。
-
Try、Cancel、Confirm方法實現冪等。
-
賬號 B 在 Try 方法中不允許更新賬戶金額,在 Confirm 中更新賬戶金額。
-
賬戶 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 什么是可靠消息最終一致性事務
可靠消息最終一致性方案是指當事務發起方執行完成本地事務后並發出一條消息,事務參與方(消息消費者)一定能夠接收消息並處理事務成功,此方案強調的是只要消息發給事務參與方最終事務要達到一致。
此方案是利用消息中間件完成,如下圖:
事務發起方(消息生產方)將消息發給消息中間件,事務參與方從消息中間件接收消息,事務發起方和消息中間件之間,事務參與方(消息消費方)和消息中間件之間都是通過網絡通信,由於網絡通信的不確定性會導致分布式事務問題。
因此可靠消息最終一致性方案要解決以下幾個問題:
-
本地事務與消息發送的原子性問題
本地事務與消息發送的原子性問題即:事務發起方在本地事務執行成功后消息必須發出去,否則就丟棄消息。即實現本地事務和消息發送的原子性,要么都成功,要么都失敗。本地事務與消息發送的原子性問題是實現可靠消息最終一致性方案的關鍵問題。
下面這種操作,先發送消息,在操作數據庫:
begin transaction;
//1.發送MQ
//2.數據庫操作
commit transation;這種情況下無法保證數據庫操作與發送消息的一致性,因為可能發送消息成功,數據庫操作失敗。 那么第二種方案,先進行數據庫操作,再發送消息:
begin transaction;
//1.數據庫操作
//2.發送MQ
commit transation;這種情況下貌似沒有問題,如果發送 MQ 消息失敗,就會拋出異常,導致數據庫事務回滾。但如果是超時異常,數據庫回滾,但 MQ 其實已經正常發送了,同樣會導致不一致。
-
事務參與方接收消息的可靠性
事務參與方必須能夠從消息隊列接收到消息,如果接收消息失敗可以重復接收消息。
-
消息重復消費的問題
由於網絡2的存在,若某一個消費節點超時但是消費成功,此時消息中間件會重復投遞此消息,就導致了消息的重復消費。
要解決消息重復消費的問題就要實現事務參與方的方法冪等性。
5.2 解決方案
上節討論了可靠消息最終一致性事務方案需要解決的問題,本節討論具體的解決方案。
5.2.1 本地消息表方案
本地消息表這個方案最初是 eBay 提出的,此方案的核心是通過本地事務保證數據業務操作和消息的一致性,然后通過定時任務將消息發送至消息中間件,待確認消息發送給消費方成功再將消息刪除。
下面以注冊送積分為例來說明:下例共有兩個微服務交互,用戶服務和積分服務,用戶服務負責添加用戶,積分服務負責增加積分。
交互流程如下:
-
用戶注冊
用戶服務在本地事務新增用戶和增加 "積分消息日志"。(用戶表和消息表通過本地事務保證一致)
begin transaction
//1.新增用戶
//2.存儲積分消息日志
commit transation這種情況下,本地數據庫操作與存儲積分消息日志處於同一個事務中,本地數據庫操作與記錄消息日志操作具備原子性。
-
定時任務掃描日志
如何保證將消息發送給消息隊列呢?
經過第一步消息已經寫到消息日志表中,可以啟動獨立的線程,定時對消息日志表中的消息進行掃描並發送至消息中間件,在消息中間件反饋發送成功后刪除該消息日志,否則等待定時任務下一周期重試。
-
消費消息
如何保證消費者一定能消費到消息呢?
這里可以使用 MQ 的 ack(即消息確認)機制,消費者監聽 MQ,如果消費者接收到消息並且業務處理完成后向 MQ 發送 ack(即消息確認),此時說明消費者正常消費消息完成,MQ 將不再向消費者推送消息,否則消費者會不斷重試向消費者來發送消息。
積分服務接收到"增加積分"消息,開始增加積分,積分增加成功后向消息中間件回應 ack,否則消息中間件將重復投遞此消息。
由於消息會重復投遞,積分服務的"增加積分"功能需要實現冪等性。
5.3 小結
可靠消息最終一致性就是保證消息從生產方經過消息中間件傳遞到消費方的一致性:
-
本地事務與消息發送的原子性問題。
-
事務參與方接收消息的可靠性。
可靠消息最終一致性事務適合執行周期長且實時性要求不高的場景。引入消息機制后,同步的事務操作變為基於消息執行的異步操作, 避免了分布式事務中的同步阻塞操作的影響,並實現了兩個服務的解耦。
6 分布式事務解決方案之最大努力通知
6.1 什么是最大努力通知
最大努力通知也是一種解決分布式事務的方案,下邊是一個是充值的例子:
交互流程:
-
賬戶系統調用充值系統接口
-
充值系統完成支付處理向賬戶發起充值結果通知,若通知失敗,則充值系統按策略進行重復通知
-
賬戶系統接收到充值結果通知修改充值狀態
-
賬戶系統未接收到通知會主動調用充值系統的接口查詢充值結果
通過上邊的例子我們總結最大努力通知方案的目標:發起通知方通過一定的機制最大努力將業務處理結果通知到接收方。
具體包括:
-
有一定的消息重復通知機制。因為接收通知方可能沒有接收到通知,此時要有一定的機制對消息重復通知
-
消息校對機制。如果盡最大努力也沒有通知到接收方,或者接收方消費消息后要再次消費,此時可由接收方主動向通知方查詢消息信息來滿足需求。
最大努力通知與可靠消息一致性有什么不同?
-
解決方案思想不同
可靠消息一致性,發起通知方需要保證將消息發出去,並且將消息發到接收通知方,消息的可靠性關鍵由發起通知方來保證。最大努力通知,發起通知方盡最大的努力將業務處理結果通知為接收通知方,但是可能消息接收不到,此時需要接 收通知方主動調用發起通知方的接口查詢業務處理結果,通知的可靠性關鍵在接收通知方。
-
兩者的業務應用場景不同
可靠消息一致性關注的是交易過程的事務一致,以異步的方式完成交易。最大努力通知關注的是交易后的通知事務,即將交易結果可靠的通知出去。
-
技術解決方向不同
可靠消息一致性要解決消息從發出到接收的一致性,即消息發出並且被接收到。最大努力通知無法保證消息從發出到接收的一致性,只提供消息接收的可靠性機制。可靠機制是,最大努力的將消息通知給接收方,當消息無法被接收方接收時,由接收方主動查詢消息(業務處理結果)
6.2 解決方案
通過對最大努力通知的理解,采用 MQ 的 ack 機制就可以實現最大努力通知。
方案1:
本方案是利用 MQ 的 ack 機制由 MQ 向接收通知方發送通知,流程如下:
-
發起通知方將通知發給 MQ。使用普通消息機制將通知發給MQ。
注意:如果消息沒有發出去可由接收通知方主動請求發起通知方查詢業務執行結果。(后邊會講)
-
接收通知方監聽 MQ。
-
接收通知方接收消息,業務處理完成回應 ack。
-
接收通知方若沒有回應 ack 則 MQ 會重復通知。
MQ會按照間隔 1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知間隔,直到達到通知要求的時間窗口上限。
-
接收通知方可通過消息校對接口來校對消息的一致性。
方案 2:
交互流程如下:
-
發起通知方將消息發給 MQ。
使用可靠消息一致方案中的事務消息保證本地事務和消息的原子性,最終將通知先發給 MQ。
-
通知程序監聽 MQ,接收 MQ 的消息。
方案 1 中接收通知方直接監聽 MQ,方案 2 中由通知程序監聽 MQ。
通知程序若沒有回應 ack 則 MQ 會重復通知。
-
通知程序通過互聯網接口協議(如 http、webservice)調用接收通知方案接口,完成通知。
通知程序調用接收通知方案接口成功就表示通知成功,即消費 MQ 消息成功,MQ 將不再向通知程序投遞通知消息。
-
接收通知方可通過消息校對接口來校對消息的一致性。
方案1和方案2的不同點:
-
方案 1 中接收通知方與 MQ 接口,即接收通知方案監聽 MQ,此方案主要應用與內部應用之間的通知。
-
方案 2 中由通知程序與 MQ 接口,通知程序監聽 MQ,收到 MQ 的消息后由通知程序通過互聯網接口協議調用接收通知方。此方案主要應用於外部應用之間的通知,例如支付寶、微信的支付結果通知。
6.3 小結
最大努力通知方案是分布式事務中對一致性要求最低的一種,適用於一些最終一致性時間敏感度低的業務;最大努力通知方案需要實現如下功能:
-
消息重復通知機制
-
消息校對機制
7 總結
分布式事務對比分析
2PC 最大的詬病是一個阻塞協議。RM 在執行分支事務后需要等待 TM 的決定,此時服務會阻塞並鎖定資源。由於其阻塞機制和最差時間復雜度高,因此,這種設計不能適應隨着事務涉及的服務數量增加而擴展的需要,很難用於並發較高以及子事務生命周期較長(long-running transactions) 的分布式服務中。
如果拿TCC事務的處理流程與2PC兩階段提交做比較,2PC 通常都是在跨庫的 DB 層面,而 TCC 則在應用層面的處理,需要通過業務邏輯來實現。這種分布式事務的實現方式的優勢在於,可以讓應用自己定義數據操作的粒度,使得降低鎖沖突、提高吞吐量成為可能。而不足之處則在於對應用的侵入性非常強,業務邏輯的每個分支都需要實現 Try、Confirm、Cancel 三個操作。此外,其實現難度也比較大,需要按照網絡狀態、系統故障等不同的失敗原因實 現不同的回滾策略。典型的使用場景:滿減,登錄送優惠券等。
可靠消息最終一致性事務適合執行周期長且實時性要求不高的場景。引入消息機制后,同步的事務操作變為基於消息執行的異步操作, 避免了分布式事務中的同步阻塞操作的影響,並實現了兩個服務的解耦。典型的使用場景:注冊送積分,登錄送優惠券等。
最大努力通知是分布式事務中要求最低的一種,適用於一些最終一致性時間敏感度低的業務;允許發起通知方處理業務失敗,在接收通知方收到通知后積極進行失敗處理,無論發起通知方如何處理結果都會不影響到接收通知方的后續處理;發起通知方需提供查詢執行情況接口,用於接收通知方校對結果。典型的使用場景:銀行通知、支付結果通知等。
2PC | TCC | 可靠消息 | 最大努力通知 | |
---|---|---|---|---|
一致性 | 強一致性 | 最終一致性 | 最終一致性 | 最終一致性 |
吞吐量 | 低 | 中 | 高 | 高 |
實現復雜度 | 易 | 難 | 中 | 易 |
總結
在條件允許的情況下,我們盡可能選擇本地事務單數據源,因為它減少了網絡交互帶來的性能損耗,且避免了數據弱一致性帶來的種種問題。若某系統頻繁且不合理的使用分布式事務,應首先從整體設計角度觀察服務的拆分是否 合理,是否高內聚低耦合?是否粒度太小?分布式事務一直是業界難題,因為網絡的不確定性,而且我們習慣於拿分布式事務與單機事務 ACID 做對比。
無論是數據庫層的 XA、還是應用層 TCC、可靠消息、最大努力通知等方案,都沒有完美解決分布式事務問題,它們不過是各自在性能、一致性、可用性等方面做取舍,尋求某些場景偏好下的權衡。