1. 程式人生 > >分布式事務入門

分布式事務入門

邏輯 行處理 個數 mit oracl 日誌文件 也不會 時間段 提交

今天和大佬們討論框架和技術時提到了事務,然後自己一直都是做的單體應用的事務,比如使用Spring Boot中的@Transactional註解做事務處理,對於分布式事務完全沒有了解過。又一次發現自己菜得很,所以趕緊學習一下,盡量縮小一下與大佬們的差距。

事務(Transaction)

事務提供一種機制,將一個活動涉及的所有操作納入到一個不可分割的執行單元,只有在活動內的所有操作均能正常執行的情況下,才算完成了活動,否則只要有一個操作執行失敗,活動就算失敗。這裏的活動就是事務,只要事務中有任一操作失敗,就會導致整個事務的回滾。簡單理解,就是一種"要麽什麽都不做,要麽做全套(All or Nothing)"的機制。

舉個例子,我微信給靜靜轉賬,這時就有兩個操作,操作一是我的微信支出200,操作二是靜靜的微信收入200,這兩個操作就組成了一個事務:要麽都執行成功,要麽都執行失敗。好好想想,要是操作一成功了,操作二失敗了,我豈不是要白白損失了200?事務就是為了避免我損失這200,而誕生的解決方案(手動滑稽)。

數據庫本地事務(DataBase Local Transaction)

數據庫本地事務通常理解也就是我前面提到的單體應用的事務。

數據庫事務有四個特性(ACID),即原子性、一致性、隔離性和持久性。

原子性(Atomicity):一個事務中的所有操作,要麽全部完成,要麽全部不完成。如果事務在執行的過程中發生錯誤,就會回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。例子就是前面微信給靜靜轉賬的例子。

一致性(Consistency):一個事務執行之前和執行之後,數據都必須處在一致性狀態。即事務在完成的時候,所有的數據都要發生改變,以保證數據的完整性。我的微信上減少了200,靜靜的微信也要相應增加200。

隔離性(Isolation):在並發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。由並發事務所做的修改必須與任何其他並發事務所做的修改隔離。事務查看數據更新時,數據所處的狀態要麽是在另一個事務修改它之前的狀態,要麽是另一個事務修改它之後的狀態,事務不會查看到中間狀態的數據。還是微信給靜靜轉賬的例子,我分兩次給靜靜轉200,一次轉100,我轉賬是一個事務,靜靜查看是一個事務,靜靜查看微信查看到的是最終的200,而不會看到中間的100。

持久性(Durability):事務只要成功結束(提交),數據庫的變化就永久保存下來。我給靜靜轉賬200這件事情永久保存在微信的記錄上了。

數據庫本地事務通常是使用數據庫的日誌和鎖來實現的,而事務的核心其實就是為了處理異常情況,比如數據庫在提交事務的時候突然斷電。這裏就使用SQL Server來舉例,講講數據庫在突然斷電的情況下是怎麽保證數據的一致性的。我們知道SQL Server數據庫是由兩個文件組成的,一個數據庫文件和一個日誌文件,在通常情況下,日誌文件都要比數據庫文件大很多。數據庫進行任何寫入操作的時候都是要先寫日誌的,同樣的道理,我們在執行事務的時候,數據庫會先首先記錄下這個事務的redo操作日誌,然後才開始真正操作數據庫,在操作之前首先會把日誌文件寫入磁盤,那麽當突然斷電的時候,即使操作沒有完成,在重新啟動數據庫的時候,數據庫會根據當前數據的情況進行undo回滾或redo前滾,這樣就保證了數據的強一致性。

分布式理論

分布式系統的核心同樣是處理各種異常情況,這也是分布式系統復雜的地方,因為分布式的網絡環境很復雜,類似"斷電"的故障會比單機多很多,所以我們在做分布式系統的時候,最先考慮的就是這種情況。這些異常可能由機器宕機、網絡異常、消息丟失、消息亂序、數據錯誤、不可靠的TCP、存儲數據丟失、其他異常等等。

當我們的單個數據庫的性能產生瓶頸的時候,我們可能會對數據庫進行分區,這裏所說的分區指的是物理分區,分區之後可能不同的庫就處於不同的服務器上了,這個時候單個數據庫庫的ACID已經不能適應這種情況了,而在這種集群情況下,再想保證集群的ACID幾乎很難達到,或者即使能達到效率和性能也會大幅下降,使得我們的系統變得很差。這時我們就需要引入一個新的理論來適應這種集群的情況,就是CAP定理(或者叫CAP原則)。

CAP定理

CAP定理又被叫做布魯爾定理,對於設計分布式系統(不僅僅是分布式事務)的架構師來說,CAP就是一個入門的基礎理論。這個入門的基礎理論是由加州大學伯克利分校的Eric Brewer教授提出來的,他提出WEB服務無法同時滿足以下3個屬性:

一致性(Consistency):客戶端知道一系列的操作都會同時發生(生效)。對於某個指定的客戶端來說,讀操作能返回最新的寫操作的數據。也就是說,對於數據分布在不同節點上的數據來說,如果在某個節點更新(寫)了數據,那麽在其他節點如果都能讀取到這個最新的數據,那麽就稱為強一致,如果在某個節點沒有讀取到,那麽就是分布不一致。

可用性(Availability):每個操作都必須以預期的響應結束。非故障的節點必須在合理的時間內返回合理的響應(不是錯誤和超時的響應)。兩個關鍵點,一個是合理的時間,一個是合理的響應。合理的時間指的是請求不能無限被阻塞,應該在合理的時間給出返回。合理的響應指的是系統應該明確返回結果並且結果是正確的。這裏的正確指的是比如應該返回200,而不是返回20。

分區容忍/容錯性(Partition Tolerance):即使出現單個組件無法可用,操作仍然可以完成。當出現網絡分區後,系統能夠繼續工作。比如集群中由多臺機器,其中某臺機器網絡出現了問題,但是這個集群仍然可以正常工作。

在分布式系統中,在任何數據庫設計中,一個WEB應用最多只能同時支持上面的兩個屬性。但是因為網絡無法100%可靠,且任何橫向擴展策略都要依賴於數據分區,實際的分布式場景中,分區一定是要存在的,即必須要有分區容忍性。因此,CAP理論說是三選二,實際上就是二選一,設計人員必須在一致性和可用性之間做出選擇。

CAP理論原本應該是產生三種組合:CA,CP和AP。如果我們選擇了CA而放棄了P分區容錯性,那麽當發生分區現象的時候,為了保證C一致性,就必須拒絕請求,但是這樣又不符合A可用性,所以分布式系統理論上不可能選擇CA,除非應用在一個用不會通信故障的網絡中(理想)。這樣,就只有兩種組合,一種是CP,一種是AP。

對於CP來說,放棄了A可用性,當節點間不可通信時,進行阻塞,直到通信恢復,期間無法再對外提供服務,用戶體驗不好。還是我微信給靜靜轉賬的例子,只有當我微信扣款成功並且靜靜微信收款成功,整個事務才算完成,顯然耗費資源。

對於AP來說,放棄了C強一致性,具體實現是一致性的延遲。給用戶一個可以忍受的時間段,在這個時間內達到數據的最終一致性,就像我在微信給靜靜轉賬一樣,可以不是馬上到賬,可能是2小時內到賬,可能是明天到賬,也可能是明年到賬(手動滑稽)。

關於CAP理論的論證是另外的知識點,這裏不做論證,只有CAP理論是正確的結論。

BASE理論

在分布式系統中,我們往往追求的是可用性,它的重要程度比一致性要高,那麽如何實現高可用性呢?前人已經給我們提出來了另外一個理論,就是BASE理論,它是對CAP定理的進一步擴展,是對CAP中的一致性和可用性進行一個權衡的結果。BASE理論指的是:

基本可用(Basically Available):分布式系統在出現故障時要保證核心功能可用,允許損失部分可用功能。

軟狀態(Soft state):允許系統中存在中間狀態,這個狀態不影響系統的可用性。

最終一致性(Eventually Consistent):經過一段時間後,所有節點的數據都達到一致,即延遲達到CAP中的C一致性。

BASE理論的核心思想就是:我們無法做到強一致,但每個應用都可以根據自身的業務特點,采用適當的方式來使系統達到最終一致性。BASE理論和數據庫本地事務的ACID特性是相反的,它完全不同於ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內不一致,即使最終是會達到一致的狀態(延遲達到一致)。

分布式事務的幾種解決方案

在說方案之前,一定要明確是否真的需要分布式事務。出現分布式事務的一個原因是因為微服務太多,這樣可能會導致一個團隊中一個人維護幾個微服務,太多團隊設計,搞得所有人疲憊不堪。這種時候,如果可能的話,還是建議把需要事務的微服務聚合成一個單機服務,使用數據庫本地事務。因為任何一種分布式事務的方案都會增加系統的復雜度,導致開發或維護的成本過高。所以千萬不要因為追求某些設計,而引入不必要的成本和復雜度。

而在分布式系統中,要實現分布式事務,無外乎就是這幾種解決方案:兩階段提交、補償事務、本地消息表、MQ事務消息、Sagas事務模型等。

兩階段提交(2PC,2-Participant-Coordinator)

要說兩階段提交,一定要先講數據庫的兩階段提交。數據庫支持的兩階段提交,叫做XA Transactions,MySQL從5.5版本開始支持,SQL Server從2005開始支持,Oracle從7開始支持。其中的XA是一個兩階段提交協議,這個協議分為兩個階段:

第一階段:事務協調器要求每個涉及到事務的數據庫預提交(Precommit)此操作,並反映是否可以提交。

第二階段:事務協調器要求每個數據庫提交數據。

其中,如果有任何一個數據庫否認此提交,那麽所有數據庫都會被要求回滾它們在此事務中的那部分信息。

所有兩階段提交的實現,實際上都是用的XA協議的原理,通過流程圖可以看出中間的一些比如Commit和Abort的細節:

技術分享圖片

兩階段提交這種解決方案屬於犧牲了一部分可用性來換取的一致性,盡量保證了數據的強一致,實現成本也較低,各大主流數據庫都有自己的實現。同時缺點也很明顯:

單點問題:事務管理器在整個流程中扮演的角色很關鍵,如果其宕機,比如在第一階段已經完成,在第二階段正準備提交的時候事務管理器宕機,資源管理器就會一直阻塞,導致數據庫無法使用。

同步阻塞:在準備就緒之後,資源管理器中的資源一直處與阻塞,知道提交完成,釋放資源。

數據不一致:兩階段提交協議雖然是為了分布式數據的強一致性設計,但是仍然存在數據不一致的可能。比如在第二階段中,假設協調者發出了事務Commit的通知,但是因為網絡問題,該通知僅被一部分參與者收到並執行了Commit操作,其余參與者因為沒有收到通知而一直處於阻塞狀態,這時候就產生了數據的不一致性。

總的來說,XA協議雖然較為簡單,成本較低,但是有單點問題,且不能支持高並發(因為基於同步阻塞,犧牲了可用性,對性能影響較大)。

補償事務(TCC,Try-Confirm-Cancel)

補償事務也就是用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作,它分為三個階段:

Try階段:嘗試執行,完成所有業務檢查(一致性),預留必須的業務資源(準隔離性)。即對業務系統做檢測和資源預留。

Confrim階段:確認真正執行業務,不做任何業務檢查,只使用Try階段預留的業務資源,Confrim操作滿足冪等性。要求具備冪等設計。即對業務系統做提交確認,Try階段執行成功並開始執行Confirm階段時,默認Confrim階段是不會出錯的。只要Try成功,Confirm一定成功。Confirm失敗後會進行重試。

Cancel階段:在業務執行出錯需要回滾的狀態下,執行取消業務、釋放預留資源的操作。

還是我微信給靜靜轉賬的例子:

1.首先在Try階段,要先調用遠程接口,把我微信的錢和靜靜微信的錢凍結起來。

2.在Confirm階段,執行遠程調用的轉賬的操作,轉賬成功進行解凍。

3.如果第2步執行成功,那麽轉賬成功;如果第2步執行失敗,則調用遠程凍結接口對應的解凍方法(Cancel)。

TCC事務機制相比於XA,解決了幾個缺點:

1.解決了協調者單點:由主業務方發起並完成這個業務活動。業務活動管理器也變成多點,引入集群。

2.解決了同步阻塞:引入超時機制,超時後進行補償,並且不會鎖定整個資源,將資源轉換為業務邏輯形式,粒度變小。

3.解決了數據一致性:有了補償機制後,由業務活動管理器控制一致性。

總的來說,跟2PC比起來,實現以及流程相對簡單了一些。但缺點也是比較明顯的,在2、3步中都有可能失敗。TCC是屬於應用層的一種補償方式,需要程序員在實現的時候多寫很多補償的代碼。在一些場景中,一些業務流程可能用TCC不太好定義及處理。因此TCC適合一些強隔離性的、嚴格一致性要求的、執行時間較短的活動業務。

本地消息表(異步確認)

本地消息表這種實現方式應該是業界使用最多的,其核心思想是將分布式事務拆分成本地事務進行處理,這種思路來源於ebay。這個方案的核心是將需要分布式處理的任務通過消息日誌的方式來異步執行。消息日誌可以存儲到本地文本、數據庫或消息隊列,再通過業務規則自動或人工發起重試。人工重試更多的是應用於支付場景,通過對賬系統對事後問題的處理。

技術分享圖片

基本思路就是:

消息生產方,需要額外建一個消息表,並記錄消息發送狀態。消息表和業務數據要在一個事務裏提交,也就是說它們要在一個數據庫裏面。然後消息會經過消息隊列(MQ)發送到消息的消費方。如果消息發送失敗,會進行重試發送。

消息消費方,需要處理這個消息,並完成自己的業務邏輯。此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那麽就會重試執行。如果是業務上面的失敗,可以個生產方發送一個業務補償消息,通知生產方進行回滾等操作。

生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

這種方案遵循BASE理論,采用的是最終一致性,是幾種方案裏面比較適合實際業務場景的,既不會出現像2PC那樣復雜的實現(當調用鏈很長的時候,2PC的可用性是非常低的),也不會像TCC那樣可能出現確認或回滾不了的情況。

總的來說,本地消息表是一種非常經典的實現,避免了分布式事務,實現了最終一致性。但是消息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理。

MQ事務消息

有一些第三方的MQ是支持事務消息的,比如RocketMQ,它們支持事務消息的方式也是類似於采用的二階段提交,但是市面上一些主流的MQ都是不支持事務消息的,比如RabbitMQ和Kafka都不支持。

以阿裏的RocketMQ中間件為例,其思路大致是:

第一階段Prepared消息,會拿到消息的地址。

第二階段執行本地事務。

第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。消息接收者就能使用這個消息。

如果確認消息失敗,在RocketMQ Broker中提供了定時掃描沒有更新狀態的消息。如果有消息沒有得到確認,會向消息發送者發送消息,來判斷是否提交,在RocketMQ中是以Listener的形式給發送者,用來處理消息。如果消費超時,則需要一直重試,消息接收端需要保證冪等。如果消息消費失敗,這時就需要人工進行處理。因為這個概率較低,如果為了這種小概率事件而設計這個復雜的流程反而得不償失。

也就是說,在業務方法內要向消息隊列提出兩次請求,一次發送消息和一次確認消息。如果確認消息發送失敗了,RocketMQ會定期掃描消息集群中的事務消息,這時候發現了Prepared消息,它會向消息發送者確認,所以生產方需要實現一個check接口,RocketMQ會根據發送端設置的策略來決定是回滾還是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。

技術分享圖片

MQ事務消息實現了最終一致性,且不需要依賴本地數據庫事務。

Sagas事務模型

Sagas事務模型又叫做長時間運行的事務(Long-running-transaction),其核心思想是將長事務才分為多個本地短事務,由Saga事務協調器協調,如果正常結束那就正常完成,如果某個步驟失敗,則根據相反順序一次調用補償操作,重新進行業務回滾。它描述的是另外一種在沒有兩階段提交的情況下解決分布式系統中復雜的業務事務問題。

這個理論較新,又難,這裏只是入門,因此只要知道有這麽個解決方案就好了(啊,好難啊)。

虛心地請教了大佬後了解到,tx-lcn是一個目前比較流行的解決方案,部門研發的新框架也打算用這個開源的分布式事務框架。在了解完分布式事務的概念與幾種解決方案之後,接下來就要好好地學習具體的實現與應用(tx-lcn)了。

技術分享圖片

分布式事務入門