分散式事務的多種解決方案
需求緣起
在微服務架構中,隨著服務的逐步拆分,資料庫私有已經成為共識,這也導致所面臨的分散式事務問題成為微服務落地過程中一個非常難以逾越的障礙,但是目前尚沒有一個完整通用的解決方案。
其實不僅僅是在微服務架構中,隨著使用者訪問量的逐漸上漲,資料庫甚至是服務的分片、分割槽、水平拆分、垂直拆分已經逐漸成為較為常用的提升瓶頸的解決方案,因此越來越多的原子操作變成了跨庫甚至是跨服務的事務操作。最終結果是在對高效能、高擴充套件性,高可用性的追求的道路上,我們開始逐漸放鬆對一致性的追求,但是在很多場景下,尤其是賬務,電商等業務中,不可避免的存在著一致性問題,使得我們不得不去探尋一種機制,用以在分散式環境中保證事務的一致性。
引用自 https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
理論基石
ACID 和 BASE
見 https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
見 https://www.txlcn.org/zh-cn/docs/preface.html
2PC
談到分散式事務,首先要說的就是 2PC(two phase commit)方案,如下圖所示:
2PC 把事務的執行分為兩個階段,第一個階段即 prepare 階段,這個階段實際上就是投票階段,協調者向參與者確認是否可以共同提交,再得到全部參與者的所有回答後,協調者向所有的參與者釋出共同提交或者共同回滾的指令,用以保證事務達到一致性。
需求樣例
這裡我們定義一個充值需求,後續我們在各個實現中看看如何為該需求實現分散式事務。
Order 和 Account 分別是獨立的一個服務,充值完成後,要分別將訂單Order 設定為成功以及增加使用者餘額。
實現1 Seata
介紹 & 框架
Seata(Fescar) is a distributed transaction solution with high performance and ease of use for microservices architecture.
阿里開源,其特點是用一個事務管理器,來管理每個服務的事務,本質上是 2PC(後文會解釋) 的一種實現。
原理
代理 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 https://www.txlcn.org
或者 Seata MT 模式
程式碼示例
實現充值需求
需要把 Oder.done 和 Account 的餘額+ 操作都實現 tcc 介面。
可以看出,這樣真的很麻煩,能用本地事務的還是儘量用本地事務。
實現3 事務訊息
介紹
以購物場景為例,張三購買物品,賬戶扣款 100 元的同時,需要保證在下游的會員服務中給該賬戶增加 100 積分。
由於資料庫私有,所以導致在實際的操作過程中會出現很多問題,比如先發送訊息,可能會因為扣款失敗導致賬戶積分無故增加,如果先執行扣款,則有可能因服務宕機,導致積分不能增加,無論是先發訊息還是先執行本地事務,都有可能導致出現數據不一致的結果。
事務訊息的本質就是為了解決此類問題,解決本地事務執行與訊息傳送的原子性問題。
實現框架
Apache RocketMQ™ is an open source distributed messaging and streaming data platform.
原理
- 事務發起方首先發送 prepare 訊息到 MQ。
- 在傳送 prepare 訊息成功後執行本地事務。
- 根據本地事務執行結果返回 commit 或者是 rollback。
- 如果訊息是 rollback,MQ 將刪除該 prepare 訊息不進行下發,如果是 commit 訊息,MQ 將會把這個訊息傳送給 consumer 端。
- 如果執行本地事務過程中,執行端掛掉,或者超時,MQ 將會不停的詢問其同組的其它 producer 來獲取狀態。
- 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已經完成任務了,
- 使用者在A系統裡領取優惠券,並往log1插入一條記錄
- 由定時任務輪詢log1,發訊息給B系統
- B系統收到訊息後,先檢查是否在log3中執行過這條訊息,沒有的話插入log2表,並進行發簡訊,傳送成功後刪除log2的記錄,插入log3
- B系統發訊息給A系統
- A系統根據id刪除這個訊息
假設出現網路中斷和系統 Crash 等問題時,為了繼續執行事務,需要進行重試。重試方式有:
- 定時任務恢復事務的執行,
- 使用 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,專注系