分佈式事務的多種解決方案

需求緣起

在微服務架構中,隨着服務的逐步拆分,數據庫私有已經成爲共識,這也致使所面臨的分佈式事務問題成爲微服務落地過程當中一個很是難以逾越的障礙,可是目前尚沒有一個完整通用的解決方案。html

其實不只僅是在微服務架構中,隨着用戶訪問量的逐漸上漲,數據庫甚至是服務的分片、分區、水平拆分、垂直拆分已經逐漸成爲較爲經常使用的提高瓶頸的解決方案,所以愈來愈多的原子操做變成了跨庫甚至是跨服務的事務操做。最終結果是在對高性能、高擴展性,高可用性的追求的道路上,咱們開始逐漸放鬆對一致性的追求,可是在不少場景下,尤爲是帳務,電商等業務中,不可避免的存在着一致性問題,使得咱們不得不去探尋一種機制,用以在分佈式環境中保證事務的一致性。mysql

引用自 www.infoq.cn/article/201…git

理論基石

ACID 和 BASE

www.infoq.cn/article/201…
www.txlcn.org/zh-cn/docs/…github

2PC

談到分佈式事務,首先要說的就是 2PC(two phase commit)方案,以下圖所示:redis

2PC 把事務的執行分爲兩個階段,第一個階段即 prepare 階段,這個階段實際上就是投票階段,協調者向參與者確認是否能夠共同提交,再獲得所有參與者的全部回答後,協調者向全部的參與者發佈共同提交或者共同回滾的指令,用以保證事務達到一致性。
2PC 是幾乎全部分佈式事務算法的基礎,後續的分佈式事務算法幾乎都由此改進而來。算法

需求樣例

這裏咱們定義一個充值需求,後續咱們在各個實現中看看如何爲該需求實現分佈式事務。spring

Order 和 Account 分別是獨立的一個服務,充值完成後,要分別將訂單Order 設置爲成功以及增長用戶餘額。sql

實現1 Seata

介紹 & 框架

Seata(Fescar) is a distributed transaction solution with high performance and ease of use for microservices architecture.
阿里開源,其特色是用一個事務管理器,來管理每一個服務的事務,本質上是 2PC(後文會解釋) 的一種實現。
Seata 提供了全局的事務管理器。數據庫

原理

Fescar官方介紹
Fescar全局鎖的理解apache

代理 SQL 查詢,實現事務管理,相似中間件。

實現充值需求

用該方案實現需求的話,就是這樣的:

Order 和 Account 都接入 Seata 來代理事務。

代碼示例

比起本身去實現 2PC,Seata 提供了簡化方案,代碼實例見 : Seata Samples

實現2 TCC

介紹

TCC(Try-Confirm-Concel) 模型是一種補償性事務,主要分爲 Try:檢查、保留資源,Confirm:執行事務,Concel:釋放資源三個階段,以下圖所示:

其中,活動管理器記錄了全局事務的推動狀態以及各子事務的執行狀態,負責推動各個子事務共同進行提交或者回滾。同時負責在子事務處理超時後不停重試,重試不成功後轉手工處理,用以保證事務的最終一致性。

原理

每一個子節點,要實現 TCC 接口,才能被管理。
優勢:不依賴 local transaction,能夠管理非關係數據庫庫的服務。
缺點:TCC 模式多增長了一個狀態,致使在業務開發過程當中,複雜度上升,並且協調器與子事務的通訊過程增長,狀態輪轉處理也更爲複雜。並且,不少業務是沒法補償的,例如銀行卡充值。

實現框架

tx-lcn LCN distributed transaction framework, compatible with dubbo, spring cloud and Motan framework, supports various relational databases www.txlcn.org

或者 Seata MT 模式

代碼示例

txlcn-demo

實現充值需求

須要把 Oder.done 和 Account 的餘額+ 操做都實現 tcc 接口。
能夠看出,這樣真的很麻煩,能用本地事務的仍是儘可能用本地事務。

實現3 事務消息

介紹

以購物場景爲例,張三購買物品,帳戶扣款 100 元的同時,須要保證在下游的會員服務中給該帳戶增長 100 積分。
因爲數據庫私有,因此致使在實際的操做過程當中會出現不少問題,好比先發送消息,可能會由於扣款失敗致使帳戶積分無端增長,若是先執行扣款,則有可能因服務宕機,致使積分不能增長,不管是先發消息仍是先執行本地事務,都有可能致使出現數據不一致的結果。

事務消息的本質就是爲了解決此類問題,解決本地事務執行與消息發送的原子性問題。

實現框架

Apache RocketMQ™ is an open source distributed messaging and streaming data platform.

原理

  1. 事務發起方首先發送 prepare 消息到 MQ。
  2. 在發送 prepare 消息成功後執行本地事務。
  3. 根據本地事務執行結果返回 commit 或者是 rollback。
  4. 若是消息是 rollback,MQ 將刪除該 prepare 消息不進行下發,若是是 commit 消息,MQ 將會把這個消息發送給 consumer 端。
  5. 若是執行本地事務過程當中,執行端掛掉,或者超時,MQ 將會不停的詢問其同組的其它 producer 來獲取狀態。
  6. Consumer 端的消費成功機制有 MQ 保證。

優勢:對異步操做支持友好。
缺點:Producer 端要爲 RMQ 實現事務查詢接口,致使在業務開發過程當中,複雜度上升。

代碼示例

// TODO

實現充值需求

經過 MQ,來保障 Order 和 Acount 的兩個操做要麼一塊兒成功,要麼一塊兒失敗。
注意一個點,假設 Account 的餘額+失敗了,這裏是沒法回滾 Order 的操做的,Account 要保證本身能正確處理消息。

實現4 本地消息表

介紹 & 原理

分佈式事務=A系統本地事務 + B系統本地事務 + 消息通知;
準備:
A系統維護一張消息表log1,狀態爲未執行,
B系統維護2張表,
未完成表log2,
已完成表log3,
消息中間件用兩個topic,
topic1是A系統通知B要執行任務了,
topic2是B系統通知A已經完成任務了,

  1. 用戶在A系統裏領取優惠券,並往log1插入一條記錄
  2. 由定時任務輪詢log1,發消息給B系統
  3. B系統收到消息後,先檢查是否在log3中執行過這條消息,沒有的話插入log2表,並進行發短信,發送成功後刪除log2的記錄,插入log3
  4. B系統發消息給A系統
  5. A系統根據id刪除這個消息

假設出現網絡中斷和系統 Crash 等問題時,爲了繼續執行事務,須要進行重試。重試方式有:

  1. 定時任務恢復事務的執行,
  2. 使用 MQ 來傳遞消息,MQ能夠保證消息被正確消費。

優勢:簡單。
缺點:程序會出現執行到一半的狀態,重試則要求每一個操做須要實現冪等性

注意:分佈式系統實現冪等性的時候,記得使用分佈式鎖,分佈式鎖詳細介紹見文末參考文章。

實現充值需求

經過消息表,把斷開的事務繼續執行下去。

總結

咱們先對這些實現方案進行一個總結:

基礎原理 實現 優點 必要前提
2PC Seata 簡單 關係型數據庫
2PC TCC 不依賴關係係數據庫 實現 TCC 接口
最終一致性 事務消息 高性能 實現事務檢查接口
最終一致性 本地消息表 去中心化 侵入業務,接口須要冪等性

各個方案有本身的優劣,實際使用過程當中,咱們仍是須要根據狀況來選擇不一樣事務方案來靈活組合。

例如存在服務模塊A 、B、 C。A模塊是mysql做爲數據源的服務,B模塊是基於redis做爲數據源的服務,C模塊是基於mongo做爲數據源的服務。若須要解決他們的事務一致性就須要針對不一樣的節點採用不一樣的方案,而且統一協調完成分佈式事務的處理。

方案:將A模塊採用 Seata 模式、B/C採用TCC模式就能完美解決。

參考文章

RocketMQ 4.3 正式發佈,支持分佈式事務
Seata
txlcn
分佈式事務 CAP 理解論證 解決方案
再有人問你分佈式鎖,這篇文章扔給他

程序小哥介紹

考拉APP系統開發小哥Nick,專一系統開發和優化。

相關文章
相關標籤/搜索