億級流量架構之分佈式事務解決方案解析

上一篇文章 ( 億級流量架構之分佈式事務思路及方法) 中梳理事務到分佈式事務的演變過程, 以及分佈式事務的處理思路, 這篇文章主要從應用的角度對比目前較爲流行的一些分佈式事務方案, 以及一些商業應用。

想讓數據具有高可用性, 就得寫多份數據, 寫多份數據就會有數據一致性問題, 數據已執行問題又會引發性能問題, 所以如何權衡, 是一件仁者見仁、智者見者的問題, 目前的數據一致性, 即分佈式事務, 大概有如下幾種解決方案:

  1. Master-Slave 方案。

  2. Master-Master 方案。

  3. 兩階段和三階段提交方案。

  4. Paxos 方案。

第 3 點在上篇文章中已經講過, 1、2 這兒會簡單梳理, 重點是第四個方案, 目前很多公司的事務處理方式, 例如阿里巴巴的 TCC(Try–Confirm–Cancel), 亞馬遜的 PRC(Plan–Reserve–Confirm) 都是兩階段提交的變種, 凡是通過業務補償, 或者是在業務層面上做的分佈式事務, 基本都是兩階段提交的玩法, 但是這否是應用層事務處理方式, 而在數據層解決事務問題, Paxos 是不二之選。

Master-Slave 方案

這個也叫主從模式,Slave 一般是 Master 的備份。在這樣的系統中,一般是如下設計的:

1)讀寫請求都由 Master 負責。

2)寫請求寫到 Master 上後,由 Master 同步到 Slave 上。

從 Master 同步到 Slave 上,你可以使用異步,也可以使用同步,可以使用 Master 來 push,也可以使用 Slave 來 pull。通常來說是 Slave 來週期性的 pull,所以,是最終一致性。

這個設計的問題是,如果 Master 在 pull 週期內垮掉了,那麼會導致這個時間片內的數據丟失。如果你不想讓數據丟掉,Slave 只能成爲 Read-Only 的方式等 Master 恢復。

如果可以容忍數據丟掉的話,你可以馬上讓 Slave 代替 Master 工作(對於只負責計算的結點來說,沒有數據一致性和數據丟失的問題,Master-Slave 的方式就可以解決單點問題了) ,Master Slave 也可以是強一致性的, 比如:當我們寫 Master 的時候,Master 負責先寫自己,等成功後,再寫 Slave,兩者都成功後返回成功,整個過程是同步的,如果寫 Slave 失敗了,那麼兩種方法,一種是標記 Slave 不可用報錯並繼續服務(等 Slave 恢復後同步 Master 的數據,可以有多個 Slave,這樣少一個,還有備份, 也就是多個 Slave),另一種是回滾自己並返回寫失敗。

注:一般不先寫 Slave,因爲如果寫 Master 自己失敗後,還要回滾 Slave,此時如果回滾 Slave 失敗,就得手工訂正數據

Master-Master 方案

Master-Master,主主模式, 又叫 Multi-master,是指一個系統存在兩個或多個 Master,每個 Master 都提供 read-write 服務。這個模型是 Master-Slave 的加強版,數據間同步一般是通過 Master 間的異步完成,所以是最終一致性。Master-Master 的好處是,一臺 Master 掛了,別的 Master 可以正常做讀寫服務,他和 Master-Slave 一樣,當數據沒有被複制到別的 Master 上時,數據會丟失。很多數據庫都支持 Master-Master 的 Replication 的機制。

這種模式的問題在於: 如果多個 Master 對同一個數據進行修改的時候,這個模型的惡夢就出現了——對數據間的衝突合併,這並不是一件容易的事情。爲了解決這問題, Dynamo 提出了一種解決辦法, 記錄數據的版本號和修改者, 這也就意味着數據衝突這個事是交給用戶自己搞的。

兩階段和三階段提交方案

這個是業務層分佈式事務處理的核心, , 在上篇文章 ( 億級流量架構之分佈式事務思路及方法) 中 "二三階段提交協議" 介紹得比較詳細了, 這兒不多說。需要注意是這是重點, 不太瞭解的朋友, 爲了更好的理解後面的方案, 建議看看相關部分。

Paxos 方案

理解 Paxos 算法之前, 先講一個情景——兩將軍問題, 來理解這個算法是解決了什麼問題。

兩將軍問題

有兩支軍隊,它們分別有一位將軍領導,現在準備攻擊一座修築了防禦工事的城市。這兩支軍隊都駐紮在那座城市的附近,分佔一座山頭。一道山谷把兩座山分隔開來,並且兩位將軍唯一的通信方式就是派各自的信使來往于山谷兩邊。不幸的是,這個山谷已經被那座城市的保衛者佔領,並且存在一種可能,那就是任何被派出的信使通過山谷是會被捕。請注意,雖然兩位將軍已經就攻擊那座城市達成共識,但在他們各自佔領山頭陣地之前,並沒有就進攻時間達成共識。兩位將軍必須讓自己的軍隊同時進攻城市才能取得成功。因此,他們必須互相溝通,以確定一個時間來攻擊,並同意就在那時攻擊。如果只有一個將軍進行攻擊,那麼這將是一個災難性的失敗。這個思維實驗就包括考慮他們如何去做這件事情。下面是我們的思考:

1)第一位將軍先發送一段消息 “讓我們在上午 9 點開始進攻”。然而,一旦信使被派遣,他是否通過了山谷,第一位將軍就不得而知了。任何一點的不確定性都會使得第一位將軍攻擊猶豫,因爲如果第二位將軍不能在同一時刻發動攻擊,那座城市的駐軍就會擊退他的軍隊的進攻,導致他的軍對被摧毀。

2)知道了這一點,第二位將軍就需要發送一個確認回條:“我收到您的郵件,並會在 9 點的攻擊。” 但是,如果帶着確認消息的信使被抓怎麼辦?所以第二位將軍會猶豫自己的確認消息是否能到達。

3)於是,似乎我們還要讓第一位將軍再發送一條確認消息——“我收到了你的確認”。然而,如果這位信使被抓怎麼辦呢?

4)這樣一來,是不是我們還要第二位將軍發送一個 “確認收到你的確認” 的信息。

靠,於是你會發現,這事情很快就發展成爲不管發送多少個確認消息,都沒有辦法來保證兩位將軍有足夠的自信自己的信使沒有被敵軍捕獲。

Paxos 算法

Paxos 算法解決的問題是在一個可能發生上述異常的分佈式系統中如何就某個值達成一致,保證不論發生以上任何異常,都不會破壞決議的一致性。一個典型的場景是,在一個分佈式數據庫系統中,如果各節點的初始狀態一致,每個節點都執行相同的操作序列,那麼他們最後能得到一個一致的狀態。爲保證每個節點執行相同的命令序列,需要在每一條指令上執行一個「一致性算法」以保證每個節點看到的指令一致。一個通用的一致性算法可以應用在許多場景中,是分佈式計算中的重要問題。從 20 世紀 80 年代起對於一致性算法的研究就沒有停止過。

這個算法詳細解釋可以參考維基百科以及 raft 算法 (Paxos 改進) 作者的視頻(B 站, YouTube), 這兒我用自己的話敘述:

這個算法將節點分了很多類, 官方定義爲: Client、Propose、 Acceptor、 Learner, 含義不重要, 先來理解他們的作用, 看我的文字就行了。

在我們讀書的時候, 班級裏有組長, 有班委成員, 有班務記錄員, 下面一一對應起來:

加入我們班決定這周沒去遊玩, 現在班會討論去玩什麼項目:

Client : 代表普通同學, 可以提出方案, 需要將方案交給組長 (Propose)

Propose : 組長, 一個班肯定有很多組長, 主要任務是將組員的方案提出來給班委投票確認, 同時統計班委們返回的投票結果, 並將結果告訴所有人。

Acceptor:班委, 這兒的班委具有投票權, 但是班委不關心投票的內容, 哈哈哈哈, 是不是有點奇怪, 先記住, 後面就理解了, 班委只關心方案是不是已經被提過, 只要這個方案之前沒有被提過就會同意, 將同意的信息返回給提案的組長。

當組長收到各個班委回覆的信息之後, 會統計同意的人數, 如果人數過半 (N2+1N2+1), 那麼就會廣播這個提案已經被認可, 這時候 Learner 會在班務本子上記錄下這個提案。

如上圖, 有一個組員 (Client), 一個組長 (Proposer), 三個班委 (Acceptor), 兩個 Learner, 當組員提出方案時, 組長會將方案提交給三位班委, 班委會看看這個方案之前是不是已經提過 (主要是根據方案編號, 也就是方案編號一致變化, 默認是遞增), 沒提過的話會通過這個提案, 然後組長統計通過的比例, 過半數之後會將方案通過的編號進行廣播, 班委會回饋信息, 此時 Learner 會記錄下來同時回饋。

剛剛留下了一個疑惑, 爲什麼開始階段 (Prepare(1)) 時班委不關心內容呢?

在這幾個角色中, Acceptor(班委) 是數據庫 (其實也不是數據庫, 僅僅是爲了方便理解),Learner 也是數據庫 (備份), 當你準備提交一條消息時, 第一步僅僅是看能不能與之建立正常的連接, 其次看看這個數據之前是不是已經提交過, 如果大部分都可以建立正常的連接並且沒有被提交過, 那麼說明我們的數據就可以提交了。

Paxos 活鎖

前面說過, 三個班委 (Acceptor) 只要接受到的提案是未提交的且過半的話, 就會通過, 如果一個提案 1 的組長 Proposer 正在投票信息準備通知時, 另一個組長 Proposer 又提交了提案 2, 那麼班委就會開始討論提案 2, 放棄提案 1 的討論, 此時提案 1 被丟棄, 那組長 1 會將提案重新提交, 這導致了死鎖的誕生, 爲了解決這個問題, 可以讓提交方案的組長隨機睡眠一段時間。

Paxos 改進版

前面例子可以看出是一個兩階段提交的過程, 改進版最主要的點在於, 在 Acceptor 中選出一個主節點, 要提交議案直接交給主節點, 由主節點將這些消息同步給其他節點, 如果此時過半數, 那麼久將數據提交, 然後將信息返回給提議員 (組長), 什麼意思呢, 就是組長提交方案之後, 交給班委的頭目, 班委頭目統計好投票結果, 如果通過了直接通知所有班委以及記錄員, 同時將信息返回給提議員 (也就是組長)。

主節點的選舉

對於三個班委 (Acceptor) 而言, 有一個時間週期, 如果這個週期內收到主節點的心跳包, 那麼就會相安無事, 如果週期內沒收到心跳包, 那麼就會向其他節點發出請求包, 這個包主要是自己要當主節點, 請大家投票, 所有接受到這個請求包的節點, 回覆同意, 當一個節點收到的同意信息過半之後, 就會成爲主節點, 同時廣播這個信息, 收到信息的節點就成爲了從節點。

所有有關操作都會通過這個主節點, 主節點再在其餘的節點之間進行投票, 通過之後主節點直接提交事務然後將信息返回給調用者。

Paxos 與數據提交

簡單說來,Paxos 的目的是讓整個集羣的結點對某個值的變更達成一致。Paxos 算法基本上來說是個民主選舉的算法——大多數的決定會成個整個集羣的統一決定。任何一個點都可以提出要修改某個數據的提案,是否通過這個提案取決於這個集羣中是否有超過半數的結點同意(所以 Paxos 算法需要集羣中的結點是單數)。

這個算法有兩個階段(假設這個有三個結點:A,B,C):

第一階段:Prepare 階段

A 把申請修改的請求 Prepare Request 發給所有的結點 A,B,C。注意,Paxos 算法會有一個 Sequence Number(\ 可以認爲是一個提案號,這個數不斷遞增,而且是唯一的,也就是說 A 和 B 不可能有相同的提案號),這個提案號會和修改請求一同發出,任何結點在 “Prepare 階段” 時都會拒絕其值小於當前提案號的請求。所以,結點 A 在向所有結點申請修改請求的時候,需要帶一個提案號,越新的提案,這個提案號就越是是最大的。

如果接收結點收到的提案號 n 大於其它結點發過來的提案號,這個結點會迴應 Yes(本結點上最新的被批准提案號),並保證不接收其它 < n 的提案。這樣一來,結點上在 Prepare 階段裏總是會對最新的提案做承諾。

優化:在上述 prepare 過程中,如果任何一個結點發現存在一個更高編號的提案,則需要通知 提案人,提醒其中斷這次提案。

第二階段:Accept 階段

如果提案者 A 收到了超過半數的結點返回的 Yes,然後他就會向所有的結點發布 Accept Request(同樣,需要帶上提案號 n),如果沒有超過半數的話,那就返回失敗。

當結點們收到了 Accept Request 後,如果對於接收的結點來說,n 是最大的了,那麼,它就會修改這個值,如果發現自己有一個更大的提案號,那麼,結點就會拒絕修改。

我們可以看以,這似乎就是一個 “兩段提交” 的優化。其實,2PC/3PC 都是分佈式一致性算法的殘次版本,Google Chubby 的作者 Mike Burrows 說過這個世界上只有一種一致性算法,那就是 Paxos,其它的算法都是殘次品。

我們還可以看到:對於同一個值的在不同結點的修改提案就算是在接收方被亂序收到也是沒有問題的。

商業產品

這兒主要了解 GTS、LCN、TXC, 另外還有上一篇文章講過的 TCC, 這兒就不繼續重複了。

GTS

GTS 是目前業界第一款,也是唯一的一款通用的解決微服務分佈式事務問題的中間件,而且可以保證數據的強一致性。本文將對 GTS 簡單概述, 詳情可以參考阿里巴巴官方文檔介紹。

GTS 是一款分佈式事務中間件,由阿里巴巴中間件部門研發,可以爲微服務架構中的分佈式事務提供一站式解決方案。GTS 方案的基本思路是:將分佈式事務與具體業務分離,在平臺層面開發通用的事務中間件 GTS,由事務中間件協調各服務的調用一致性,負責分佈式事務的生命週期管理、服務調用失敗的自動回滾。

GTS 方案有三方面的優勢:
第一、它將微服務從分佈式事務中解放出來,微服務的實現不需要再考慮反向接口、冪等、回滾策略等複雜問題,只需要業務自己的接口即可,大大降低了微服務開發的難度與工作量。將分佈式事務從所謂的 “貴族技術” 變爲大家都能使用的“平民技術 ”,有利於微服務的落地與推廣。
第二、GTS 對業務代碼幾乎沒有侵入,只需要通過註解 @TxcTransaction 界定事務邊界即可,微服務接入 GTS 的成本非常低。
第三、性能方面 GTS 也非常優秀,是傳統 XA 方案的 8~10 倍。

GTS 原理

GTS 中間件主要包括客戶端(GTS Client)、資源管理器(GTS RM)和事務協調器(GTS Server)三部分。GTS Client 主要完成事務的發起與結束。GTS RM 完成分支事務的開啓、提交、回滾等操作。GTS Server 主要負責分佈式事務的整體推進,事務生命週期的管理。

GTS 和微服務集成後的結構圖如上圖所示。GTS Client 需要和業務應用集成部署,RM 與微服務集成部署。當業務應用發起服務調用時,首先會通過 GTS Client 向 TC 註冊新的全局事務。之後 GTS Server 會給業務應用返回全局唯一的事務編號 xid。業務應用調用服務時會將 xid 傳播到服務端。微服務在執行數據庫操作時會通過 GTS RM 向 GTS Server 註冊分支事務,並完成分支事務的提交。如果 A、B、C 三個服務均調用成功,GTS Client 會通知 GTS Server 結束事務。假設 C 調用失敗,GTS Client 會要求 GTS Server 發起全局回滾。然後由各自的 RM 完成回滾工作。

LCN

TX-LCN 定位是於一款事務協調性框架,框架本事並不操作事務,而是基於對事務的協調從而達到事務一致性的效果。TX-LCN 由兩大模塊組成, TxClient、TxManager,TxClient 作爲模塊的依賴框架,提供 TX-LCN 的標準支持,TxManager 作爲分佈式事務的控制放。事務發起方或者參與反都由 TxClient 端來控制。

下圖來自 LCN 官網, 與 LCN 有關詳情可以訪問官方倉庫 。

核心的步驟

LCN 事務模式

LCN 主要有三種事務模式, 分別是 LCN 模式、TCC 模式、TXC 模式。

LCN 模式

原理:

LCN 模式是通過代理 Connection 的方式實現對本地事務的操作,然後在由 TxManager 統一協調控制事務。當本地事務提交回滾或者關閉連接時將會執行假操作,該代理的連接將由 LCN 連接池管理。

特點:

TCC 模式

原理:

TCC 事務機制相對於傳統事務機制(X/Open XA Two-Phase-Commit),其特徵在於它不依賴資源管理器 (RM) 對 XA 的支持,而是通過對(由業務系統提供的)業務邏輯的調度來實現分佈式事務。主要由三步操作,Try: 嘗試執行業務、 Confirm: 確認執行業務、 Cancel: 取消執行業務。

特點:

TXC 模式

原理:

TXC 模式命名來源於淘寶,實現原理是在執行 SQL 之前,先查詢 SQL 的影響數據,然後保存執行的 SQL 快走信息和創建鎖。當需要回滾的時候就採用這些記錄數據回滾數據庫,目前鎖實現依賴 redis 分佈式鎖控制。

特點:

TXC

TXC(Taobao Transaction Constructor) 是阿里巴巴的一個分佈式事務中間件,它可以通過極少的代碼侵入,實現分佈式事務。

在大部分情況下,應用只需要引入 TXC Client 的 jar 包,進行幾項簡單配置,以及以行計的代碼改造,即可輕鬆保證分佈式數據一致性。TXC 同時提供了豐富的編程和配置策略,以適應各種長尾的應用需求。

TXC 標準模式(AT 模式)

TXC 標準模式(Automatic TXC)是最主要的事務模式,通過基於 TDDL 的 TXC 數據源,它對 SQL 語句提供了分佈式事務支持。它幫助應用方以最小的改造代價來實現 TDDL 下的事務的功能。

在標準模式下,當開啓了 TXC 分佈式事務時,TXC 框架將自動的根據執行的 SQL 語句,進行事務分支劃分(每個物理庫上的一個本地事務作爲一個分佈式事務分支),把各個分支統一納入事務。

分佈式事務的隔離級別可以配置,讀未提交(read uncommitted)和讀已提交(read committed)。讀未提交是缺省設置。

標準模式適合於 TDDL 分庫分表、多 TDDL 數據源、跨進程的多 TDDL 數據源等幾乎任何 TDDL 應用場景下的分佈式事務。

TXC 自定義模式(MT 模式)

MT(Manual TXC)模式,提供用戶可以介入兩階段提交過程的一種模式。在這種模式下,用戶可以根據自身業務需求自定義在 TXC 的兩階段中每個階段的具體行爲。MT 模式提供了更多的靈活性,可能性,以達到特殊場景下的自定義優化,及特殊功能的實現。

MT 模式不依賴於 TDDL,這是它相對於標準模式的一個最大的優勢。MT 模式幾乎滿足任何我們想到的事務場景。

TXC 重試模式

RT(Retry TXC)模式嚴格地說,不屬於傳統的事務範圍。在 TXC 將其定義爲一種特殊的事務,它通過在用戶指定時間內不停的異步重試失敗的 SQL 語句,來保證 SQL 語句最終成功。

RT 模式也是基於 TXC 數據源的。

當我們通過 TDDL 執行一個需要分庫的 SQL,假設在第一個庫執行成功了,但是在第二個庫執行失敗了。如果採用 TXC 標準模式,第一個庫的 SQL 會回滾。對用戶來說,他的 SQL 失敗了,在兩個庫上是一致的。

如果採用 RT 模式,第二個庫執行失敗的 SQL 會保存下來,TXC 不斷重試這個 SQL,直到成功。對用戶來說, 他的 SQL 成功了,在兩個庫上最終是一致的。當然,TXC 不會一直重試 SQL,用戶可以指定一個超時時間,超過這個時間限制,TXC 會發送告警信息到用戶。用戶拿到告警信息後,可以從業務庫的 RT SQL 表中拿到對應的 SQL 語句,決定下一步怎麼處理。

試想一下,當一個 SQL 涉及到分庫,我們執行這個 SQL 失敗了,通常來說,我們需要通過 log 查出它在哪幾個庫成功了哪幾個庫失敗了,並且在失敗的庫上不斷重試。這是很繁瑣的。RT 模式把用戶從這種繁瑣工作中解脫出來,用戶不再需要關注哪些庫上 SQL 失敗了,也不需要自己重試 SQL。

出處:https://www.cnblogs.com/Courage129/p/14443653.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/CJ219p87gmffzefSwC_CIQ