4.2 Two-Phase-Commit

Two-Phase-Commit

在分布式系統中, 每個節點雖然可以知道自己的操作是成功還是失敗, 卻無法知道其它的節點是成功還是失敗. 當一個transaction跨越了多個節點的時候, 為了保持transaction的ACID特性, 需要引入一個作為協調者(Coordinator)的元件來統一掌握所有節點(或稱參與者, Cohort)的操作結果並最終指示這些節點是否要把操作結果真正的進行commit的動作.

再來, 看一下兩階段各有什麼事情要做:

  • 第一階段

    1. 協調者會問所有參與者, 是否可以執行commit操作了

    2. 各個參與者開始transaction執行的準備工作, 如: lock resources, 預留resources, 寫undo/redo log...等

    3. 參與者回覆協調者, 如果transaction的準備工作成功, 則回應"可以commit", 否則回應"拒絕commit"

  • 第二階段

    • 如果所有的參與者都回"可以commit", 那麼協調者就向所有的參與者發送"正式commit"的命令. 參與者完成正式commit, 並釋放所有資源, 然後回應"完成", 協調者收集各個節點的"完成"回應後結束這個global transaction

    • 如果有任何一個參與者回覆"拒絕commit", 那麼協調者向所有的參與者發送"rollback操作", 則參與者們釋放所有資源, 然後回應"rollback完成", 協調者收集各節點的"rollback"回應後, 取消這個global transaction

所以你可以看到, 2PC其實就是種第一階段投票, 第二階段做決定的演算法, 且基本上是強一致性的. 這個比Master-Slave的強一致性策略還要更保守一些(先嘗試再commit). 在一些系統設計中, 會串連一系列的呼叫, 譬如: A->B->C->D, 每一步都會分配一些資源或改寫一些資料. 舉個例子, 在B2C網站購物的下單操作在後端會有一系列的流程要跑. 如果一步一步的作, 就會出現這樣的問題, 如某一步做不下去了, 那麼前面每一次所分配的資源需要作反向操作把它們都回收掉, 所以操作起來比較複雜. 現在很多處理流程(workflow)都會借鑑2PC這個演算法, 使用try->confirm的流程來確保整個流程能夠成功完成.

另外, 在這段過程中我們也可以看到一些其它的問題:

  • Synchronous blocking operation: 這很直觀, 畢竟從名稱一看就知道會大幅影響性能

  • Timeout, 大概有以下幾種情境:

    1. 如果第一階段中, 參與者沒有收到詢問請求, 或是參與者的回應沒有送到協調者那邊. 那麼協調者要針對timeout做處理, 一但timeout, 可以當作失敗, 也可以retry.

    2. 如果第二階段中, 正式commit發出後, 若有的參與者沒有收到, 或是參與者commit/rollback後的確認訊息沒有返回, 一但參與者的回應timeout了, 要就retry, 不然就把那個參與者標記為問題節點踢出整個集群, 這樣可以保證服務節點都是資料一致性的.

    3. 糟糕的情形是, 第二階段中, 如果參與者收不到協調者的commit/rollback指令, 則參與者將會處於"狀態未知"的階段, 參與者完全不知道要怎麼辦, 比如: 若所有的參與者完成第一階段的回覆後 (可能全yes/全no/部分yes部分no), 但協調者這時候卻掛掉了, 那所有的參與者都會完全不知道該怎麼辦(不可以問別的參與者). 若出了這種問題, 為了一致性, 要就死等協調者, 不然就重發第一階段的yes/no命令.

對2PC來說, timeout的第三種情形應該就是最大的問題了, 如果第一階段完成後, 參與者在第二階段沒有收到決策, 那資料節點就會進入一種不知所措的狀態, 這個狀態會block住整個transaction. 即是說, 協調者對於transaction的完成非常重要, 協調者的可用性是關鍵點. 因此, 出現了三階段提交(Three-Phase-Commit, 3PC), 這邊直接copy維基百科的圖, 其把2PC的第一階段分成兩段: 先詢問, 然後再鎖資源. 最後才真的commit.

3PC的核心理念是: 在詢問的時候並不鎖定資源, 除非所有人都同意了, 才開始鎖資源.

理論上來說, 若第一階段所有的節點回傳成功, 那麼有理由相信成功commit的機率很大. 如此可以降低參與者的狀態未知的機率. 也就是說, 一但參與者收到了precommit, 意味著它知道大家其實都同意修改了, 這是很重要的一點. 下面這張圖是3PC的狀態轉換圖(圖中的虛線F為Failuer, T為Timeout, Q是Query, a為Abort, w-Wait, p-PreCommit, c-Commit):

從上圖的狀態變化圖可以從虛線看到, 如果結果處在P狀態(PreCommit)的時候發生了F/T的問題, 3PC比2PC好的地方是: 3PC可以直接繼續把狀態變成C(Commit), 但2PC會不知所措.

其實3PC是一個很複雜的東西, 實作起來除了難之外, 也會有一些問題.

看到這邊不要說你, 我自己也有很多的問題, 譬如說思考2PC/3PC中各種各樣的失敗場景, 其實你會發現timeout是個很難處理的東西, 因為網路上的timeout在很多時候讓你無所適從, 你也不知道對方是做了還是沒做. 然後好好的一個狀態機就因為timeout成了個擺設.

一個網路服務會有三種狀態:

  • Success

  • Failure

  • Timeout

我說第三個絕對是惡夢, 尤其在需要維護狀態的時候.

這篇文章是從這裡看來的, 我還會再整理過

Last updated