淺析阿里分佈式事務組件 fescar/seata 2pc 的設計思想

二階段提交協議的由來

X/Open 組織提出了分佈式事務處理的規範 DTP 模型(Distributed Transaction Processing),該模型中主要定義了三個基本組件,分別是git

  • 應用程序(Application Program ,簡稱AP):用於定義事務邊界(即定義事務的開始和結束),而且在事務邊界內對資源進行操做。
  • 資源管理器(Resource Manager,簡稱RM):如數據庫、文件系統等,並提供訪問資源的方式。
  • 事務管理器(Transaction Manager ,簡稱TM):負責分配事務惟一標識,監控事務的執行進度,並負責事務的提交、回滾等。

通常,咱們稱 TM 爲事務的協調者,而稱 RM 爲事務的參與者。TM 與 RM 之間的通訊接口,則由 XA 規範來約定。github

在 DTP 模型的基礎上,才引出了二階段提交協議來處理分佈式事務。redis

二階段提交基本算法

前提

二階段提交協議可以正確運轉,須要具有如下前提條件:算法

  1. 存在一個協調者,與多個參與者,且協調者與參與者之間能夠進行網絡通訊
  2. 參與者節點採用預寫式日誌,日誌保存在可靠的存儲設備上,即便參與者損壞,不會致使日誌數據的消失
  3. 參與者節點不會永久性損壞,即便後仍然能夠恢復

實際上,條件2和3所要求的,現今絕大多數關係型數據庫都能知足。數據庫

基本算法

第一階段

  1. 協調者節點向全部參與者節點詢問是否能夠執行提交操做,並開始等待各參與者節點的響應。
  2. 參與者節點執行詢問發起爲止的全部事務操做,並將Undo信息和Redo信息寫入日誌。
  3. 各參與者節點響應協調者節點發起的詢問。若是參與者節點的事務操做實際執行成功,則它返回一個"贊成"消息;若是參與者節點的事務操做實際執行失敗,則它返回一個"停止"消息。

第二階段

當協調者節點從全部參與者節點得到的相應消息都爲"贊成"時:微信

  1. 協調者節點向全部參與者節點發出"正式提交"的請求。
  2. 參與者節點正式完成操做,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送"完成"消息。
  4. 協調者節點收到全部參與者節點反饋的"完成"消息後,完成事務。

以下圖所示:
二階段提交基本算法網絡

若是任一參與者節點在第一階段返回的響應消息爲"終止",或者 協調者節點在第一階段的詢問超時以前沒法獲取全部參與者節點的響應消息時:併發

  1. 協調者節點向全部參與者節點發出"回滾操做"的請求。
  2. 參與者節點利用以前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送"回滾完成"消息。
  4. 協調者節點收到全部參與者節點反饋的"回滾完成"消息後,取消事務。

以下圖所示:
事務提交/回滾運維

缺陷分析

二階段提交協議除了協議自己具備的侷限性以外,若是咱們把如下狀況也考慮在內:異步

  • 協調者宕機
  • 參與者宕機
  • 網絡閃斷(腦裂)

那麼二階段提交協議其實是存在不少問題的

協議自己的缺陷

協議自己的缺陷是指,在協議正常運行的狀況下,不管全局事務最終是被提交仍是被回滾,依然存在的問題,而暫不考慮參與者或者協調者宕機,或者腦裂的狀況。

性能問題

參與者的本地事務開啓後,直到它接收到協調者的 commit 或 rollback 命令後,它纔會提交或回滾本地事務,而且釋放因爲事務的存在而鎖定的資源。不幸的是,一個參與者收到協調者的 commit 或者 rollback 的前提是:協調者收到了全部參與者在一階段的回覆。
若是說,協調者一階段詢問多個參與者採用的是順序詢問的方式,那麼一個參與者最快也要等到協調者詢問完全部其它的參與者後纔會被通知提交或回滾,在協調者未詢問完成以前,這個參與者將保持佔用相關的事務資源。
即便,協調者一階段詢問多個參與者採用的是併發詢問的方式,那麼一個參與者等待收到協調者的提交或者回滾通知的時間,將取決於在一階段過程當中,響應協調者最慢的那個參與者的響應時間。
不管是哪種狀況,參與者都將在整個一階段持續的時間裏,佔用住相關的資源,參與者事務的處理時間增長。若此時在參與者身上有其它事務正在進行,那麼其它事務有可能由於與這個延遲的事務有衝突,而被阻塞,這些被阻塞的事務,進而會引發其它事務的阻塞。
總而言之,總體事務的平均耗時增長了,總體事務的吞吐量也下降了。這會使得整個應用系統的延遲變高,吞吐量下降,可擴展性下降(當參與者變多的時候,延遲可能更嚴重)。
總的來講,二階段提交協議,不是一個高效的協議,會帶來性能上的損失。

全局事務隔離性的問題

全局事務的隔離性與單機事務的隔離性是不一樣的。
當咱們在單機事務中提到不容許髒讀時,那麼意味着在事務未提交以前,它對數據形成的影響不該該對其它事務可見。
當咱們在全局事務中提到不容許髒讀時,意味着,在全局事務未提交以前,它對數據形成的影響不該該對其它事務可見。
在二階段提交協議中,當在第二階段全部的參與者都成功執行 commit 或者 rollback 以後,全局事務纔算結束。但第二階段存在這樣的中間狀態:即部分參與者已執行 commit 或者 rollback,而其它參與者還未執行 commit 或者 rollback。此刻,已經執行 commit 或者 rollback 的參與者,它對它本地數據的影響,對其它全局事務是可見的,即存在髒讀的風險。對於這種狀況,二階段協議並無任何機制來保證全局事務的隔離性,沒法作到「讀已提交」這樣的隔離級別。

協調者宕機

若是在第一階段,協調者發生了宕機,那麼由於全部參與者沒法再接收到協調者第二階段的 commit 或者 rollback 命令,因此他們會阻塞下去,本地事務沒法結束,
若是協調者在第二階段發生了宕機,那麼可能存在部分參與者接收到了 commit/rollback 命令,而部分沒有,所以這部分沒有接收到命令的參與者也會一直阻塞下去。
協調者宕機屬於單點問題,能夠經過另選一個協調者的方式來解決,但這隻能保證後續的全局事務正常運行。而由於以前協調者宕機而形成的參與者阻塞則沒法避免。若是這個新選擇的協調者也宕機了,那麼同樣會帶來阻塞的問題。

參與者宕機

若是在第一階段,某個參與者發生了宕機,那麼會致使協調者一直等待這個參與者的響應,進而致使其它參與者也進入阻塞狀態,全局事務沒法結束。
若是在第二階段,協調者發起 commit 操做時,某個參與者發生了宕機,那麼全局事務已經執行了 commit 的參與者的數據已經落盤,而宕機的參與者可能還沒落盤,當參與者恢復過來的時候,就會產生全局數據不一致的問題。

網絡問題-腦裂

當網絡閃斷髮生在第一階段時,可能會有部分參與者進入阻塞狀態,全局事務沒法結束。
當發生在第二階段時,可能發生部分參與者執行了 commit 而部分參與者未執行 commit,從而致使全局數據不一致的問題。

三階段提交

三階段提交

在二階段提交中,當協調者宕機的時候,不管是在第一階段仍是在第二階段發生宕機,參與者都會由於等待協調者的命令而進入阻塞狀態,從而致使全局事務沒法繼續進行。所以,若是在參與者中引入超時機制,即,當指定時間過去以後,參與者自行提交或者回滾。可是,參與者應該進行提交仍是回滾呢?悲觀的作法是,統一都回滾。但事情每每沒那麼簡單。
當第一階段,協調者宕機時,那麼全部被阻塞的參與者選擇超時後回滾事務是最明智的作法,由於還未進入第二階段,因此參與者都不會接收到提交或者回滾的請求,當前這個事務是沒法繼續進行提交的,由於參與者不知道其它參與者的執行狀況,因此統一回滾,結束分佈式事務。
在二階段提交協議中的第二階段,當協調者宕機後,因爲參與者沒法知道協調者在宕機前給其餘參與者發了什麼命令,進入了第二階段,全局事務要麼提交要麼回滾,參與者若是引入超時機制,那麼它應該在超時以後提交仍是回滾呢,彷佛怎麼樣都不是正確的作法。執行回滾,太保守,執行提交,太激進。
若是在二階段提交協議中,在第一階段和第二階段中間再引入一個階段,若是全局事務度過了中間這個階段,那麼在第三階段,參與者就能夠認爲此刻進行提交的成功率會更大。但這難道不是治標不治本嗎,當進入第三階段,全局事務須要進行回滾時候,若是協調者宕機,那麼參與者超時以後自行進行提交事務,就會形成全局事務的數據不一致。
再考慮參與者宕機的狀況下,協調者應該在超時以後,對全局事務進行回滾。
總結起來,三階段提交主要在二階段提交的基礎上,爲了解決參與者和協調者宕機的問題,而引入了超時機制,並由於超時機制,附帶引入中間這一層。
而且,三階段提交併無解決二階段提交的存在的腦裂的問題。
總而言之,二階段和三階段提交都沒法完美地解決分佈式事務的問題。關於三階段提交更詳細的算法和步驟,能夠參考個人另一篇文章《分佈式事務概覽》

fescar二階段提交

fescar 是阿里最近開源的一個關於分佈式事務的處理組件,它的商業版是阿里雲上的 GTS。
在其官方wiki上,咱們能夠看到,它對XA 二階段提交思考與改進。
在咱們上面提到的參與者中,這個參與者每每是數據庫自己,在 DTP 模型中,每每稱之爲 RM,即資源管理器。fescar 的二階段提交模型,也是在 DTP 模型的基礎上構建。

RM邏輯不與數據庫綁定

fescar 2PC 與 XA 2PC 的第一個不一樣是,fescar 把 RM 這一層的邏輯放在了 SDK 層面,而傳統的 XA 2PC,RM的邏輯其實就在數據庫自己。fescar 這樣作的好處是,把提交與回滾的邏輯放在了 SDK 層,從而沒必要要求底層的數據庫必須對 XA 協議進行支持。對於業務來講,業務層也不須要爲本地事務和分佈式事務兩類不一樣場景來適配兩套不一樣的數據庫驅動。
基於咱們先前對 XA 2PC 討論,XA 2PC 存在參與者宕機的狀況,而 fescar 的 2PC 模型中,參與者其實是 SDK。參與者宕機這個問題之因此在 XA 2PC 中是個大問題,主要是由於 XA 中,分支事務是有狀態的,即它是跟會話綁定在一塊兒的,沒法跨鏈接跨會話對一個分支事務進行操做,所以在 XA 2PC 中參與者一旦宕機,分支事務就已經沒法再進行恢復。
fescar 2PC 中,參與者其實是SDK,而SDK是能夠作高可用設計的。而且,在其第一階段,分支事務實際上已是被提交了的,後續的全局上的提交和回滾,其實是操做數據的鏡像,全局事務的提交會異步清理 undo_log,回滾則會利用保存好的數據鏡像,進行恢復。fescar 的 2PC 中,其實是利用了 TCC 規範的無狀態的理念。由於全局事務的執行、提交和回滾這幾個操做間不依賴於公共狀態,好比說數據庫鏈接。因此參與者其實是能夠成爲無狀態的集羣的。
也就是說,在 fescar 2PC 中,協調者若是發現參與者宕機或者超時,那麼它能夠委託其餘的參與者去作。

第二階段非阻塞化

fescar 與 xa

fescar 2PC 的第一階段中,利用 SDK 中的 JDBC 數據源代理經過對業務 SQL 的解析,把業務數據在更新先後的數據鏡像組織成回滾日誌,利用本地事務 的 ACID 特性,將業務數據的更新和回滾日誌的寫入在同一個 本地事務 中提交。
這就意味着在阻塞在第一階段事後就會結束,減小了對數據庫數據和資源的鎖定時間,明顯效率會變動高。根據 fescar 官方的說法,一個正常運行的業務,大機率是 90% 以上的事務最終應該是成功提交的,所以能夠在第一階段就將本地事務提交呢,這樣 90% 以上的狀況下,能夠省去第二階段持鎖的時間,總體提升效率。
fescar 的這個設計,直接優化了 XA 2PC 協議自己的性能缺陷。

協調者的高可用

XA 2PC 中存在協調者宕機的狀況,而 fescar 的總體組織上,是分爲 server 層和 SDK 層的,server 層做爲事務的協調者,fescar 的話術中稱協調者爲 TC(Transaction Coordinator ),稱 SDK 爲 TM(Transaction Manager)。截止至這篇文章發表前,fescar 的server層高可用還未實現,依據其官方的藍圖,它可能會採用集羣內自行協商的方案,也可能直接借鑑高可用KV系統。自行實現集羣內高可用方案,可能須要引進一套分佈式一致性協議,例如raft,我認爲這是最理想的方式。而直接利用高可用KV系統,例如 redis cluster,則會顯得系統太臃腫,但實現成本低。

事務的隔離性

XA 2PC 是沒有機制去支持全局事務隔離級別的,fescar 是提供全局事務的隔離性的,它把全局鎖保存在了 server 層。全局事務隔離級別若是過高,性能會有很大的損耗。目前的隔離界別默認是讀未提交,若是須要讀已提交或者更高的級別,就會涉及到全局鎖,則意味着事務的併發性會受影響。應用層業務層應該選擇合適的事務隔離級別。

腦裂的問題仍然沒有完美解決

不管是 XA 仍是 fescar,都未解決上述提到的腦裂的問題。腦裂的問題主要影響了全局事務的最後的提交和回滾階段。
沒有完美的分佈式事務解決方案,即便是 fescar 或者 GTS,它們也必然須要人工介入。但腦裂問題是小几率事件,而且不是致命性錯誤,能夠先經過重試的方法來解決,若是不行,能夠收集必要的事務信息,由運維介入,以自動或者非自動的方式,恢復全局事務。

參考資料

維基百科:二階段提交
《2PC之踵?是時候升級二階段提交協議了》 by Tim Yang
《分佈式事務概覽》by beanlam
fescar wiki

掃一掃關注個人微信公衆號

相關文章
相關標籤/搜索