作者:旺德
事務的概念在這篇文章中描述過,在分布式系統中,讀寫位於多個節點的數據,如果依舊想保證ACID特性,就必須實現分布式事務。而其實現關鍵則是適當的提交協議,目前最簡潔,且使用最廣泛的無疑是兩階段提交協議(2PC)。
1.實現分布式事務關鍵組件
單機系統通過事務管理器(transaction manager,TM)實現本地事務。分布式系統中,需要協調多個節點的事務管理器,共同提交成功或失敗,因此需要事務協調者(transaction coordinator,TC)。一個分布式事務管理器,可以粗略地劃分為這兩個子系統。這兩個子系統根據自己在事務執行中扮演的角色,也可稱之為參與者與協調者。
本地事務管理器負責本機事務並發控制和異常恢復等功能,事務協調者負責開啟事務,將事務劃分為多個子事務分發到相應的節點執行,並協調事務完成(一起提交成功或失敗)。在實現中,TM和TC可以實現在同一個進程中,也可以部署在不同的節點。
2.經典兩階段提交協議
兩階段提交的流程比較簡單。當分布式事務T執行完成,即事務執行的各節點都告知協調者TC,事務已經執行完成,TC便開啟兩階段提交流程。
Phase 1 Prepare:
1.TC寫本地日誌,並持久化。TC向所有參與者發送PrepareT消息。
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達成一致後,再去通知參與者,當協調者出現異常,可以迅速選出新的協調者,推進事務至完成。