由於分散式系統的各個服務可能分佈在不同的節點上,如果各節點直接沒有相互的通訊獲取其他節點狀態,那麼各個節點是無法知道其他節點的任務處理結果的。
如果在分散式系統中發起一個事務,該事務涉及多個不同節點,那麼為了保證事務 ACID 特性,就需要引入一個協調者來統一排程事務涉及的多個節點,被排程的節點稱為事務參與者。由此衍生出 2PC 和 3PC 協議,本文就來詳細介紹 2PC 和 3PC 的工作機制。
2PC(兩階段提交,Two-Phase Commit)
顧名思義,分為兩個階段:Prepare 和 Commit
Prepare:提交事務請求
基本流程如下圖:
-
詢問 協調者向所有參與者傳送事務請求,詢問是否可執行事務操作,然後等待各個參與者的響應。
-
執行 各個參與者接收到協調者事務請求後,執行事務操作(例如更新一個關係型資料庫表中的記錄),並將 Undo 和 Redo 資訊記錄事務日誌中。
-
響應 如果參與者成功執行了事務並寫入 Undo 和 Redo 資訊,則向協調者返回 YES 響應,否則返回 NO 響應。當然,參與者也可能當機,從而不會返回響應。
Commit:執行事務提交
執行事務提交分為兩種情況,正常提交和回退。
正常提交事務
流程如下圖:
-
commit 請求 協調者向所有參與者傳送 Commit 請求。
-
事務提交 參與者收到 Commit 請求後,執行事務提交,提交完成後釋放事務執行期佔用的所有資源。
-
反饋結果 參與者執行事務提交後向協調者傳送 Ack 響應。
-
完成事務 接收到所有參與者的 Ack 響應後,完成事務提交。
中斷事務
在執行 Prepare 步驟過程中,如果某些參與者執行事務失敗、當機或與協調者之間的網路中斷,那麼協調者就無法收到所有參與者的 YES 響應,或者某個參與者返回了 No 響應,此時,協調者就會進入回退流程,對事務進行回退。流程如下圖紅色部分(將 Commit 請求替換為紅色的 Rollback 請求):
-
rollback 請求 協調者向所有參與者傳送 Rollback 請求。
-
事務回滾 參與者收到 Rollback 後,使用 Prepare 階段的 Undo 日誌執行事務回滾,完成後釋放事務執行期佔用的所有資源。
-
反饋結果 參與者執行事務回滾後向協調者傳送 Ack 響應。
-
中斷事務 接收到所有參與者的 Ack 響應後,完成事務中斷。
2PC 的問題
-
同步阻塞 參與者在等待協調者的指令時,其實是在等待其他參與者的響應,在此過程中,參與者是無法進行其他操作的,也就是阻塞了其執行。 倘若參與者與協調者之間網路異常導致參與者一直收不到協調者資訊,那麼會導致參與者一直阻塞下去。
-
單點 在 2PC 中,一切請求都來自協調者,所以協調者的地位是至關重要的,如果協調者當機,那麼就會使參與者一直阻塞並一直佔用事務資源。
如果協調者也是分散式,使用選主方式提供服務,那麼在一個協調者掛掉後,可以選取另一個協調者繼續後續的服務,可以解決單點問題。但是,新協調者無法知道上一個事務的全部狀態資訊(例如已等待 Prepare 響應的時長等),所以也無法順利處理上一個事務。
-
資料不一致 Commit 事務過程中 Commit 請求/Rollback 請求可能因為協調者當機或協調者與參與者網路問題丟失,那麼就導致了部分參與者沒有收到 Commit/Rollback 請求,而其他參與者則正常收到執行了 Commit/Rollback 操作,沒有收到請求的參與者則繼續阻塞。這時,參與者之間的資料就不再一致了。
當參與者執行 Commit/Rollback 後會向協調者傳送 Ack,然而協調者不論是否收到所有的參與者的 Ack,該事務也不會再有其他補救措施了,協調者能做的也就是等待超時後像事務發起者返回一個“我不確定該事務是否成功”。
-
環境可靠性依賴 協調者 Prepare 請求發出後,等待響應,然而如果有參與者當機或與協調者之間的網路中斷,都會導致協調者無法收到所有參與者的響應,那麼在 2PC 中,協調者會等待一定時間,然後超時後,會觸發事務中斷,在這個過程中,協調者和所有其他參與者都是出於阻塞的。這種機制對網路問題常見的現實環境來說太苛刻了。
3PC(三階段提交,Three-Phase Commit)
上面說明了 2PC 協議的多個缺點,那麼 3PC 就是在 2PC 的基礎上,為了解決 2PC 的某些缺點而設計的,3PC 分為三個階段:CanCommit,PreCommit 和 doCommit。
CanCommit
流程如下圖:
-
事務詢問 協調者向所有參與者傳送事務 canCommit 請求,請求中包含事務內容,詢問是否可以執行事務提交操作,並開始等待響應。
-
反饋詢問結果 參與者收到 canCommit 請求後,分析事務內容,判斷自身是否可以執行事務,如果可以,那麼就返回 Yes 響應,進入預備狀態,否則返回 No 響應。
注意:此過程中並沒有執行事務(對比 2PC 的 Prepare 階段,參與者是執行了事務的)。
PreCommit
流程圖如下:
PreCommit 階段根據各參與者返回的 CanCommit 響應,決定下一步動作。如果收到了所有參與者的 Yes 響應,則執行事務預提交,否則(收到了至少一個 No 響應或一定時長內沒有收到所有參與者的 Yes 響應,如 3PC 第一張圖片中紅色部分),執行事務中斷。
事務預提交
-
傳送 PreCommit 請求 協調者傳送 PreCommit 請求,並進入 Prepared 階段。
-
參與者處理 PreCommit 參與者收到 PreCommit 請求後,執行事務操作,並將 Undo 和 Redo 資訊記錄事務日誌中。
-
反饋執行結果 如果參與者成功執行了事務並寫入 Undo 和 Redo 資訊,那麼反饋 Ack 給協調者,並等待下一步指令。
事務中斷
上圖中,紅色的 Abort 表示協調者傳送的不是 PreCommit 請求,而是 Abort 請求。
-
傳送事務中斷請求 協調者向所有參與者傳送 Abort 請求。
-
中斷事務 參與者收到 Abort 請求後,會觸發事務中斷。此外,如果參與者在等待協調者指令超時,會自己觸發事務中斷,在 2PC 中,參與者會一直阻塞的等待協調者指令,所以 3PC 中解決了因為這種情況帶來的阻塞。
doCommit
流程圖如下:
協調者根據第二階段的響應決定最終操作,如果協調者收到了所有參與者在 PreCommit 階段的 Ack 響應,那麼會進入執行事務提交階段,否則執行事務中斷。
事務提交
-
傳送提交請求 協調者收到所有參與者在 PreCommit 階段返回的 Ack 響應後,向所有參與者傳送 doCommit 請求,並進入提交狀態。
-
事務提交 參與者收到 Commit 請求後,執行事務提交,提交完成後釋放事務執行期佔用的所有資源。
-
反饋結果 參與者完成事務提交之後,向協調者返回 Ack 響應。
-
完成事務 協調者收到所有參與者的 Ack 響應後,完成事務。
事務中斷
-
傳送事務中斷請求 協調者向所有參與者傳送 Abort 請求。
-
事務回滾 參與者收到 Abort 請求後,會使用第二階段記錄的 Undo 資訊進行事務回滾,並在完成回滾後釋放所有事務資源。
注意:因為第一階段並沒有任何參與者實際執行事務,所以在第二階段(PreCommit 階段)執行事務中斷,是不需要事務回滾的,也就不需要下面的反饋結果,直接中斷事務即可。
-
反饋回滾結果 參與者執行事務回滾後向協調者傳送 Ack 響應。
-
中斷事務 協調者接收到所有參與者反饋的 Ack 響應後,完成事務中斷。
3PC 的改進和缺點
改進
-
降低了阻塞
-
參與者返回 CanCommit 請求的響應後,等待第二階段指令,若等待超時,則自動 abort,降低了阻塞;
-
參與者返回 PreCommit 請求的響應後,等待第三階段指令,若等待超時,則自動 commit 事務,也降低了阻塞;
-
-
解決單點故障問題
-
參與者返回 CanCommit 請求的響應後,等待第二階段指令,若協調者當機,等待超時後自動 abort,;
-
參與者返回 PreCommit 請求的響應後,等待第三階段指令,若協調者當機,等待超時後自動 commit 事務;
-
缺點
資料不一致問題仍然是存在的,比如第三階段協調者發出了 abort 請求,然後有些參與者沒有收到 abort,那麼就會自動 commit,造成資料不一致。
總結
從上面講述來看,2PC 和 3PC 都無法完美解決分散式資料一致性問題,雖然無法保證事務的 ACID 特性,但兩階段的思想在很多實際架構中有這廣泛應用,例如 JTA 事務以及一些資料庫的資料同步。
引用一句話,是 Google Chubby 作者說的:
“There is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos.”
Paxos 是蘭伯特提出的一個解決分散式一致性的演算法,後面再寫文講述其原理。