淺析分佈式事務

1、單機事務

1.1 ACID特性

1.1.1 原子性

一個事務(transaction)中的全部操做,要麼所有完成,要麼所有不完成,不會結束在中間某個環節。算法

事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。數據庫

就像你買東西要麼交錢收貨一塊兒都執行,要麼發不出貨,就退錢。segmentfault

1.1.2 一致性

事務的一致性指的是在一個事務執行以前和執行以後數據庫都必須處於一致性狀態。網絡

若是事務成功地完成,那麼系統中全部變化將正確地應用,系統處於有效狀態。架構

若是在事務中出現錯誤,那麼系統中的全部變化將自動地回滾,系統返回到原始狀態。併發

1.1.3 隔離性

指的是在併發環境中,當不一樣的事務同時操縱相同的數據時,每一個事務都有各自的完整數據空間。異步

由併發事務所作的修改必須與任何其餘併發事務所作的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另外一事務修改它以前的狀態,要麼是另外一事務修改它以後的狀態,事務不會查看到中間狀態的數據。分佈式

1.1.4 持久性

指的是隻要事務成功結束,它對數據庫所作的更新就必須永久保存下來。.net

即便發生系統崩潰,從新啓動數據庫系統後,數據庫還能恢復到事務成功結束時的狀態。架構設計

打個比方,你買東西的時候須要記錄在帳本上,即便老闆忘記了那也有據可查。

1.2 隔離級別

1.2.1 read uncommitted

read uncommitted:讀未提交,就是一個事務能夠讀取另外一個未提交事務的數據。

髒讀(Dirty Reads):一個事務正在對一條記錄作修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作"髒讀"。

那怎麼解決髒讀呢?Read committed,讀提交,能解決髒讀問題

1.2.2 read committed

read committed:讀提交,就是一個事務要等另外一個事務提交後才能讀取數據。

不可重複讀(unrepeatable read):如有事務對數據進行更新(UPDATE)操做時,讀操做事務要等待這個更新操做事務提交後才能讀取數據,能夠解決髒讀問題。但若是第一個事務在第二個事務的先後各有一次查詢操做,則其讀取的數據可能不一致,便可能出現了一個事務範圍內兩個相同的查詢卻返回了不一樣數據,這就是不可重複讀。

那怎麼解決可能的不可重複讀問題?Repeatable read ,可重複讀。

1.2.3 repeatable read

repeatable read:可重複讀,對同一字段的屢次讀取結果都是一致的,除非數據是被自己事務本身所修改;

幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」

那麼怎麼解決幻讀的問題了?serializable,串行化。

1.2.4 serializable

serializable:串行化,全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾。


2、分佈式事務

2.1 分佈式理論

2.1.1 CAP理論

CAP是一個經典的分佈式系統理論。其告訴咱們,一個分佈式系統不能同時知足 一致性(Consistency)、可用性(Availablity)和分區容忍性(Partition tolerance)這三個基本需求,最多隻能同時知足其中兩項。

(一) Consistency 一致性

多個副本之間,在同一時刻可否有一樣的值。

對於一個將副本數據分佈在不一樣節點的系統來講,若是第一個節點的數據進行了更新操做而且更新成功後,卻沒有使得其餘節點的數據等到對應的更新,因而在對第二個節點的數據進行讀取操做時,獲取的依然是老數據(或者稱爲髒數據),這就是典型的分佈式數據不一致的狀況。

強一致性

分佈式系統中,若是可以作到針對各一個數據項的更新操做執行成功以後,全部的用戶夠能夠讀取到最新的值,那麼這樣的系統就被認爲具備強一致性。

弱一致性

系統不能保證後續訪問返回更新的值。須要在一些條件知足以後,更新的值才能返回。從更新操做開始,到系統保證任何觀察者老是看到更新的值的這期間被稱爲不一致窗口。

最終一致性

這是弱一致性的特殊形式;存儲系統保證若是沒有對某個對象的新更新操做,最終全部的訪問將返回這個對象的最後更新的值。

(二)Available 可用性

系統提供的服務必須一直處於可用的狀態。即便集羣中部分節點故障。

對於客戶的每一個請求操做老是可以在有限的時間內返回結果。超過了有限時間,那麼系統就被認爲是不可用。

(三)Partition tolerance分區容錯性

系統在遇到節點故障或者網絡分區時,仍然可以對外提供一致性和可用性的服務。

對於分佈式系統而言,分區是必定存在的,即partition必定存在,因此咱們只能在可用性和一致性之間抉擇。

2.1.2 BASE理論

BASE是Basically Available(基本可用)、Soft state(軟狀態) 和Eventually consistent(最終一致性)的縮寫;BASE理論是對CAP中一致性和可用性權衡的結果。其核心思想是:即便沒法作到強一致性,但每一個業務均可以根據業務的特色採起適當的方式是系統達到最終一致性。

(一) 基本可用

基本可用指分佈式系統出現不可預知的故障的時候,容許損失部分可用性—注意,這毫不等價於系統不可用。

常見於:

  • 耗時增長,響應變慢;
  • 限流降級;

(二) 軟狀態

軟狀態指容許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體可用性,即容許系統在不一樣節點的數據副本之間數據同步的過程存在延遲。

(三) 最終一致性

最終一致性強調的是全部的數據副本,在通過一段時間的同步以後,最終都可以達到一個一致的狀態。所以,最終一致性的本質是須要系統保證最終數據可以達到一致,而不須要實時保證系統數據的強一致性。

總的來講,BASE理論面向的是大型高可用可擴展的分佈式系統,和傳統的事物ACID特性是相反的,它徹底不一樣於ACID的強一致性模型,而是經過犧牲強一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。但同時,在實際的分佈式場景中,不一樣業務單元和組件對數據一致性的要求是不一樣的,所以在具體的分佈式系統架構設計過程當中,ACID特性和BASE理論每每又會結合在一塊兒。

2.1.3 分佈式一致性算法

參考 CAP 一致性協議及應用解析 對 分佈式一致性算法的解析。

2.1.3.1 Paxos 協議

像 2PC 和 3PC 都須要引入一個協調者的角色,當協調者 down 掉以後,整個事務都沒法提交,參與者的資源都出於鎖定的狀態,對於系統的影響是災難性的,並且出現網絡分區的狀況,頗有可能會出現數據不一致的狀況。有沒有不須要協調者角色,每一個參與者來協調事務呢,在網絡分區的狀況下,又能最大程度保證一致性的解決方案呢。此時 Paxos 出現了。

Paxos 算法是 Lamport 於 1990 年提出的一種基於消息傳遞的一致性算法。因爲算法難以理解起初並無引發人們的重視,Lamport在八年後從新發表,即使如此Paxos算法仍是沒有獲得重視。2006 年 Google 的三篇論文石破天驚,其中的 chubby 鎖服務使用Paxos 做爲 chubbycell 中的一致性,後來才獲得關注。

Paxos 協議是一個解決分佈式系統中,多個節點之間就某個值(提案)達成一致(決議)的通訊協議。它可以處理在少數節點離線的狀況下,剩餘的多數節點仍然可以達成一致。即每一個節點,既是參與者,也是決策者

2.1.3.2 Raft協議

Paxos 是論證了一致性協議的可行性,可是論證的過程聽說晦澀難懂,缺乏必要的實現細節,並且工程實現難度比較高廣爲人知實現只有 zk 的實現 zab 協議。而後斯坦福大學RamCloud項目中提出了易實現,易理解的分佈式一致性複製協議 Raft。Java,C++,Go 等都有其對應的實現

  • 節點狀態

    • Leader(主節點):接受 client 更新請求,寫入本地後,而後同步到其餘副本中
    • Follower(從節點):從 Leader 中接受更新請求,而後寫入本地日誌文件。對客戶端提供讀請求
    • Candidate(候選節點):若是 follower 在一段時間內未收到 leader 心跳。則判斷 leader 可能故障,發起選主提議。節點狀態從 Follower 變爲 Candidate 狀態,直到選主結束
  • termId:任期號,時間被劃分紅一個個任期,每次選舉後都會產生一個新的 termId,一個任期內只有一個 leader。termId 至關於 paxos 的 proposalId。
  • RequestVote:請求投票,candidate 在選舉過程當中發起,收到 quorum (多數派)響應後,成爲 leader。
  • AppendEntries:附加日誌,leader 發送日誌和心跳的機制
  • election timeout:選舉超時,若是 follower 在一段時間內沒有收到任何消息(追加日誌或者心跳),就是選舉超時。

Leader 不會修改自身日誌,只會作追加操做,日誌只能由Leader轉向Follower。例如即將要down掉的Leader節點已經提交日誌1,未提交日誌 2,3。down 掉以後,節點 2 啓動最新日誌只有 1,而後提交了日誌 4。好巧不巧節點 1 又啓動了。此時節點 2 的編號 4 日誌會追加到節點 1 的編號 1 日誌的後面。節點 1 編號 2,3 的日誌會丟掉。

不依賴各個節點物理時序保證一致性,經過邏輯遞增的 term-id 和 log-id 保證。

2.1.3.3 ZAB協議

ZAB(ZooKeeper Atomic Broadcast)是爲ZooKeeper設計的一種支持崩潰恢復的原子廣播協議。

不少人會誤覺得ZAB協議是Paxos的一種特殊實現,事實上他們是兩種不一樣的協議。ZAB和Paxos最大的不一樣是,ZAB主要是爲分佈式主備系統設計的,而Paxos的實現是一致性狀態機(state machine replication)

儘管ZAB不是Paxos的實現,可是ZAB也參考了一些Paxos的一些設計思想,好比:

  • leader向follows提出提案(proposal)
  • leader 須要在達到法定數量(半數以上)的follows確認以後纔會進行commit
  • 每個proposal都有一個紀元(epoch)號,相似於Paxos中的選票(ballot)

ZAB特性

  1. 一致性保證

    1.1可靠提交(Reliable delivery) -若是一個事務 A 被一個server提交(committed)了,那麼它最終必定會被全部的server提交

    1.2全局有序(Total order) - 假設有A、B兩個事務,有一臺server先執行A再執行B,那麼能夠保證全部server上A始終都被在B以前執行

    1.3 因果有序(Causal order) - 若是發送者在事務A提交以後再發送B,那麼B必將在A以前執行

  2. 只要大多數(法定數量)節點啓動,系統就行正常運行
  3. 當節點下線後重啓,它必須保證能恢復到當前正在執行的事務

2.2 分佈式事務方案

2.2.1 強一致性方案

2.2.1.1 2PC協議

兩階段提交協議把分佈式事務分紅兩個過程,一個是準備階段,一個是提交階段,準備階段和提交階段都是由事務管理器發起的,爲了接下來說解方便,咱們把事務管理器稱爲協調者,把資管管理器稱爲參與者,資源管理器通常指數據庫。 兩階段以下:

  1. 準備階段

事務協調者(事務管理器)給每一個參與者(資源管理器)發送Prepare消息,每一個參與者要麼直接返回失敗(如權限驗證失敗),要麼在本地執行事務,寫本地的redo和undo日誌,但不提交,到達一種「萬事俱備,只欠東風」的狀態。

能夠進一步將準備階段分爲如下三個步驟:

  1. 協調者節點向全部參與者節點詢問是否能夠執行提交操做(vote),並開始等待各參與者節點的響應。
  2. 參與者節點執行詢問發起爲止的全部事務操做,並將Undo信息和Redo信息寫入日誌。(注意:若成功這裏其實每一個參與者已經執行了事務操做)
  3. 各參與者節點響應協調者節點發起的詢問。若是參與者節點的事務操做實際執行成功,則它返回一個」贊成」消息;若是參與者節點的事務操做實際執行失敗,則它返回一個」停止」消息。

2. 提交階段

若是協調者收到了參與者的失敗消息或者超時,直接給每一個參與者發送回滾(Rollback)消息;不然,發送提交(Commit)消息;參與者根據協調者的指令執行提交或者回滾操做,釋放全部事務處理過程當中使用的鎖資源。(注意:必須在最後階段釋放鎖資源)

接下來分兩種狀況分別討論提交階段的過程。

當協調者節點從全部參與者節點得到的相應消息都爲」贊成」時:

  1. 協調者節點向全部參與者節點發出」正式提交(commit)」的請求。
  2. 參與者節點正式完成操做,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送」完成」消息。
  4. 協調者節點受到全部參與者節點反饋的」完成」消息後,完成事務。

若是任一參與者節點在第一階段返回的響應消息爲」停止」,或者 協調者節點在第一階段的詢問超時以前沒法獲取全部參與者節點的響應消息時:

  1. 協調者節點向全部參與者節點發出」回滾操做(rollback)」的請求。
  2. 參與者節點利用以前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
  3. 參與者節點向協調者節點發送」回滾完成」消息。
  4. 協調者節點受到全部參與者節點反饋的」回滾完成」消息後,取消事務。

無論最後結果如何,第二階段都會結束當前事務。

二階段提交看起來確實可以提供原子性的操做,可是不幸的事,二階段提交仍是有幾個缺點的:

  1. 同步阻塞問題。執行過程當中,全部參與節點都是事務阻塞型的。當參與者佔有公共資源時,其餘第三方節點訪問公共資源不得不處於阻塞狀態。
  2. 單點故障。因爲協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤爲在第二階段,協調者發生故障,那麼全部的參與者還都處於鎖定事務資源的狀態中,而沒法繼續完成事務操做。(若是是協調者掛掉,能夠從新選舉一個協調者,可是沒法解決由於協調者宕機致使的參與者處於阻塞狀態的問題)
  3. 數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求以後,發生了局部網絡異常或者在發送commit請求過程當中協調者發生了故障,這會致使只有一部分參與者接受到了commit請求,而在這部分參與者接收到commit請求以後就會執行commit操做,可是其餘部分未接到commit請求的機器則沒法執行事務提交。因而整個分佈式系統便出現了數據不一致性的現象。
  4. 二階段沒法解決的問題:協調者再發出commit消息以後宕機,而惟一接收到這條消息的參與者同時也宕機了。那麼即便協調者經過選舉協議產生了新的協調者,這條事務的狀態也是不肯定的,沒人知道事務是否被已經提交。
2.2.1.2 3PC協議

因爲二階段提交存在着諸如同步阻塞、單點問題、腦裂等缺陷,因此,研究者們在二階段提交的基礎上作了改進,提出了三階段提交。

與兩階段提交不一樣的是,三階段提交有兩個改動點。

  1. 引入超時機制。同時在協調者和參與者中都引入超時機制。
  2. 在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段以前各參與節點的狀態是一致的。

也就是說,除了引入超時機制以外,3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

1. CanCommit階段

​ 3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者若是能夠提交就返回Yes響應,不然返回No響應。

  1. 事務詢問 協調者向參與者發送CanCommit請求。詢問是否能夠執行事務提交操做。而後開始等待參與者的響應。
  2. 響應反饋 參與者接收到CanCommit請求以後,正常狀況下,若是其自身認爲能夠順利執行事務,則返回Yes響應,並進入預備狀態。不然反饋No

2. PreCommit階段

​ 協調者根據參與者的反應狀況來決定是否能夠進行事務的PreCommit操做。根據響應狀況,有如下兩種可能。

​ 假如協調者從全部的參與者得到的反饋都是Yes響應,那麼就會執行事務的預執行。

  1. 發送預提交請求 協調者向參與者發送PreCommit請求,並進入Prepared階段。
  2. 事務預提交 參與者接收到PreCommit請求後,會執行事務操做,並將undo和redo信息記錄到事務日誌中。
  3. 響應反饋 若是參與者成功的執行了事務操做,則返回ACK響應,同時開始等待最終指令。

​ 假若有任何一個參與者向協調者發送了No響應,或者等待超時以後,協調者都沒有接收到參與者的響應,那麼就執行事務的中斷。

  1. 發送中斷請求 協調者向全部參與者發送abort請求。
  2. 中斷事務 參與者收到來自協調者的abort請求以後(或超時以後,仍未收到協調者的請求),執行事務的中斷。

3. doCommit階段

​ 該階段進行真正的事務提交,也能夠分爲如下兩種狀況。

執行提交

  1. 發送提交請求 協調接收到參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向全部參與者發送doCommit請求。
  2. 事務提交 參與者接收到doCommit請求以後,執行正式的事務提交。並在完成事務提交以後釋放全部事務資源。
  3. 響應反饋 事務提交完以後,向協調者發送Ack響應。
  4. 完成事務 協調者接收到全部參與者的ack響應以後,完成事務。

中斷事務

協調者沒有接收到參與者發送的ACK響應(多是接收者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

  1. 發送中斷請求 協調者向全部參與者發送abort請求
  2. 事務回滾 參與者接收到abort請求以後,利用其在階段二記錄的undo信息來執行事務的回滾操做,並在完成回滾以後釋放全部的事務資源。
  3. 反饋結果 參與者完成事務回滾以後,向協調者發送ACK消息
  4. 中斷事務 協調者接收到參與者反饋的ACK消息以後,執行事務的中斷。

​ 在doCommit階段,若是參與者沒法及時接收到來自協調者的doCommit或者abort請求時,會在等待超時以後,會繼續進行事務的提交。(其實這個應該是基於機率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那麼協調者產生PreCommit請求的前提條件是他在第二階段開始以前,收到全部參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道你們其實都贊成修改了)因此,一句話歸納就是,當進入第三階段時,因爲網絡超時等緣由,雖然參與者沒有收到commit或者abort響應,可是他有理由相信:成功提交的概率很大。 )

2PC與3PC的區別、3PC的缺點

​ 相對於2PC,3PC主要解決的單點故障問題,並減小阻塞,由於一旦參與者沒法及時收到來自協調者的信息以後,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。可是這種機制也會致使數據一致性問題,由於,因爲網絡緣由,協調者發送的abort響應沒有及時被參與者接收到,那麼參與者在等待超時以後執行了commit操做。這樣就和其餘接到abort命令並執行回滾的參與者之間存在數據不一致的狀況。

2.2.2 最終一致性方案

2.2.2.1 查詢模式

任何一個服務操做都須要提供一個查詢接口,用來向外部輸出操做執行的狀態。服務操做的使用方能夠經過查詢接口,得知服務操做執行的狀態,而後根據不一樣狀態來作不一樣的處理操做。
爲了可以實現查詢,每一個服務操做都須要有惟一的流水號標識,也可以使用這次服務操做對應的資源ID來標誌,例如:請求流水號、訂單號等。
首先,單筆查詢操做是必須提供的,批量查詢則根據須要來提供,若是使用了批量查詢,須要有合理的分頁機制,而且必須限制分頁的大小,以及對批量查詢的QPS須要有容量評估和流控等。

2.2.2.2 補償模式

有了上面的查詢模式,在任何狀況下,咱們都能得知具體的操做所處的狀態,若是整個操做處於不正常的狀態,咱們須要修正操做中有問題的子操做,這可能須要從新執行未完成的子操做,或者取消已經完成的子操做,經過修復使整個分佈式系統達到一致,爲了讓系統最終一致而作的努力都叫作補償。
對於服務化系統中同步調用的操做,業務操做發起的主動方在尚未獲得業務操做執行方的明確返回或者調用超時,這個時候業務發起的主動方須要及時的調用業務執行方得到操做執行的狀態,這裏使用查詢模式,得到業務操做的執行方的狀態後,若是業務執行方已經執行完預設的工做,則業務發起方給業務的使用方返回成功,若是業務操做的執行方的狀態爲失敗或者未知,則會當即告訴業務的使用方失敗,而後調用業務操做的逆向操做,保證操做不被執行或者回滾已經執行的操做,讓業務的使用方、業務發起的主動方、業務的操做方最終達成一致的狀態。

2.2.2.3 按期校對

既然咱們在系統中實現最終一致性,系統在沒有達到一致以前,系統間的狀態是不一致的,甚至是混亂的,須要補償操做來達到一致的目的,可是咱們如何來發現須要補償的操做呢?
在操做的主流程中的系統間執行校對操做,咱們能夠過後異步的批量校對操做的狀態,若是發現不一致的操做,則進行補償,補償操做與補償模式中的補償操做是一致的。
另外,實現按期校對的一個關鍵就是分佈式系統中須要有一個自始至終惟一的ID。

2.2.2.4 本地消息表

基本思路是

消息生產方,須要額外建一個消息表,並記錄消息發送狀態。消息表和業務數據要在一個事務裏提交,也就是說他們要在一個數據庫裏面。而後消息會通過MQ發送到消息的消費方。若是消息發送失敗,會進行重試發送。

消息消費方,須要處理這個消息,並完成本身的業務邏輯。此時若是本地事務處理成功,代表已經處理成功了,若是處理失敗,那麼就會重試執行。若是是業務上面的失敗,能夠給生產方發送一個業務補償消息,通知生產方進行回滾等操做。

生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。

2.2.2.5 MQ消息事務

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

以阿里的 RocketMQ 中間件爲例,其思路大體爲:

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

第二階段執行本地事務。

第三階段經過第一階段拿到的地址去訪問消息,並修改狀態。

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

2.2.2.6 TCC模式

TCC的定義

Try: 嘗試執行業務

完成全部業務檢查(一致性)
  預留必須業務資源(準隔離性)

Confirm: 確認執行業務

真正執行業務
  不做任何業務檢查
  只使用Try階段預留的業務資源
  Confirm操做知足冪等性

Cancel: 取消執行業務

釋放Try階段預留的業務資源
  Cancel操做知足冪等性

實現

  1. 一個完整的業務活動由一個主業務服務與若干從業務服務組成。
  2. 主業務服務負責發起並完成整個業務活動。
  3. 從業務服務提供 TCC 型業務操做。
  4. 業務活動管理器控制業務活動的一致性,它登記業務活動中的操做,並在活動提交時確認全部的兩階段事務的 confirm 操做,在業務活動取消時調用全部兩階段事務的 cancel 操做。

3、參考文獻

【1】CAP 一致性協議及應用解析

【2】最終一致性的理解

【3】分佈式事務?No, 最終一致性

【4】聊聊zookeeper的ZAB算法

相關文章
相關標籤/搜索