一文理解分布式事務的解決方案
單體數據庫不涉及網絡交互,所以在多表之間實現事務是比較簡單的,這種事務稱之為本地事務。
但是單體數據庫的性能達到瓶頸的時候,就需要分庫,就會出現跨庫(數據庫實例)的事務需求;隨著企業應用的規模越來越大,企業會進一步進行服務化改造,以滿足業務增長的需求;當前微服務架構越來越流行,跨服務的事務場景也會越來越多。
這些都屬于分布式事務。分布式事務是指是指事務的發起者、參與者、數據資源服務器以及事務管理器分別位于分布式系統的不同節點之上。
概括起來,分布式事務有三種場景:
-
跨數據庫分布式事務
-
跨服務分布式事務
-
混合式分布式事務
本文將介紹分布式事務常見的解決方案:
-
2PC
-
3PC
-
TCC
-
Saga事務
-
基于本地消息表機制
-
基于事務消息機制
-
最大努力通知機制
常見解決方案
分布式事務是由多個本地事務組成的,分布式事務跨越了多設備,之間又經歷的復雜的網絡,可想而知想要實現嚴格的事務道路阻且長。
2PC
二階段提交(Two-phase Commit,簡稱2PC),是指為了使基于分布式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法(Algorithm)。
整體分為兩個階段,如圖所示。
2PC的優缺點
2PC的優點是能利用參與者(RM)自身的功能進行本地事務的提交和回滾,對業務邏輯零侵入(相對TCC解決方案)。
但2PC也存在三大缺點:同步阻塞、單點故障和數據不一致問題。
-
同步阻塞
由于參與者(RM)在執行操作時都是同步阻塞的,所以在2PC過程中其他節點訪問加鎖資源不得不處于阻塞狀態。
-
單點故障
協調者(TM)是單點,一旦協調者發生故障,參與者會一直阻塞。如果是某個熱點資源阻塞,可能會導致整個系統的雪崩。
-
數據不一致問題
在第二階段中,因為網絡原因導致部分參與者(RM)沒有接收到協調者(TM)的信息,或者部分參與者進行提交/回滾操作時,發生異常。這都會導致數據的不一致性問題。
2PC的優化
Percolator是Google的上一代分布式事務解決方案,構建在BigTable之上,在Google內部用于網頁索引更新的業務,原始的論文見于參考文檔4。
TiDB的事務模型沿用了Percolator的事務模型,之后講解分布式數據庫相關博客進行講解。
3PC
3PC相對2PC增加了三階段模式以及超時機制。
超時機制:第三階段中,當參與者長時間沒有得到協調者的響應,在默認情況下,參與者會自動將超時的事務進行提交(即使是協調者發送的可能是rollback命令,這里就造成了數據的不一致)。解決2PC同步阻塞的情況.
同時3PC增加的第一階段的詢問通知,降低2PC中的數據不一致問題的概率。
但2PC中的單點故障問題,3PC并沒有解決。
3PC的三個階段,如圖所示:
-
第一階段,CanCommit
-
第二階段,PreCommit
-
第三階段,DoCommit
總結
2PC還是3PC都是協議,是一種指導思想,與項目中真正的落地方案還是有差別的。
但2PC和3PC對于大型分布式系統很少會使用,因為在事務處理過程中,協調者需要同時連接多個數據庫(RM)。
通常微服務都是連接各自領域的數據庫,微服務想要修改另一個領域的數據,都是要通過RPC接口來實現的,并不會多數據源訪問。如果存在過多協調者進行多數據源的鏈接,勢必會增加服務治理的難度并可能導致數據的錯亂。
TCC
不管是2PC還是3PC都是依賴于數據庫的事務提交和回滾。
但有時一些業務不僅僅涉及到數據庫,例如發送一條短信、上傳一張圖片等業務層的邏輯。
所以事務的提交和回滾就得提升到業務層面而不是數據庫層面了,而TCC就是一種業務層面的兩階段提交。
TCC (Try、Commit、Cancel) 是一種補償型事務。該模型要求應用的每個服務提供try、confirm、cancel三個接口,核心思想是首先對資源的進行預留評估,如果事務可以提交,則完成對預留資源的確認;如果事務要回滾,則釋放預留的資源。
TCC其本質是一個應用層面上的2PC,同樣分為兩個階段,如下圖所示:
比如:一個扣款服務使用TCC的話,需要寫Try方法,用來扣款資金;還需要一個Confirm方法來執行真正的扣款;最后還需要提供Cancel方法用于進行扣款操作的回滾。
可以看到原本的一個方法,需要膨脹成三個方法,所以說TCC對業務有很大的侵入。
雖說對業務有侵入,但是TCC沒有資源的阻塞,每一個方法都是直接提交事務的,如果出錯是通過業務層面的Cancel來進行補償,所以TCC屬于補償型事務。
對于2PC中出現單點故障問題或超時問題,TCC的解決方案是不停重試:不停地重試沒有收到響應的Confirm/Cancel接口直到成功為止,如果重試策略失敗就通過記錄和報警進行人工介入。
但這種重試機制,造成了TCC的冪等問題與空回滾問題。
TCC需要注意的問題
-
冪等問題
由于有重調機制,因此對于Try、Confirm、Cancel三個方法都需要冪等實現,避免重復執行產生錯誤。
-
空回滾問題
參與者(RM)的Try接口響應由于網絡問題沒有讓協調者(TM)成功接收到,此時協調者(TM)就會發出Cancel命令。那么Cancel接口就需要在未執行Try的情況下能正常的Cancel。
-
懸掛問題
事務協調器在調用TCC服務的一階段Try操作時,可能會出現因網絡擁堵而導致的超時,此時事務協調器會觸發二階段回滾,調用TCC服務的Cancel操作,Cancel調用未超時;在此之后,擁堵在網絡上的一階段Try數據包被TCC服務收到,出現了二階段Cancel請求比一階段Try請求先執行的情況,此TCC服務在執行晚到的Try之后,將永遠不會再收到二階段的Confirm或者Cancel,造成TCC服務懸掛。
所以在實現TCC服務時,要允許空回滾,但也要拒絕執行空回滾之后Try請求,要避免出現懸掛。
補償型TCC
上面講解的是通用型TCC,它需要對分布式事務相關的所有業務有掌控權。但有時候(例如調用的是別的公司的接口),通用型TCC行不通。
因此存在補償型TCC,可以理解為沒有Try的TCC形式。由于未提供Try接口,可以認為是Saga機制的另一種形式。
比如坐飛機需要換乘,換乘的飛機又是不同的航空公司,比如從A飛到B,再從B飛到C,只有A-B和B-C都買到票了才有意義。
這時候就直接接調用航空公司的買票操作,當兩個航空公司都買成功了那就直接成功了,如果某個公司買失敗了,那就需要調用取消訂票接口。
相當于直接執行TCC的第二階段,需要重點關注回滾操作。如果回滾失敗得有記錄報警和人工介入等。
總結
TCC事務將分布式事務從資源層提到業務層來實現,可以讓業務靈活選擇資源的鎖定粒度,并且全局事務執行過程中不會一直持有鎖,所以系統的吞吐量比2PC模式要高很多。
由于TCC事務的帶來的工程復雜度、網絡延遲和服務治理難度的提高,所以除非是與支付交易相關的核心業務場景(對一致性要求很高),其他業務場景不要使用TCC事務。
Saga事務
Saga事務,也是一種補償事務。同補償型TCC一樣,沒有Try階段,而是把分布式事務看作一組本地事務構成的事務鏈。
Saga事務基本協議如下:
-
每個Saga事務由一系列冪等的有序子事務(sub-transaction,擔任參與者的身份) Ti 組成。
-
每個Ti 都有對應的冪等補償動作Ci,補償動作用于撤銷Ti造成的結果。
事務鏈中如果包含有業務順序的邏輯,一定要合理安排事務鏈的順序(以項目利益為優先,如果出現紕漏可以人工補齊的原則,例如先扣款后發貨;先退貨再退款)
由于Saga模型中沒有Prepare階段,因此事務間不能保證隔離性,當多個Saga事務操作同一資源時,就會產生更新丟失、臟數據讀取等問題,這時需要在業務層控制并發,例如:在應用層面加鎖,或者應用層面預先凍結資源。
下面以下單流程為例,整個操作包括:扣減庫存(庫存服務)、創建訂單(訂單服務)、支付(支付服務)等等。
事務正常執行完成T1, T2, T3, ...,Tn,例如:扣減庫存(T1),創建訂單(T2),支付(T3),依次有序進行,但支付服務出現報錯,此時Saga有兩種策略可以使用。
Saga事務的恢復策略
Saga定義了兩種恢復策略。
-
向前恢復(forward recovery)
適用于必須要成功的場景,發生失敗進行重試,執行順序是類似于這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn,其中j是發生錯誤的子事務(sub-transaction)。
顯然,向前恢復沒有必要提供補償事務,如果業務中子事務(最終)會成功,或補償事務難以定義或不可能,向前恢復更符合需求。
過度積極的重試策略(例如間隔太短或重試次數過多)會對下游服務造成不利影響,所以需要使用一個比較合理的重試機制。
-
向后恢復(backward recovery)
這種做法的效果是撤銷掉之前所有成功的子事務,使得整個Saga的執行結果撤銷。
下面講解的示例均為向后恢復策略。
Saga事務協調模式
Saga執行事務的順序稱為Saga的協調邏輯。這種協調邏輯有兩種模式,協調(Orchestration)和事件編排(Event Choreography)分別如下:
-
協調(Orchestration):Saga提供一個控制類,方便子事務的協調工作。事務執行的命令從控制類發起,按照邏輯順序請求Saga的子事務,從子事務那里接受到反饋以后,控制類再發起向其他子事務的調用。所有Saga的子事務都圍繞這個控制類進行溝通和協調工作。
以電商訂單的例子為例:
-
事務發起方的主業務邏輯請求控制類開啟訂單事務
-
控制類向庫存服務請求扣減庫存,庫存服務回復處理結果。
-
控制類向訂單服務請求創建訂單,訂單服務回復創建結果。
-
控制類向支付服務請求支付,支付服務回復處理結果。
-
主業務邏輯接收并處理事務處理結果回復。
控制類必須事先知道執行整個訂單事務所需的流程。如果有任何失敗,它負責通過向每個子事務發送命令來撤銷之前的操作來協調分布式的回滾。基于控制類協調一切時,回滾要容易得多,因為控制類默認是執行正向流程,回滾時只要執行反向流程即可。
-
事件編排(Event Choreography):子事務之間的調用、分配、決策和排序,通過交換事件進行進行。是一種去中心化的模式,子事務之間通過消息機制進行溝通,通過監聽器的方式監聽其他子事務發出的消息,從而執行后續的邏輯處理。由于沒有中間協調點,靠子事務進行相互協調。
以電商訂單的例子為例:
-
事務發起方的主業務邏輯發布開始訂單事件
-
庫存服務監聽開始訂單事件,扣減庫存,并發布庫存已扣減事件
-
訂單服務監聽庫存已扣減事件,創建訂單,并發布訂單已創建事件
-
支付服務監聽訂單已創建事件,進行支付,并發布訂單已支付事件
-
主業務邏輯監聽訂單已支付事件并處理。
實現方式對比
基于協調的Saga的優點如下:
-
服務之間關系簡單,避免子事務之間的循環依賴關系,因為Saga控制類會調用Saga子事務,但子事務不會調用控制類。
-
程序開發簡單,子事務只需要完成自身的任務,不用考慮處理消息的方式,降低子事務接入的復雜性。
-
易維護擴展,如果事務需要添加新步驟,只需修改控制類,保持事務復雜性保持線性,回滾更容易管理。
-
容易測試,測試工作集中在集中在控制類上,其他服務單獨測試功能即可。
基于協調的Saga的缺點如下:
-
控制類集中太多邏輯的風險,導致難以維護。
-
控制類存在單點故障風險。
基于事件編排的Saga的優點如下:
-
避免控制類單點故障風險。
-
子事務之間通過訂閱時間溝通,組合會更靈活。
基于事件編排的Saga的缺點如下:
-
服務之間存在循環依賴的風險。
-
當涉及的步驟較多,服務間關系混亂。如果沒有完善的文檔支撐,了解整個事務的執行過程只能通過閱讀代碼完成。增加開發人員理解和維護代碼的難度。
基于本地消息表機制
本地消息表機制會在數據庫中存放一個本地事務消息表,在進行本地事務操作的同時將操作狀態插入到本地事務消息表。消息插入成功后再調用其他服務,如果調用成功就修改這條本地消息的狀態;如果調用失敗則不停重試,下游接口需要保證冪等性。
本地消息表機制是一種最大努力通知思想。
這里以支付服務和會計服務為例展開介紹本地消息表方案。大概流程:用戶在支付服務完成了支付訂單支付成功后,此時會調用會計服務的接口生成一條原始的會計憑證到數據庫中。整體流程如圖:
完整流程:
-
在支付庫中加入一張消息表來記錄支付消息,即用戶支付成功后往這張消息表插入一條支付成功的消息,狀態為“發送中”。這里要保證了本地事務的強一致性(支付邏輯和插入消息表的消息組成了一個強一致性的事務,要么同時成功,要么同時失?。?。
-
完成第1步的邏輯后,再向mq的PAY_QUEUE隊列中投遞一條支付消息,這條支付消息的內容跟保存在支付庫消息表的消息內容一致。
-
會計服務監聽到這條消息了,會計服務處理消費邏輯開始生成會計憑證。
-
會計憑證生成后,再反向向mq投遞一條消費成功的消息到ACC_QUEUE隊列。
-
支付服務監聽到會計服務消費成功的消息,將本地消息表的消息狀態改為“已發送”。
-
消息恢復系統每隔一段時間去本地消息表中撈取狀態為“發送中”的消息,然后重新投遞到mq的PAY_QUEUE隊列中。
如果消息恢復系統重新投遞同一條消息達到一定閾值,則記錄報警和通知人工處理。
總結
本地消息表機制的優點是建設成本比較低,但也存在兩個缺點:
-
本地消息表與業務耦合在一起,很難實現通用性,無法單獨運維管理。
-
本地消息表是基于數據庫來做的,在高并發下是有性能瓶頸的。
基于事務消息機制
無論是2PC&3PC還是TCC、本地消息事務,基本都遵守XA協議的思想。即這些方案本質上都是事務協調者協調各個事務參與者的本地事務的進度,使所有本地事務共同提交或回滾,最終達成全局已執行的特性。在協調的過程中,協調者需要收集各個本地事務的當前狀態,并根據這些狀態發出下一階段的操作指令。
但這些全局事務方案由于操作繁瑣、時間跨度大,或者在全局事務期間會排他地鎖住相關資源,使得整個分布式系統的全局事務的并發度不會太高。這很難滿足電商等高并發場景對事務吞吐量的要求,因此互聯網服務提供商探索出了很多與XA協議背道而馳的分布式事務解決方案。其中利用消息中間件實現的最終一致性全局事務(事務消息事務)就是一個經典方案。
基于事務消息機制
普通消息是無法解決本地事務執行和消息發送的一致性問題的。因為消息發送是一個網絡通信的過程,發送消息的過程就有可能出現發送失敗、或者超時的情況。超時有可能發送成功了,有可能發送失敗了,消息的發送方是無法確定的,所以此時消息發送方無論是提交事務還是回滾事務,都有可能不一致性出現。
解決這個問題,需要引入事務消息,事務消息和普通消息的區別在于事務消息發送成功后,處于prepared狀態,不能被訂閱者消費,等到事務消息的狀態更改為可消費狀態后,下游訂閱者才可以監聽到次消息。
事務消息的發送處理流程如下:
-
事務發起者預先發送一個事務消息。MQ系統收到事務消息后,將消息持久化,消息的狀態是“待發送”,并給發送者一個ACK消息。
-
事務發起者如果沒有收到ACK消息,則取消本地事務的執行;如果收到了ACK消息,則執行本地事務。
-
執行本地事務后,根據結果發送給MQ系統提交或回滾請求。
本地事務執行完畢后,發給MQ的通知消息有可能丟失。所以支持事務消息的MQ系統有一個定時掃描邏輯,掃描出狀態仍然是“待發送”狀態的消息,并向消息的發送方發起詢問,詢問這條事務消息的最終狀態如何并根據結果更新事務消息的狀態。因此事務的發起方需要給MQ系統提供一個事務消息狀態查詢接口。
-
MQ系統收到消息通知后,如果提交請求,則將消息更改為“可消費”供訂閱者消費;如果事務執行回滾,則刪除該事務消息。
如果事務消息的狀態是“可發送”,則MQ系統向下游參與者推送消息,推送失敗會不停重試。
-
下游參與者收到消息后,執行本地事務,本地事務如果執行成功,則給MQ系統發送ACK消息;如果執行失敗或給MQ發送ACK的消息丟失,則MQ系統會持續推送給消息。
總結
基于事務消息機制實現了最終一致性,適用于異步更新的場景,并且對數據實時性要求不高的地方。
對比本地消息表實現方案,不需要創建本地消息表,也不需要依賴本地數據庫事務了,所以這種方案更適用于高并發的場景。
RocketMQ可以直接支持生產環境使用基于事務消息機制,其他消息中間件(例如Kafka,RabbitMQ等)需要自研封裝一個可靠消息服務。
RocketMQ事務消息解決的是本地事務的執行和發消息這兩個動作滿足事務的約束。Kafka事務消息則是用在一次事務中需要發送多個消息的情況,保證多個消息之間的事務約束,即多條消息要么都發送成功,要么都發送失敗。
最大努力通知機制
最大努力通知機制本質是通過引入定期校驗機制來對最終一致性做兜底,對業務侵入性較低、對MQ系統要求較低,實現比較簡單,適合于對最終一致性敏感度比較低、業務鏈路較短的場景,比如跨平臺、跨企業的系統間的業務交互。
適用場景
小明通過聯通網上營業廳為手機充話費。整個操作的流程如下:
-
小明選擇充值金額“50元”,支付方式“支付寶”。
-
聯通網上營業廳創建一個充值訂單,狀態為“支付中”,并跳轉到支付寶的支付頁面。
-
支付寶驗明確認小明的支付后,從小明的賬戶中扣除50元,并向聯通的賬戶中增加50元。執行完畢后向MQ系統發送一條消息,消息的內容標識支付是否成功,消息發送允許失敗。
-
如果消息發送成功,那么支付寶的通知服務會訂閱到該消息,并調用聯通的接口通知本次支付的結果。如果此時聯通的服務掛掉了,導致通知失敗了,則會按照5min、10min、30min、1h、...、24h等遞增的時間間隔,間隔性重復調用聯通的接口,直到調用成功或者達到預定的時間窗口上限后,則不再通知。這就是最大努力通知的含義。
-
如果聯通服務恢復正常,收到了支付寶的通知,給賬戶充值(聯通的充值接口需要保證冪等性)
-
如果聯通服務故障時間很久,恢復正常后,已超出支付寶通知服務的時間窗口,則聯通掃描“支付中”的訂單,主動向支付寶發起請求,核驗訂單的支付結果。
技術選型
2PC&3PC
2PC&3PC強依賴數據庫,能夠很好的提供強一致性和強事務性,但相對來說延遲比較高,比較適合傳統的單體應用,在同一個方法中存在跨庫操作的情況,不適合大型分布式、高并發和高性能要求的場景。
TCC
TCC適用于執行時間確定且較短,實時性要求高,對數據一致性要求高,比如互聯網金融企業最核心的三個服務:交易、支付、賬務。
Saga事務
Saga事務適用于業務流程長、業務流程多的業務且并發操作同一資源較少的情況。在銀行業金融機構使用廣泛,比如互聯網微貸、渠道整合場景、金融機構對接系統(需要對接外部系統)等
基于本地消息表機制&基于事務消息機制
兩者都適用于事務中參與方支持操作冪等,對一致性要求不高,業務上能容忍數據不一致,直到兜底機制完成最終一致性。事務涉及的參與方、參與環節較少,業務上有對賬/校驗系統兜底。
最大努力通知機制
最大努力通知機制適合于對最終一致性敏感度比較低、業務鏈路較短的場景。
前置知識
對于文中提到名詞的補充解釋。
DTP模型
DTP(Distributed Transaction Process)是一個分布式事務模型。在這個模型里面,有三個角色:
-
AP: Application,事務的發起者,也就是業務層。哪些操作屬于一個事務,就是AP定義的。
-
TM: Transaction Manager,事務管理器,也稱協調者。接收AP的事務請求,對全局事務進行管理,管理事務分支狀態,協調RM的處理,通知RM哪些操作屬于哪些全局事務以及事務分支等等。是整個事務調度模型的核心部分。
-
RM:Resource Manager,資源管理器,也稱參與者。一般是數據庫,也可以是其他的資源管理器,如消息隊列(如JMS數據源),文件系統等。
DTP模型上定義了三個角色,但實際實現上可以由一個角色同時擔當兩個功能。比如:AP和TM合并,TM沒必要單獨部署組件。
XA協議(XA Specification)
XA是一種分布式事務處理規范。XA規范了TM與RM之間的通信接口(如下圖所示的函數),在TM與多個RM之間形成一個雙向通信橋梁,從而在多個數據庫資源下保證強一致性。目前知名的數據庫,如Oracle、DB2、MySQL等,都是實現了XA接口的,都可以作為RM。
在整個事務處理過程中,數據一直處于鎖住狀態,即從prepare到commit、rollback的整個過程中,TM一直持有數據庫的鎖,如果有其他事務要修改數據庫的該條數據,就必須等待鎖的釋放。