- Posted by 微博@Yangsc_o
- 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文主要回顧一下分佈式事務的實現方案和基本原理
事務
事務提供一種機制將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下方能提交,只要其中任一操作執行失敗,都將導致整個事務的回滾。簡單地說,事務提供一種“要麼什麼都不做,要麼做全套(All or Nothing)”機制。
數據庫本地事務
- 事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個特性,簡稱爲事務ACID屬性。
事務的ACID是通過InnoDB日誌和鎖來保證。事務的隔離性是通過數據庫鎖的機制實現的,持久性通過redo log(重做日誌)來實現,原子性和一致性通過Undo log來實現。
UndoLog的原理很簡單,爲了滿足事務的原子性,在操作任何數據之前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲UndoLog)。然後進行數據的修改。如果出現了錯誤或者用戶執行了ROLLBACK語句,系統可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態。
和Undo Log相反,RedoLog記錄的是新數據的備份。在事務提交前,只要將RedoLog持久化即可,不需要將數據持久化。當系統崩潰時,雖然數據沒有持久化,但是RedoLog已經持久化。系統可以根據RedoLog的內容,將所有數據恢復到最新的狀態。 對具體實現過程有興趣的同學可以去自行搜索擴展。
更詳細的分析可以看一下我之前的博客《Mysql鎖&事務&MVCC》,說完數據庫事務,接下來分析一下分佈式事務;
分佈式事務
分佈式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分佈式系統的不同節點之上。簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分佈在不同的服務器上,且屬於不同的應用,分佈式事務需要保證這些小操作要麼全部成功,要麼全部失敗。本質上來說,分佈式事務就是爲了保證不同數據庫的數據一致性。
分佈式事務的基礎
CAP
CAP的人都知道,三者不能共有,如果感興趣可以搜索CAP的證明,在分佈式系統中,網絡無法100%可靠,分區其實是一個必然現象,如果我們選擇了CA而放棄了P,那麼當發生分區現象時,爲了保證一致性,這個時候必須拒絕請求,但是A又不允許,所以分佈式系統理論上不可能選擇CA架構,只能選擇CP或者AP架構。
對於CP來說,放棄可用性,追求一致性和分區容錯性,我們的zookeeper其實就是追求的強一致,我的上一篇文章也提高了。
對於AP來說,放棄一致性(這裏說的一致性是強一致性),追求分區容錯性和可用性,這是很多分佈式系統設計時的選擇,例如我們上一篇提到的Eureka,後面的BASE也是根據AP來擴展。
順便一提,CAP理論中是忽略網絡延遲,也就是當事務提交時,從節點A複製到節點B,但是在現實中這個是明顯不可能的,所以總會有一定的時間是不一致。同時CAP中選擇兩個,比如你選擇了CP,並不是叫你放棄A。因爲P出現的概率實在是太小了,大部分的時間你仍然需要保證CA。就算分區出現了你也要爲後來的A做準備,比如通過一些日誌的手段,是其他機器回覆至可用。
BASE
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。是對CAP中AP的一個擴展
- 基本可用:分佈式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。
- 軟狀態:允許系統中存在中間狀態,這個狀態不影響系統可用性,這裏指的是CAP中的不一致。
- 最終一致:最終一致是指經過一段時間後,所有節點數據都將會達到一致。
BASE解決了CAP中理論沒有網絡延遲,在BASE中用軟狀態和最終一致,保證了延遲後的一致性。BASE和 ACID 是相反的,它完全不同於ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。
目前很多系統而言,都是選擇了保證的最終一致性;
方案
是否真的要分佈式事務在說方案之前,首先你一定要明確你是否真的需要分佈式事務?
太多團隊的團隊都是一個人維護幾個微服務,過度設計,搞得人疲勞不堪,而微服務過多就會引出分佈式事務,此時最好還是把微服務聚合成一個單機服務,使用數據庫的本地事務。防止增加系統複雜度,減少成本,不能因爲追求某些設計,而引入不必要的成本和複雜度。
如果確定需要引入分佈式事務可以看看下面幾種常見的方案。
2PC
XA是一個分佈式事務協議,由Tuxedo提出。XA中大致分爲兩部分:事務管理器和本地資源管理器。其中本地資源管理器往往由數據庫實現,比如Oracle、DB2這些商業數據庫都實現了XA接口,而事務管理器作爲全局的調度者,負責各個本地資源的提交和回滾。XA實現分佈式事務的原理如下:
2PC 兩階段提交是一種使分佈式系統中所有節點在進行事務提交時保持一致性而設計的一種協議;在一個分佈式系統中,所有的節點雖然都可以知道自己執行操作後的狀態,但是無法知道其他節點執行操作的狀態,在一個事務跨越多個系統時,就需要引入一個作爲協調者的組件來統一掌控全部的節點並指示這些節點是否把操作結果進行真正的提交,想要在分佈式系統中實現一致性的其他協議都是在兩階段提交的基礎上做的改進。
- 完成:
- 回滾
在XA協議中分爲兩階段:
第一階段:事務管理器要求每個涉及到事務的數據庫預提交(precommit)此操作,並反映是否可以提交.
第二階段:事務協調器要求每個數據庫提交數據,或者回滾數據。
優點: 儘量保證了數據的強一致,實現成本較低,在各大主流數據庫都有自己實現,對於MySQL是從5.5開始支持。
缺點:
- 單點問題:事務管理器在整個流程中扮演的角色很關鍵,如果其宕機,比如在第一階段已經完成,在第二階段正準備提交的時候事務管理器宕機,資源管理器就會一直阻塞,導致數據庫無法使用。
- 同步阻塞:在準備就緒之後,資源管理器中的資源一直處於阻塞,直到提交完成,釋放資源。
- 數據不一致:兩階段提交協議雖然爲分佈式數據強一致性所設計,但仍然存在數據不一致性的可能,比如在第二階段中,假設協調者發出了事務commit的通知,但是因爲網絡問題該通知僅被一部分參與者所收到並執行了commit操作,其餘的參與者則因爲沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。
總的來說,XA協議比較簡單,成本較低,但是其單點問題,以及不能支持高併發(由於同步阻塞)依然是其最大的弱點。
3PC
三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。
與兩階段提交不同的是,三階段提交有兩個改動點。
-
引入超時機制。同時在協調者和參與者中都引入超時機制。
-
在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參與節點的狀態是一致的。
也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit
、PreCommit
、DoCommit
三個階段。
相對於2PC,3PC主要解決的單點故障問題,並減少阻塞,因爲一旦參與者無法及時收到來自協調者的信息之後,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。但是這種機制也會導致數據一致性問題,因爲,由於網絡原因,協調者發送的abort響應沒有及時被參與者接收到,那麼參與者在等待超時之後執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。
TCC
Try: 嘗試執行業務
• 完成所有業務檢查(一致性)
• 預留必須業務資源(準隔離性)
Confirm:確認執行業務
• 真正執行業務
• 不作任何業務檢查
• 只使用Try階段預留的業務資源
• Confirm操作要滿足冪等性
Cancel: 取消執行業務
• 釋放Try階段預留的業務資源
• Cancel操作要滿足冪等性
我們可以從阿里巴巴開源的Seata簡單探究其原理,Seata管理的分佈式事務的典型生命週期
- TM要求TC開始一項新的全局事務。 TC生成代表全局交易的XID。
- XID通過微服務的調用鏈傳播。
- RM將本地事務註冊爲XID到TC的相應全局事務的分支。
- TM要求TC提交或回退相應的XID全局事務。
- TC驅動XID的相應全局事務下的所有分支事務以完成分支提交或回滾。
本地消息表
本地消息表這個方案最初是ebay提出的 ebay的完整方案https://queue.acm.org/detail.cfm?id=1394128。
此方案的核心是將需要分佈式處理的任務通過消息日誌的方式來異步執行。消息日誌可以存儲到本地文本、數據庫或消息隊列,再通過業務規則自動或人工發起重試。人工重試更多的是應用於支付場景,通過對賬系統對事後問題的處理。
本地消息隊列是BASE理論,是最終一致模型,適用於對一致性要求不高的。實現這個模型時需要注意重試的冪等。
RocketMQ事務
在RocketMQ中實現了分佈式事務,實際上其實是對本地消息表的一個封裝,將本地消息表移動到了MQ內部,下面簡單介紹一下MQ事務
-
A服務先發送個Half Message給Brock端,消息中攜帶 B服務;
-
當A服務知道Half Message發送成功後,那麼開始第3步執行本地事務。
-
執行本地事務(會有三種情況1、執行成功。2、執行失敗。3、網絡等原因導致沒有響應)
-
如果本地事務成功,那麼Product像Brock服務器發送Commit,這樣B服務就可以消費該message。
如果本地事務失敗,那麼Product像Brock服務器發送Rollback,那麼就會直接刪除上面這條半消息。
如果因爲網絡等原因遲遲沒有返回失敗還是成功,那麼會執行RocketMQ的回調接口,來進行事務的回查。
Saga事務
兩階段提交其實可以保證事務的強一致性,但是在很多業務場景下,我們其實只需要保證業務的最終一致性,在一定的時間窗口內,多個系統中的數據不一致是可以接受的,在過了時間窗口之後,所有系統都會返回一致的結果。
Saga 其實就一種簡化的分佈式事務解決方案,它將一系列的分佈式操作轉化成了一系列的本地事務,在每一個本地事務中我們都會更新數據庫並且向集羣中的其他服務發送一條的新的消息來觸發下一個本地的事務;一旦本地的事務因爲違反了業務邏輯而失敗,那麼就會立刻觸發一系列的回滾操作來撤回之前本地事務造成的副作用。
在此不在詳細贅述;
總結
上面只是一些解決方案簡單介紹,如果真正的想要落地,其實每種方案需要思考的地方都非常多,複雜度都比較大,所以最後再次提醒一定要判斷好是否使用分佈式事務。我們調研了阿里的Seata,但還未落地,大多數場景我們還是使用MQ選擇了最終一致性。
參考
DTP模型之一:(XA協議之一)XA協議、二階段2PC、三階段3PC提交