上一篇文章( 億級流量架構之分散式事務思路及方法)中梳理事務到分散式事務的演變過程, 以及分散式事務的處理思路,這篇文章主要從應用的角度對比目前較為流行的一些分散式事務方案,以及一些商業應用。
想讓資料具有高可用性,就得寫多份資料,寫多份資料就會有資料一致性問題,資料已執行問題又會引發效能問題,所以如何權衡,是一件仁者見仁、智者見者的問題,目前的資料一致性,即分散式事務,大概有如下幾種解決方案:
- Master-Slave 方案。
- Master-Master 方案。
- 兩階段和三階段提交方案。
- 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:班委,這兒的班委具有投票權,但是班委不關心投票的內容,哈哈哈哈,是不是有點奇怪, 先記住,後面就理解了,班委只關心方案是不是已經被提過,只要這個方案之前沒有被提過就會同意,將同意的資訊返回給提案的組長。
當組長收到各個班委回覆的資訊之後,會統計同意的人數,如果人數過半(\(\frac{N}{2}+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有關詳情可以訪問官方倉庫 。
核心的步驟
- 建立事務組,是指在事務發起方開始執行業務程式碼之前先呼叫TxManager建立事務組物件,然後拿到事務標示GroupId的過程。
- 加入事務組,新增事務組是指參與方在執行完業務方法以後,將該模組的事務資訊通知給TxManager的操作。
- 通知事務組,是指在發起方執行完業務程式碼以後,將發起方執行結果狀態通知給TxManager,TxManager將根據事務最終狀態和事務組的資訊來通知相應的參與模組提交或回滾事務,並返回結果給事務發起方。
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分散式鎖控制。
特點:
- 該模式同樣對程式碼的嵌入性低。
- 該模式僅限於對支援SQL方式的模組支援。
- 該模式由於每次執行SQL之前需要先查詢影響資料,因此相比LCN模式消耗資源與時間要多。
- 該模式不會佔用資料庫的連線資源。
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。