Davids原理探究:分佈式事務(2PC、3PC、TCC、基於消息達到最終一致性)

分佈式事務(2PC、3PC、TCC、基於消息達到最終一致性)

二階段提交(2PC)

  • 第一階段:請求/表決階段
    • 在分佈式事務發起者向分佈式事務協調者發送請求的時候,事務協調者向所有參與者發送事務預處理請求(vote request)
    • 這個時候參與者會開啓本地事務並開始執行本地事務,執行完成後不會commit,而是向事務協調者報告是否可以處理本次事務
  • 第二階段:提交/執行/回滾階段
    • 分佈式事務協調者收到所有參與者反饋後,所有參與者節點均響應可以提交,則通知參與者和發起者執行commit,否則rollback

三點常見問題:

  1. 性能問題:從流程上面可以看出,最大的缺點就是在執行過程中節點都處於阻塞狀態。各個操作數據庫的節點都佔用着數據庫資源,只有當所有節點準備完畢,事務協調者纔會通知進行全局commit/rollback,參與者進行本地事務commit/rollback之後纔會釋放資源,對性能影響較大。
  2. 單點故障問題:事務協調者是整個分佈式事務的核心,一旦事務協調者出現故障,會導致參與者收不到commit/rollback的通知,從而導致參與者節點一直處於事務無法完成的中間狀態。
  3. 消息丟失問題:在第二階段的時候,如果發生局部網絡問題,一部分事務參與者收不到commit/rollback消息,那麼就會導致節點間數據不一致。

三階段提交(3PC)

在2PC的基礎上增加了CanCommit階段,並引入了超時機制。一旦事務參與者指定時間沒有收到協調者的commit/rollback指令,就會自動本地commit,這樣可以解決協調者單點故障的問題。

  • CanCommit階段(提交詢問)
    • 分佈式事務協調者詢問所有參與者是否可以進行事務操作,參與者根據自身健康情況,是否可以執行事務操作響應Y/N。
  • PreCommit階段(預提交)
    • 如果參與者返回的都是同意,協調者則向所有參與者發送預提交請求,並進入prepared階段。
    • 參與者收到預提交請求後,執行事務操作,並保存Undo和Redo信息到事務日誌中。
    • 參與者執行完本地事務之後(uncommitted),會向協調者發出Ack表示已準備好提交,並等待協調者下一步指令。
    • 如果協調者收到預提交響應爲拒絕或者超時,則執行中斷事務操作,通知各參與者中斷事務(abort)。
    • 參與者收到中斷事務(abort)或者等待超時,都會主動中斷事務/直接提交。(超時情況下國內很多博客講的是直接中斷,但是wiki說的是直接提交,這裏中斷或提交都存在不確定性,都只有一半的概率做對,可能造成不一致情況,需要看不同框架的實現。此處需要TODO一下)

If, after a cohort member receives a preCommit message, the coordinator fails or times out, the cohort member goes forward with the commit.

  • doCommit階段(最終提交)
    • 協調者收到所有參與者的Ack,則從預提交進入提交階段,並向各參與者發送提交請求。
    • 參與者收到提交請求,正式提交事務(commit),並向協調者反饋提交結果Y/N。
    • 協調者收到所有反饋消息,完成分佈式事務。(參與者包含事務發起者,比如A調用B,其實AB都參與了分佈式事務)
    • 如果協調者超時沒有收到反饋,則發送中斷事務指令(abort)。
    • 參與者收到中斷事務指令後,利用事務日誌進行rollback。
    • 參與者反饋回滾結果,協調者接收反饋結果或者超時,完成中斷事務。

TCC(Try-Confirm-Cancel)

  • Try
    • 做業務檢查及資源預留(比如凍結庫存,而不是直接減庫存)。
  • Confirm
    • 確認提交,在Try階段所有事務參與者執行成功之後開始執行Confirm,通常情況下,TCC默認Confirm是不會出錯的,認爲只要Try成功,則Confirm一定成功,若Confirm真的出錯了,需要採用重試機制或者人工干預。
  • Cancel
    • 執行回滾,在Try階段有事務參與者執行失敗則開始執行Cancel,通常情況下,TCC默認Cancel是不會出錯的,認爲只要Try成功,則Cancel一定成功,若Cancel真的出錯了,需要採用重試機制或者人工干預。

優缺點:

  1. 優點:解決了性能問題,不阻塞,不佔用數據庫資源。
  2. 缺點:代碼入侵強,每個事務都需要實現try,confirm和cancel,還需要保證接口冪等性,開發、維護成本高。

RocketMQ基於消息達到最終一致性

RocketMQ事務消息流程圖(圖片來自阿里雲):RocketMQ事務消息流程圖
RocketMQ事務消息共有三種狀態,提交狀態、回滾狀態和中間狀態。

  1. TransactionStatus.CommitTransaction:提交事務,它允許消費者消費此消息。
  2. TransactionStatus.RollbackTransaction:回滾事務,它代表該消息將被刪除,不允許被消費。
  3. TransactionStatus.Unknown:中間狀態,它代表需要檢查消息隊列來確定狀態。

執行流程:

  1. 消息發送方開啓事務,發送半事務消息到RocketMQ,但是該消息只保存在commitlog中,對消費者是不可見的,沒有保存到customerQueue中。
  2. 消息發送方處理完本次事務之後,進入第二階段。
    1. 如果成功則發送commit確認消息到RocketMQ將半事務消息保存到customerQueue中,讓customer進行消費。
    2. 如果失敗則發送rollback消息到RocketMQ將半事務消息刪除。

異常分析:

  1. 預備消息發送失敗。(流程會中斷,所以無影響)
  2. 預備消息發送成功,但是本地事務執行失敗。(預備消息沒有進入customerQueue,不會被消費到,所以無影響)
  3. 預備消息發送成功,本地事務執行成功,但是發送確認消息失敗,導致消息不能進入customerQueue,消費者無法消費。(解決方案:消息回查機制)

消息回查機制:

  1. RocketMQ會定時檢查commitlog中的預備消息,並回查本地業務(實現LocalTransactionChecker接口的check方法)。
  2. RocketMQ會根據回查狀態決定commit到customerQueue還是rollback刪除消息(解決異常2和異常3)。
  3. 爲了降低代碼入侵和判斷的複雜度,可以單獨設計一張事務(Transaction)表與具體業務解耦,會查的時候根據事務表的狀態進行查詢即可。

保障消費冪等性:

  1. RocketMQ中Message ID有可能出現衝突,所以建議使用業務唯一標識最爲冪等性處理的依據。
  2. 在消費的時候使用redis判斷消息是否消費過。

總結

強一致性分佈式事務代碼入侵較低,但是會阻塞,佔用資源,影響性能;TCC代碼和業務入侵較大;弱一致性事務異步操作就會涉及到異常情況下的回滾重試,回滾失敗等。所以最後還是需要從自身業務情況觸發來進行選擇,以下是目前主流分佈式事務實現(排名不分先後)。

  1. seata
  2. ByteTCC
  3. spring-cloud-rest-tcc
  4. hmily
  5. EasyTransaction
  6. tcc-transaction
  7. RocketMQ
    1. 阿里雲RocketMQ收發事務消息
    2. 消息隊列 RocketMQ 版與自建開源 RocketMQ 成本對比
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章