天天看點

純幹貨 | 細說分布式事務兩階段送出

事務的概念在 這篇文章 中描述過,在分布式系統中,讀寫位于多個節點的資料,如果依舊想保證ACID特性,就必須實作分布式事務。而其實作關鍵則是适當的送出協定,目前最簡潔,且使用最廣泛的無疑是兩階段送出協定(2PC)。

1.實作分布式事務關鍵元件

單機系統通過事務管理器(transaction manager,TM)實作本地事務。分布式系統中,需要協調多個節點的事務管理器,共同送出成功或失敗,是以需要事務協調者(transaction coordinator,TC)。一個分布式事務管理器,可以粗略地劃分為這兩個子系統。這兩個子系統根據自己在事務執行中扮演的角色,也可稱之為參與者與協調者。

本地事務管理器負責本機事務并發控制和異常恢複等功能,事務協調者負責開啟事務,将事務劃分為多個子事務分發到相應的節點執行,并協調事務完成(一起送出成功或失敗)。在實作中,TM和TC可以實作在同一個程序中,也可以部署在不同的節點。

2.經典兩階段送出協定

兩階段送出的流程比較簡單。當分布式事務T執行完成,即事務執行的各節點都告知協調者TC,事務已經執行完成,TC便開啟兩階段送出流程。

純幹貨 | 細說分布式事務兩階段送出

Phase 1 Prepare:

1.TC寫本地日志,并持久化。TC向所有參與者發送Prepare T消息。

2.各參與者TM收到Prepare T消息,根據自身情況,決定是否送出事務。

  • 如果決定送出,TM寫日志并持久化,向TC發送Ready T消息。
  • 如果決定不送出,TM寫日志并持久化,向TC發送Abort T消息,本地也進入事務abort流程。

Phase 2 Commit :

1.當TC收到所有節點的回應,或者等待逾時,決定事務commit或abort。

  • 如果所有參與者回應Ready T,則TC先寫日志并持久化,再向所有參與者發送Commit T消息。
  • 如果收到至少一個參與者Abort T回應,或者在逾時時間内有參與者未回應,則TC先寫日志,再向所有參與者發送Abort T消息。

2.參與者收到TC的消息後,寫或日志并持久化。

兩階段送出協定可以保證分布式事務執行的一個關鍵點:參與者在向協調者發生Ready T消息前,随時都可以自己決定是否abort,一旦這個消息發送,那麼這個事務就進入ready狀态,commit和abort完全由協調者控制。Ready T消息本質上是參與者向協調者發送的一個鄭重的、不可逆的承諾。為了保證這一個承諾,參與者需要在發送Ready T消息前将所有必要的資訊持久化,否則如果參與者在發送Ready T後異常當機,重新開機後可能無法遵守以上承諾。在第二階段,當協調者寫了或日志,整個事務的命運就被決定了,不會再發生變化了。

為了優化2PC性能,減少關鍵路徑的持久化和RPC次數是關鍵,一種對經典2PC的優化思路如下:

協調者無狀态,不再持久化日志,但是為了友善當機重新開機後恢複事務狀态,需要向每個參與者發送事務的參與者名單并持久化。這樣即使協調者當機,參與者也可以友善地詢問其他參與者事務狀态了。該思路相當于參與者在協調者當機時,自己擔當起協調者詢問事務狀态的任務。

隻要所有參與者prepare成功,事務一定會成功送出。是以為了減少送出延時,協調者可以在收到所有參與者prepare成功後就傳回用戶端成功,但如此,讀請求可能會因為送出未完成而等待,進而增大讀請求的延時。反過來,如果協調者确認所有參與者都送出成功才傳回用戶端成功,送出延時比較長,但會減少讀請求延時。

3.兩階段送出協定異常處理

兩階段送出協定的正常流程較為簡單,但它還需要考慮分布式系統中各種異常問題(節點失敗,網絡分區等)。

1.如果協調者檢測到參與者失敗:

  • 如果參與者在發送Ready T前失敗,則協調者認為該節點事務Abort,并開始abort流程。
  • 如果參與者在發送Ready T後失敗,證明參與者本地事務已經持久化,協調者忽視參與者失敗,繼續事務流程。

2.如果參與者在事務送出過程中失敗,其恢複過程,需要根據參與者日志内容,決定本地事務狀态。

  • 如果日志中包含日志,證明事務已經成功送出,REDO(T)。
  • 如果日志中包含日志,證明事務已經失敗,UNDO(T)。
  • 如果日志中包含日志,參與者P需向其它節點咨詢目前事務狀态。
    • 如果協調者正常,則向告知參與者P,事務已經commit或是abort,參與者依此REDO(T)或UNDO(T)。
    • 如果協調者異常,則向其它參與者詢問事務狀态。
      • 如果其他參與者收到資訊,并已知事務是commit還是abort狀态,需回複參與者P事務狀态。
      • 如果所有的參與者現在都不知道該事務的狀态(事務上下文銷毀了,或者自己也處于未決狀态),那麼該事務處于暫時既不能commit也不能abort。需要定期向其它節點問詢事務狀态,直到得到答案。(這是2PC最不想遇到的一個場景)
  • 如果日志中不包含上述幾種日志,說明該參與者在向協調者發送Ready T消息前就失敗了。由于協調者沒有收到參與者的回應,會逾時Abort,是以該參與者在恢複過程中,遇到這種情況也需要abort。

3.如果協調者在事務送出過程中失敗。參與者需要根據全局事務狀态(通過與其它參與者通信)決定本地行為。

(事務狀态已經形成決議:)

  • 如果至少有一個參與者中事務T已經送出(參與者包含日志),說明T必須要送出。
  • 如果至少有一個參與者中事務T已經Abort(參與者包含日志),說明T必須要Abort。

(事務狀态未形成決議:)

  • 如果至少有一個參與者沒有進入Ready狀态(參與者不包含日志)。說明全局還未就送出與否達成協定。有兩種選擇:(1)等待協調者恢複。(2)參與者自行abort。為了減少資源占用時間,選擇後者居多。
  • 如果所有參與者都進入了Ready狀态,且都沒有或日志(事實上,即使有這些日志,查日志也是一種比較費的操作,還需要考慮日志回收的問題),這種情況下,參與者誰都不知道現在事務的狀态,隻能死等協調者恢複。(又到了這個最不想遇到的場景)

當參與者均進入ready狀态,等待協調者的下一步指令,協調者在這個時候出現異常,那麼參與者将一直持有系統資源,如果基于鎖實作的并發控制,還會一直持有鎖,導緻其他事務等待。這種情況如果持續較舊,會對系統産生巨大的影響。是以2PC最大的問題就是協調者失敗,可能會導緻事務阻塞,未決事務的最終狀态,隻能等待協調者恢複後才确定。同時在這種情況下,參與者當機重新開機,回放到這類未決事務,也會因為死等而block recovery流程。

4.緩解2PC blocking思路

三階段送出是兩階段送出的延伸,目的是解決2PC block的問題,但是也引入了其它問題。它的解決方式是為參與者引入timeout機制,如果參與者成功PreCommit後,一直收不到協調者最後的DoCommit請求,等待逾時自動送出,顯然這樣會引入一緻性問題,例如,協調者收到一個參與者PreCommit失敗,打算發abort請求給其它參與者時當機,顯然此時該分布式事務應該失敗,但一些參與者可能因為逾時而送出。

為了解決這個問題,3PC多引進了一個階段,就是第一個階段CanCommit階段,協調者詢問所有參與者是否可以送出,參與者如果狀态正常,就會回應可以送出,但此時并不會占用任何系統資源。如果協調者及時收到了所有參與者ok的回應,便會認為各個參與者正常,之後的送出應該不會失敗。但是實質上,仍有小機率失敗的可能:某參與者PreCommit失敗後,協調者和參與者都當機,其它參與者逾時自動送出,産生不一緻。

是以3PC還有一個關鍵優化是協調者當機後,迅速找到一個繼任者,繼續未完的流程,盡量保證不會出現參與者逾時送出的現象。但是如果出現諸如網絡分區等異常,新的協調者聯系不上參與者,還是會産生一緻性問題。

3PC通過犧牲一定的C(onsistency)來提高A(vailability),并且增加了網絡開銷,這些都是OLTP系統很難接受的,是以基本沒有系統會采用。

但是協調者高可用,确實可以使block的時間大幅減少,基于諸如Paxos/Raft的一緻性協定的高可用方案,可以讓多個節點就commit/abort達成一緻後,再去通知參與者,當協調者出現異常,可以迅速選出新的協調者,推進事務至完成。

繼續閱讀