1. 程式人生 > >資料庫:分散式事務的解決方案

資料庫:分散式事務的解決方案

本節涉及到一些技術術語:2PC、CAP、BASE、RocketMQ、RabbitMQ、XA、Kafka、TCC

事務

在資料庫系統中,一個事務是指:由一系列資料庫操作組成的一個完整的邏輯過程。例如銀行轉帳,從原賬戶扣除金額,以及向目標賬戶新增金額,這兩個資料庫操作的總和,構成一個完整的邏輯過程,不可拆分。這個過程被稱為一個事務,具有ACID特性。

ACID:是指在資料庫管理系統(DBMS)中,事務(transaction)所具有的四個特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、永續性(Durability)。
l  原子性

:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
l  一致性:在事務開始之前和事務結束以後,資料庫的完整性限制沒有被破壞。
l  隔離性:當兩個或者多個事務併發訪問(此處訪問指查詢和修改的操作)資料庫的同一資料時所表現出的相互關係。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。

l  永續性:在事務完成以後,該事務對資料庫所作的更改便持久地儲存在資料庫之中,並且是完全的。
 

分散式理論

當我們的單個數據庫的效能產生瓶頸的時候,我們可能會對數據庫進行分割槽,這裡所說的分割槽指的是物理分割槽,分割槽之後可能不同的庫就處於不同的伺服器上了,這個時候單個數據庫的ACID已經不能適應這種情況了,而在這種ACID的叢集環境下,再想保證叢集的ACID幾乎是很難達到,或者即使能達到那麼效率和效能會大幅下降,最為關鍵的是再很難擴充套件新的分割槽了,這個時候如果再追求叢集的ACID會導致我們的系統變得很差,這時我們就需要引入一個新的理論原則來適應這種叢集的情況,就是 CAP 原則或者叫CAP定理,

那麼CAP定理指的是什麼呢?

CAP

CAP原理指的是,一致性(Consistency)可用性(Availability)分割槽容忍性(Partitiontolerance)這三個要素最多隻能同時實現兩點,不可能三者兼顧。這是Brewer教授於2000年提出的,後人也論證了CAP理論的正確性。

l  一致性(Consistency) :對於分散式的儲存系統,一個數據往往會存在多份。簡單的說,一致性會讓客戶對資料的修改操作(增/刪/改),要麼在所有的資料副本(replica)全部成功,要麼全部失敗。即,修改操作對於一份資料的所有副本(整個系統)而言,是原子(atomic)的操作。如果一個儲存系統可以保證一致性,那麼則客戶讀寫的資料完全可以保證是最新的。不會發生兩個不同的客戶端在不同的儲存節點中讀取到不同副本的情況。
l  可用性(Availability) :可用性很簡單,顧名思義,就是指在客戶端想要訪問資料的時候,可以得到響應。但是注意,系統可用(Available)並不代表儲存系統所有節點提供的資料是一致的。這種情況,我們仍然說系統是可用的。往往我們會對不同的應用設定一個最長響應時間,超過這個響應時間的服務我們仍然稱之為不可用的。
l  分割槽容忍性(Partition Tolerance) :如果你的儲存系統只執行在一個節點上,要麼系統整個崩潰,要麼全部執行良好。比如,兩個儲存節點之間聯通的網路斷開(無論長時間或者短暫的),就形成了分割槽。一般來講,為了提高服務質量,同一份資料放置在不同城市非常正常的。因此節點之間形成分割槽也很正常。

CAP定理總結

CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出WEB服務無法同時滿足一下3個屬性:

·        一致性(Consistency) :客戶端知道一系列的操作都會同時發生(生效)

·        可用性(Availability) :每個操作都必須以可預期的響應結束

·        分割槽容錯性(Partitiontolerance) :即使出現單個元件無法可用,操作依然可以完成

具體地講在分散式系統中,在任何資料庫設計中,一個Web應用至多隻能同時支援上面的兩個屬性。顯然,任何橫向擴充套件策略都要依賴於資料分割槽。因此,設計人員必須在一致性與可用性之間做出選擇。

這個定理在迄今為止的分散式系統中都是適用的! 為什麼這麼說呢?

這個時候有同學可能會把資料庫的2PC(兩階段提交)搬出來說話了。OK,我們就來看一下資料庫的兩階段提交。

對資料庫分散式事務有了解的同學一定知道資料庫支援的2PC,又叫做 XA Transactions。

MySQL從5.5版本開始支援,SQL Server 2005 開始支援,Oracle 7 開始支援。

其中,XA 是一個兩階段提交協議,該協議分為以下兩個階段:

·        第一階段:事務協調器要求每個涉及到事務的資料庫預提交(precommit)此操作,並反映是否可以提交.

·        第二階段:事務協調器要求每個資料庫提交資料。

其中,如果有任何一個數據庫否決此次提交,那麼所有資料庫都會被要求回滾它們在此事務中的那部分資訊。這樣做的缺陷是什麼呢?

咋看之下我們可以在資料庫分割槽之間獲得一致性。如果CAP 定理是對的,那麼它一定會影響到可用性。(該方案犧牲了一定的可用性換取一致性)

如果說系統的可用性代表的是執行某項操作相關所有元件的可用性的和。那麼在兩階段提交的過程中,可用性就代表了涉及到的每一個數據庫中可用性的和。

我們假設兩階段提交的過程中每一個數據庫都具有99.9%的可用性,那麼如果兩階段提交涉及到兩個資料庫,這個結果就是99.8%。根據系統可用性計算公式,假設每個月43200分鐘,99.9%的可用性就是43157分鐘, 99.8%的可用性就是43114分鐘,相當於每個月的宕機時間增加了43分鐘。

以上,可以驗證出來,CAP定理從理論上來講是正確的,CAP我們先看到這裡,等會再接著說。

BASE

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

·        Basically Available(基本可用)

·        Soft state(軟狀態)

·        Eventually consistent(最終一致性)

BASE全稱是BasicallyAvailable(基本可用), Soft-state(軟狀態/柔性事務), Eventually Consistent(最終一致性)。BASE模型在理論邏輯上是相反於ACID(原子性Atomicity、一致性Consistency、隔離性Isolation、永續性Durability)模型的概念,它犧牲高一致性,獲得可用性和分割槽容忍性。
 

基本可用(Basically Available)。基本可用是指分散式系統在出現故障的時候,允許損失部分可用性,即保證核心可用。電商大促時,為了應對訪問量激增,部分使用者可能會被引導到降級頁面,服務層也可能只提供降級服務。這就是損失部分可用性的體現。

軟狀態(Soft-state)。軟狀態是指允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。分散式儲存中一般一份資料至少會有三個副本,允許不同節點間副本同步的延時就是軟狀態的體現。mysql replication的非同步複製也是一種體現。

l  最終一致性 (Eventually Consistent)。最終一致性是指:經過一段時間以後,更新的資料會到達系統中的所有相關節點。這段時間就被稱之為最終一致性的時間視窗
BASE理論總結

BASE理論是對CAP中的一致性和可用性進行一個權衡的結果,理論的核心思想就是:我們無法做到強一致,但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventualconsistency)。

有了以上理論之後,我們來看一下分散式事務的問題。

分散式事務

在分散式系統中,要實現分散式事務,無外乎那幾種解決方案。

一、兩階段提交(2PC)

和上一節中提到的資料庫XA事務一樣,兩階段提交就是使用XA協議的原理,我們可以從下面這個圖的流程來很容易的看出中間的一些比如commit和abort的細節。

兩階段提交這種解決方案屬於犧牲了一部分可用性來換取的一致性。在實現方面,在 .NET 中,可以藉助 TransactionScop 提供的 API 來程式設計實現分散式系統中的兩階段提交,比如WCF中就有實現這部分功能。不過在多伺服器之間,需要依賴於DTC來完成事務一致性,Windows下微軟搞的有MSDTC服務,Linux下就比較悲劇了。

另外說一句,TransactionScop 預設不能用於非同步方法之間事務一致,因為事務上下文是儲存於當前執行緒中的,所以如果是在非同步方法,需要顯式的傳遞事務上下文。

優點: 儘量保證了資料的強一致,適合對資料強一致要求很高的關鍵領域。(其實也不能100%保證強一致)

缺點: 實現複雜,犧牲了可用性,對效能影響較大,不適合高併發高效能場景,如果分散式系統跨介面呼叫,目前 .NET 界還沒有實現方案。

二、補償事務(TCC)

TCC 其實就是採用的補償機制,其核心思想是:

針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。

它分為三個階段:

·     Try 階段主要是對業務系統做檢測及資源預留

·     Confirm 階段主要是對業務系統做確認提交,Try階段執行成功並開始執行 Confirm階段時,預設 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

·     Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

舉個例子,假入 Bob 要向 Smith 轉賬,思路大概是:
我們有一個本地方法,裡面依次呼叫
1、首先在 Try 階段,要先呼叫遠端介面把 Smith 和 Bob 的錢給凍結起來。
2、在 Confirm 階段,執行遠端呼叫的轉賬的操作,轉賬成功進行解凍。
3、如果第2步執行成功,那麼轉賬成功,如果第二步執行失敗,則呼叫遠端凍結介面對應的解凍方法 (Cancel)。

優點: 跟2PC比起來,實現以及流程相對簡單了一些,但資料的一致性比2PC也要差一些

缺點: 缺點還是比較明顯的,在2,3步中都有可能失敗。TCC屬於應用層的一種補償方式,所以需要程式設計師在實現的時候多寫很多補償的程式碼,在一些場景中,一些業務流程可能用TCC不太好定義及處理。(在我的理解是,有點try,catch的思想。)

三、本地訊息表(非同步確保)

本地訊息表這種實現方式應該是業界使用最多的,其核心思想是將分散式事務拆分成本地事務進行處理,其基本的設計思想是將遠端分散式事務拆分成一系列的本地事務,這種思路是來源於ebay。我們可以從下面的流程圖中看出其中的一些細節:

基本思路就是:

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

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

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

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

優點: 一種非常經典的實現,避免了分散式事務,實現了最終一致性。在 .NET中有現成的解決方案。

缺點: 訊息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理

四、MQ 事務訊息

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

以阿里的 RocketMQ 中介軟體為例,其思路大致為:

第一階段Prepared訊息,會拿到訊息的地址。
第二階段執行本地事務,

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

也就是說在業務方法內要想訊息佇列提交兩次請求,一次傳送訊息和一次確認訊息。

如果確認訊息傳送失敗了,RocketMQ會定期掃描訊息叢集中的事務訊息,這時候發現了Prepared訊息,它會向訊息傳送者確認,所以生產方需要實現一個check介面,

RocketMQ會根據傳送端設定的策略來決定是回滾還是繼續傳送確認訊息。這樣就保證了訊息傳送與本地事務同時成功或同時失敗。

 

總結

通過本文我們瞭解到兩個分散式系統的理論,他們分別是CAP和BASE 理論,同時我們也總結並對比了幾種分散式分解方案的優缺點,分散式事務本身是一個技術難題,是沒有一種完美的方案應對所有場景的,具體還是要根據業務場景去抉擇吧。然後我們介紹了一種基於本地訊息的的分散式事務解決方案CAP。

介紹個場景:

4、分散式事務的應用場景

4.1、支付

最經典的場景就是支付了,一筆支付,是對買家賬戶進行扣款,同時對賣家賬戶進行加錢,這些操作必須在一個事務裡執行,要麼全部成功,要麼全部失敗。而對於買家賬戶屬於買家中心,對應的是買家資料庫,而賣家賬戶屬於賣家中心,對應的是賣家資料庫,對不同資料庫的操作必然需要引入分散式事務。

4.2、線上下單

買家在電商平臺下單,往往會涉及到兩個動作,一個是扣庫存,第二個是更新訂單狀態,庫存和訂單一般屬於不同的資料庫,需要使用分散式事務保證資料一致性。

5、常見的分散式事務解決方案

5.1、基於XA協議的兩階段提交

XA是一個分散式事務協議,由Tuxedo提出。XA中大致分為兩部分:事務管理器和本地資源管理器。其中本地資源管理器往往由資料庫實現,比如Oracle、DB2這些商業資料庫都實現了XA介面,而事務管理器作為全域性的排程者,負責各個本地資源的提交和回滾。XA實現分散式事務的原理如下:

總的來說,XA協議比較簡單,而且一旦商業資料庫實現了XA協議,使用分散式事務的成本也比較低。但是,XA也有致命的缺點,那就是效能不理想,特別是在交易下單鏈路,往往併發量很高,XA無法滿足高併發場景。XA目前在商業資料庫支援的比較理想,在mysql資料庫中支援的不太理想,mysql的XA實現,沒有記錄prepare階段日誌,主備切換回導致主庫與備庫資料不一致。許多nosql也沒有支援XA,這讓XA的應用場景變得非常狹隘。

5.2、訊息事務+最終一致性

所謂的訊息事務就是基於訊息中介軟體的兩階段提交,本質上是對訊息中介軟體的一種特殊利用,它是將本地事務和發訊息放在了一個分散式事務裡,保證要麼本地操作成功並且對外發訊息成功,要麼兩者都失敗,開源的RocketMQ就支援這一特性,具體原理如下:

1、A系統向訊息中介軟體傳送一條預備訊息
2、訊息中介軟體儲存預備訊息並返回成功
3、A執行本地事務
4、A傳送提交訊息給訊息中介軟體

通過以上4步完成了一個訊息事務。對於以上的4個步驟,每個步驟都可能產生錯誤,下面一一分析:

·        步驟一出錯,則整個事務失敗,不會執行A的本地操作

·        步驟二出錯,則整個事務失敗,不會執行A的本地操作

·        步驟三出錯,這時候需要回滾預備訊息,怎麼回滾?答案是A系統實現一個訊息中介軟體的回撥介面,訊息中介軟體會去不斷執行回撥介面,檢查A事務執行是否執行成功,如果失敗則回滾預備訊息

·        步驟四出錯,這時候A的本地事務是成功的,那麼訊息中介軟體要回滾A嗎?答案是不需要,其實通過回撥介面,訊息中介軟體能夠檢查到A執行成功了,這時候其實不需要A發提交訊息了,訊息中介軟體可以自己對訊息進行提交,從而完成整個訊息事務

基於訊息中介軟體的兩階段提交往往用在高併發場景下,將一個分散式事務拆成一個訊息事務(A系統的本地操作+發訊息)+B系統的本地操作,其中B系統的操作由訊息驅動,只要訊息事務成功,那麼A操作一定成功,訊息也一定發出來了,這時候B會收到訊息去執行本地操作,如果本地操作失敗,訊息會重投,直到B操作成功,這樣就變相地實現了A與B的分散式事務。原理如下:

雖然上面的方案能夠完成A和B的操作,但是A和B並不是嚴格一致的,而是最終一致的,我們在這裡犧牲了一致性(不是某一時刻就立刻一致的),換來了效能的大幅度提升。當然,這種玩法也是有風險的,如果B一直執行不成功,那麼一致性會被破壞,具體要不要玩,還是得看業務能夠承擔多少風險。

舉個例子,比如A訂單系統,B為庫存系統,正常是下單,減庫存。那麼如果按照二階段是,如果庫存系統因為併發而發生回滾,那麼A系統也一併回滾,這樣的可用性就很低。

如果加入這個訊息中介軟體,則認為A系統事務成功了,B系統也會事務成功,B不成功則再發送直到它成功即可。分散式事務是A下單,B減庫存,把分散式事務分為,A下單的本地事務+傳送訊息,然後B再減庫存的本地事務。

5.3、TCC程式設計模式

所謂的TCC程式設計模式,也是兩階段提交的一個變種。TCC提供了一個程式設計框架,將整個業務邏輯分為三塊:Try、Confirm和Cancel三個操作。以線上下單為例,Try階段會去扣庫存,Confirm階段則是去更新訂單狀態,如果更新訂單失敗,則進入Cancel階段,會去恢復庫存。總之,TCC就是通過程式碼人為實現了兩階段提交,不同的業務場景所寫的程式碼都不一樣,複雜度也不一樣,因此,這種模式並不能很好地被複用。

6、總結

分散式事務,本質上是對多個數據庫的事務進行統一控制,按照控制力度可以分為:不控制、部分控制和完全控制。不控制就是不引入分散式事務,部分控制就是各種變種的兩階段提交,包括上面提到的訊息事務+最終一致性、TCC模式,而完全控制就是完全實現兩階段提交。部分控制的好處是併發量和效能很好,缺點是資料一致性減弱了,完全控制則是犧牲了效能,保障了一致性,具體用哪種方式,最終還是取決於業務場景。作為技術人員,一定不能忘了技術是為業務服務的,不要為了技術而技術,針對不同業務進行技術選型也是一種很重要的能力!