RocketMQ分布式事務消息的設計原理

                        小編:管理員 76閱讀 2022.08.03

                        Apache RocketMQ 社區正式發布4.3版本。此次發布不僅包括提升性能,減少內存使用等原有特性增強,還修復了部分社區提出的若干問題,更重要的是該版本開源了社區最為關心的分布式事務消息,而且實現了對外部組件的零依賴。接下來,本文將詳細探秘RocketMQ事務消息的設計原理以及實現機制。

                        一 需求緣起

                        在微服務架構中,隨著服務的逐步拆分,數據庫私有已經成為共識,這也導致所面臨的分布式事務問題成為微服務落地過程中一個非常難以逾越的障礙,但是目前尚沒有一個完整通用的解決方案。

                        其實不僅僅是在微服務架構中,隨著用戶訪問量的逐漸上漲,數據庫甚至是服務的分片、分區、水平拆分、垂直拆分已經逐漸成為較為常用的提升瓶頸的解決方案,因此越來越多的原子操作變成了跨庫甚至是跨服務的事務操作。最終結果是在對高性能、高擴展性,高可用性的追求的道路上,我們開始逐漸放松對一致性的追求,但是在很多場景下,尤其是賬務,電商等業務中,不可避免的存在著一致性問題,使得我們不得不去探尋一種機制,用以在分布式環境中保證事務的一致性。

                        二 理論基石

                        微服務使得單體架構擴展為分布式架構,在擴展的過程中,逐漸喪失了單體架構中數據源單一,可以直接依賴于數據庫進行事務操作的能力,而關系型數據庫中,提供了強大的事務處理能力,可以滿足ACID(Atomicity,Consistency,Isolation,Durability)的特性,這種特性保證了數據操作的強一致性,這也是分布式環境中弱一致性以及最終一致性能夠得以實現的基礎。

                        數據一致性分為三個種類型:強一致性,弱一致性以及最終一致性,正如上文所述,數據庫實現的就是強一致性,能夠保證在寫入一份新的數據庫,立即使其可見。最終一致性是弱一致性的強化版,系統保證在沒有后續更新的前提下,系統最終返回上一次更新操作的值。在沒有故障發生的前提下,不一致窗口的時間主要受通信延遲,系統負載和復制副本的個數影響。

                        然而,微服務作為分布式系統,同樣受CAP[1]原理的制約,在CAP理論中, C:Consistency、A:Availability、P:Partition tolerance三者不可同時滿足,而服務化中,更多的是提升A以及P,在這個過程中不可避免的會降低對C的要求,因此,BASE理論隨之而來。

                        BASE[2]理論來源于ebay在2008年ACM中發表的論文,BASE理論的基本原則有三個:Basically Available,Soft state,Eventually consistent,主要目的是為了提升分布式系統的可伸縮性,論文同樣闡述了如何對業務進行調整以及折中的手段,BASE理論的提出為分布式事務的發展指出了一個方向。

                        在最終一致性的實現過程中,最基本的操作就是保證事務參與者的冪等性,所謂的冪等性,就是業務方能夠使用相關的手段,保證單個事務多次提交依然能夠保證達到同樣的目的。

                        (冪等性在數學上指:滿足f(f(x))=f(x)條件的運算,在分布式系統理論指:調用方反復執行同一操作與只正確執行一次操作效果相同)

                        三 當前解決方案

                        1. 2PC/3PC

                        談到分布式事務,首先要說的就是2PC(two phase commit)方案,如下圖所示[3]:

                        2PC把事務的執行分為兩個階段,第一個階段即prepare階段,這個階段實際上就是投票階段,協調者向參與者確認是否可以共同提交,再得到全部參與者的所有回答后,協調者向所有的參與者發布共同提交或者共同回滾的指令,用以保證事務達到一致性。

                        但是分布式系統中的所有通信均存在著三種狀態:成功,失敗,超時。其中,超時狀態的存在是我們在設計分布式系統時所面對的永遠的痛,2PC同樣存在問題,尤其是在發送完可以提交的指令后,參與者在沒有收到提交或者回滾的指令時,面對已經上鎖的資源,面對已經寫出去的undo或者redo日志,參與者會一時陷入手足無措的狀態,為了解決這個問題,3PC應運而生,如下圖所示[4]:

                        3PC在commit之前增加了preCommit的過程,使得在參與者在收不到確認時,依然可以從容commit或者rollback,避免資源鎖定太久導致浪費。但是3PC同樣存在著很多問題。實現起來非常復雜,因為很難通過多次詢問來解決系統間分歧問題,尤其是存在超時狀態互不信任的分布式網絡中,這也就是著名的拜占庭將軍問題[5]。

                        總結一下,2PC是幾乎所有分布式事務算法的基礎,后續的分布式事務算法幾乎都由此改進而來,其優缺點非常明顯:

                        • 優點:在于已經有較為成熟的實現方案,比如XA。
                        • 缺點:XA是一個阻塞協議。服務在投票后需要等待協調器的決定,此時服務會阻塞并鎖定資源。由于其阻塞機制和最差時間復雜度高, 因此,這種設計不能適應隨著事務涉及的服務數量增加而擴展的需要,很難用于并發較高以及子事務聲明周期較長(long-running transactions)的分布式服務中。

                        2. SAGA

                        SAGA算法[6] 于1987年提出,是一種異步的分布式事務解決方案,其理論基礎在于,其假設所有事件按照順序推進,總能達到系統的最終一致性,因此saga需要服務分別定義提交接口以及補償接口,當某個事務分支失敗時,調用其它的分支的補償接口來進行回滾,saga的具體實現分為兩種:Choreography以及Orchestration,

                        (1) Choreography:如下圖所示:

                        這種模式下不存在協調器的概念,每個節點均對自己的上下游負責,在監聽處理上游節點事件的同時,對下游節點發布事件。

                        (2) Orchestration:存在中心節點的模式,如下圖所示:

                        該中心節點,即協調器知道整個事務的分布狀態,相比于無中心節點方式,該方式有著許多優點:

                        1. 能夠避免事務之間的循環依賴關系。
                        2. 參與者只需要執行命令/回復(其實回復消息也是一種事件消息),降低參與者的復雜性。
                        3. 開發測試門檻低。
                        4. 在添加新步驟時,事務復雜性保持線性,回滾更容易管理。因此大多數saga模型實現均采用了這種思路。

                        總結一下:SAGA模型的優點在于其降低了事務粒度,使得事務擴展更加容易,同時采用了異步化方式提升性能。但是其缺點在于很多時候很難定義補償接口,回滾代價高,而且由于SAGA在執行過程中采用了先提交后補償的思路進行操作,所以單個子事務在并發提交時的隔離性很難保證。

                        3. TCC

                        TCC(Try-Confirm-Concel)模型[7]同樣是一種補償性事務,主要分為Try:檢查、保留資源,Confirm:執行事務,Concel:釋放資源三個階段,如下圖所示:

                        其中,活動管理器記錄了全局事務的推進狀態以及各子事務的執行狀態,負責推進各個子事務共同進行提交或者回滾。同時負責在子事務處理超時后不停重試,重試不成功后轉手工處理,用以保證事務的最終一致性。

                        總結一下,相比于SAGA模型,其優點在于嘗試階段僅僅只是對業務系統做檢測,并保留業務資源,并沒有真正提交,所以后續SAGA需要針對提交的事務做補償,而TCC則僅僅需要釋放保留資源,降低了補償成本;并且,由于在Try階段對資源進行了保留鎖定,所以相比于SAGA模式,TCC模式擁有更高的隔離性。

                        缺點:相比于SAGA模式,TCC模式多增加了一個狀態,導致在業務開發過程中,復雜度上升,而且協調器與子事務的通信過程增加,狀態輪轉處理也更為復雜。

                        四 事務消息

                        以購物場景為例,張三購買物品,賬戶扣款100元的同時,需要保證在下游的會員服務中給該賬戶增加100積分。由于數據庫私有,所以導致在實際的操作過程中會出現很多問題,比如先發送消息,可能會因為扣款失敗導致賬戶積分無故增加,如果先執行扣款,則有可能因服務宕機,導致積分不能增加,無論是先發消息還是先執行本地事務,都有可能導致出現數據不一致的結果。

                        事務消息的本質就是為了解決此類問題,解決本地事務執行與消息發送的原子性問題。目前,事務消息在多種分布式消息中間件種均有實現,但是其實現方式思路卻各有不同。

                        1. 傳統事務消息實現

                        傳統事務消息實現,一種思路是依賴于AMQP協議用來確保消息發送成功,AMQP模式下需要在發送在發送事務消息時進行兩階段提交,首先進行tx_select開啟事務,然后再進行消息發送,最后進行消息的commit或者是rollback。這個過程可以保證在消息發送成功的同時本地事務也一定成功執行,但事務粒度不好控制,而且會導致性能急劇下降,同時依然無法解決本地事務執行與消息發送的原子性問題。

                        還有另外一種思路,就是通過保證多條消息的同時可見性來保證事務一致性。但是此類消息事務實現機制更多的是用到consume-transform-produce場景中,其本質還是用來保證消息自身事務,并沒有把外部事務包含進來。

                        2. RocketMQ事務消息

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

                        2.1 RocketMQ 事務消息設計

                        事務消息作為一種異步確保型事務, 將兩個事務分支通過MQ進行異步解耦,RocketMQ事務消息的設計流程同樣借鑒了兩階段提交理論,整體交互流程如下圖所示:

                        1. 事務發起方首先發送prepare消息到MQ。
                        2. 在發送prepare消息成功后執行本地事務。
                        3. 根據本地事務執行結果返回commit或者是rollback。
                        4. 如果消息是rollback,MQ將刪除該prepare消息不進行下發,如果是commit消息,MQ將會把這個消息發送給consumer端。
                        5. 如果執行本地事務過程中,執行端掛掉,或者超時,MQ將會不停的詢問其同組的其它producer來獲取狀態。
                        6. Consumer端的消費成功機制有MQ保證。

                        2.2 RocketMQ事務消息實現

                        RocketMQ事務消息在實現上充分利用了RocketMQ本身機制,在實現零依賴的基礎上,同樣實現了高性能、可擴展、全異步等一系列特性。

                        在具體實現上,RocketMQ通過使用Half Topic 以及Operation Topic 兩個內部隊列來存儲事務消息推進狀態,如下圖所示:

                        其中,Half Topic對應隊列中存放著prepare消息,Operation Topic對應的隊列則存放了prepare message對應的commit/rollback消息,消息體中則是prepare message對應的offset,服務端通過比對兩個隊列的差值來找到尚未提交的超時事務,進行回查。

                        在具體實現上,事務消息作為普通消息的一個應用場景,在實現過程中進行了分層抽象,從而避免了對RocketMQ原有存儲機制的修改,如下圖所示:

                        從用戶側來說,用戶需要分別實現本地事務執行以及本地事務回查方法,因此只需關注本地事務的執行狀態即可;而在service層,則對事務消息的兩階段提交進行了抽象,同時針對超時事務實現了回查邏輯,通過不斷掃描當前事務推進狀態,來不斷反向請求Producer端獲取超時事務的執行狀態,在避免事務掛起的同時,也避免了Producer端的單點故障。而在存儲層,RocketMQ通過Bridge封裝了與底層隊列存儲的相關操作,用以操作兩個對應的內部隊列,用戶也可以依賴其它存儲介質實現自己的service,RocketMQ會通過ServiceProvider加載進來。

                        從上述事務消息設計中可以看到,RocketMQ事務消息較好的解決了事務的最終一致性問題,事務發起方僅需要關注本地事務執行以及實現回查接口給出事務狀態判定等實現,而且在上游事務峰值高時,可以通過消息隊列,避免對下游服務產生過大壓力。

                        事務消息不僅適用于上游事務對下游事務無依賴的場景,還可以與一些傳統分布式事務架構相結合,而MQ的服務端作為天生的具有高可用能力的協調者,使得我們未來可以基于RocketMQ提供一站式輕量級分布式事務解決方案,用以滿足各種場景下的分布式事務需求。

                        關聯標簽:
                        少妇各种各样BBBⅩXX