淺談事務與一致性問題

在高并發(fā)場景下,分布式儲存和處理已經是常用手段。但分布式的結構勢必會帶來“不一致”的麻煩問題,而事務正是解決這一問題而引入的一種概念和方案。我們常把它當做并發(fā)操作的基本單位。

從MySQL事務說起(剛性事務)

提到事務,腦海里第一個反應當然是數據庫里的Transaction了。緊接著就是事務的四大特性:ACID (原子性,一致性,隔離性,持久性),所以我們先從這四大特性說起。

原子性

原子性是我們對事務最直觀的理解:事務就是一系列的操作,要么全部都執(zhí)行,要么全部都不執(zhí)行。

想要保證事務的原子性,就意味著需要在操作發(fā)生異常時,對該事務所有之前執(zhí)行過的操作進行回滾。

在MySQL中,這個回滾是通過回滾日志(Undo Log)實現的。簡單的說,回滾日志就是記錄了你所有操作的逆操作,在需要回滾時,就把這個事務的回滾日志里的操作全部執(zhí)行一次。

比如你的事務里每一個create其實都對應了一個效果跟其相反的delete語句,他們被記錄在回滾日志里,當事務發(fā)生異常觸發(fā)ROLLBACK時,就按照日志邏輯地將回滾日志里的操作全部執(zhí)行,從而達到“撤銷”操作的效果。

事務的狀態(tài)

宏觀上看事務是具有原子性的,是一個密不可分的最小單位。但是它是有幾種不同的狀態(tài)的:Active,Commited,Failed,它要么在執(zhí)行中,要么執(zhí)行成功,要么就失敗。

深入事務的內部,他就變?yōu)橐幌盗胁僮鞯募希辉倬哂性有粤?,包括了很多的中間狀態(tài),比如部分提交,參考如下的事務狀態(tài)圖:

[圖片上傳失敗...(image-30ed42-1517751460929)]

  • Active 事務的初始狀態(tài),表示正在執(zhí)行
  • Partially Commited 部分執(zhí)行,或者說在最后一條語句執(zhí)行后
  • Failed 發(fā)現操作異常,事務無法繼續(xù)執(zhí)行后
  • Commited 成功執(zhí)行整個事務
  • Aborted 事務被回滾,數據庫恢復到執(zhí)行前狀態(tài)后

并行事務的原子性

正常情況下事務都是并行執(zhí)行的,這就會出現很多復雜的新問題。

首先是事務依賴,舉一個直觀的例子來說明:

假設事務T1對數據A進行了讀寫,然后(T1還沒有執(zhí)行完)在同時,T2讀取了數據A,然后成功提交了事務。這時候T1發(fā)生了異常,進行回滾。我們可以看到事務T2是依賴于T1所修改的數據的,如果要保證T1的原子性,那就需要同時對T2進行回滾,但是它已經被提交了,我們沒法再回滾了,這種問題被稱為“不可恢復安排”。

為了避免這種情況的出現,在出現事務的依賴時,必須遵循以下的原則:

如果事務T2依賴于事務T2,那么T1必須在T2提交之前完成提交操作。

接下來我們還不得不面對級聯回滾,也就是出現了多個事務都依賴于事務A的時候,如果A回滾,那么這些事務必須也一并回滾。這會導致大量的工作撤回,至于這件事情如何處理才合適,我們會在后面介紹。

持久性

這是理解起來相對簡單的一個特性,持久性就是指,事務一旦被提交,那么數據一定會被寫入到數據庫中并持久儲存起來。

另外,當事務被提交后就無法再回滾,如果想要撤銷一個已經提交的事務,那就只能執(zhí)行一個效果與其相反的事務,這也是持久性的一種體現。關于這點,MySQL依然是通過日志實現的。

重做日志

重做日志由兩部分組成,一是內存中的重做日志緩沖區(qū),另一個是磁盤上的重做日志文件。

這個緩沖區(qū)和日志的關系跟我們日常IO中使用的buffer是差不多的:當我們在事務中嘗試對數據進行更改時,首先將數據從磁盤讀入內存,更新內存緩存的數據,然后會生成一條重做日志(本次修改的逆操作)緩存,放在重做日志緩沖區(qū)中。當事務真正提交時,再將剛才緩沖區(qū)中的日志寫入重做日志中做持久化保存,最后再把內存中的數據變動同步到磁盤上。

上面這個流程用圖片描述如下:

[圖片上傳失敗...(image-626f5a-1517751460929)]

再具體一點,InnoDB中,重做日志都是以512B的塊形式儲存的,因為磁盤的扇取也是512B,所以重做日志的寫入就保證了原子性,即便機器斷電也不會出現日志僅僅寫入一半而留下臟數據的情況。

另外需要注意的一點是,在原子性一節(jié)中提到的回滾日志也是需要持久化儲存的,因此他們也會創(chuàng)建對應的重做日志,在發(fā)生錯誤后,數據庫重啟時,會從重做日志中找出未被更新到的數據庫磁盤上的日志,重新執(zhí)行來滿足事務的持久性。

*事務日志

在數據庫系統(tǒng)中,事務的原子性和一致性是由事務日志實現的,在具體的實現上,使用的就是之前提到的回滾日志和重做日志,它們保證了兩點:

  • 發(fā)生錯誤或者需要回滾的事務能夠成功回滾(原子性)
  • 事務提交后,數據還沒來得及寫入磁盤就宕機時,重啟后能夠成功恢復數據(一致性)

在數據庫中這兩者往往一起工作,因此我們可以把他們看作一個整體。一條事務日志的內容可以抽象成下面這樣:

[圖片上傳失敗...(image-e7e2a2-1517751460929)]

一條記錄同時保存了對應數據修改前后的值,就可以非常方便的實現回滾和重做兩種功能。

隔離性

事務的隔離性會跟并發(fā)等相關概念聯系的非常密切,因為它主要就是為了保證并行事務處理能夠達到“互不干擾”的效果。

我們在一致性中討論過事務在并發(fā)情況下執(zhí)行時,可能發(fā)生的一系列問題:雖然單個事務執(zhí)行并沒有錯誤,但是它的執(zhí)行可能會牽連到其他事務的執(zhí)行,最終導致數據庫的整體一致性出現偏差。

談到這里我們就要看看事務之間的互相干擾都有哪些層級,也就是我們數據庫中非常重要的概念:

事務的隔離級別

事務的隔離級別,其實是數據庫對數據隔離性能的一種約束,選擇不同的隔離級別會影響數據一致性的程度,同時也會影響數據庫的操作性能。

標準SQL中定義了以下4種隔離級別:

  • 未提交讀

    使用查詢語句不會加鎖,可能會讀到未提交的行(臟讀)

  • 提交讀

    只對記錄加記錄鎖,而不會在記錄之間增加間隙鎖,所以允許新的記錄被插入到被鎖定記錄附近,在多次使用查詢語句時,可能會得到不同的結果(不可重復讀)

  • 可重復讀

    多次讀取同一范圍的數據會返回第一次查詢的快照,不會返回不同的數據行,但是可能發(fā)生幻讀

      幻讀 : 是指當事務不是獨立執(zhí)行時發(fā)生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。 同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發(fā)生操作第一個事務的用戶發(fā)現表中還有沒有修改的數據行,就好象 發(fā)生了幻覺一樣。
    
  • 串行化

    隱式地將全部的查詢語句都加上了共享鎖。

從上到下一致性逐漸增強,但是數據庫的讀寫性能也逐漸變差

大部分數據庫中使用提交讀作為默認的隔離級別,這是出于性能和一致性的平衡,而MySQL中則默認采用可重復讀作為配置。

對于開發(fā)者而言,不必去了解每個隔離級別具體的實現,但要能夠根據不同的場景選擇最合適的隔離級別。

隔離的實現

隔離的實現說到底其實是并發(fā)控制,因此不同隔離級別的實現,其實就是采用了不同的并發(fā)控制機制。

1.鎖

這個自然是最簡單的,也是相當常用的并發(fā)控制機制了。

不過在一個事務中,自然是不可能把整個數據庫都加鎖的,而是只對要訪問的數據加鎖(具體的粒度有行、表等)。而這些資源鎖也是理所當然地分為共享鎖(讀鎖)和互斥鎖(寫鎖)兩種。

讀鎖可以保證操作并發(fā)執(zhí)行而不受影響,寫鎖則保證了更新數據庫時不會受到其他事務的干擾。

2.時間戳

用時間戳實現隔離性,需要為記錄配置兩個字段

  • 讀時間戳:用于保存所有訪問該記錄的事務中的最大時間戳(最后讀取時間)
  • 寫時間戳:用于保存將記錄改到當前值的事務的時間戳(最后修改時間)

這樣的事務在并行執(zhí)行時,用的是樂觀鎖,先任由事務對數據進行修改,在寫回去的時候在判斷記錄的時間戳有沒有修改,如果沒有被修改,就寫入,否則,就生成一個新的時間戳并再次嘗試更新數據。

PostgreSQL就使用了這種思想來控制事務。

3.多版本和快照隔離

通過維護多個版本的數據,數據庫便可以允許事務并發(fā)執(zhí)行遇到互斥鎖時,轉而讀取舊版本的數據快照。這樣就能顯著地提升讀取的性能。我們簡稱這一手段為MVCC。

級聯回滾

之前在討論原子性問題時,討論過級聯回滾的問題,那是因為事務之間產生了依賴而導致的。因此我們將事務隔離之后,就不會再產生需要級聯回滾的場景了。

比如一個事務寫入了A數據,那么這時候是需要加共享鎖的,因此其它的事務無法讀取A,當事務A回滾時不用考慮對其它事務的影響,因為其它的事務并不可能讀到數據。

一致性

好了,這時候我們終于回歸到了本文所想討論的主題上來?!耙恢滦浴痹跀祿祛I域有兩個意義,一個是ACID中的C,另一個是CAP的C,前者是我們經常討論的,也是普遍意義上的數據庫事務一致性,而后一個將是之后會展開討論的,有關分布式事務的一致性。

ACID

事務的一致性定義基本可以理解為是事務對數據完整性約束的遵循。這些約束可能包括主鍵約束、外鍵約束或是一些用戶自定義約束。事務執(zhí)行的前后都是合法的數據狀態(tài),不會違背任何的數據完整性,這就是“一致”的意思。

當然這個含義中也隱含著對開發(fā)者的要求,就是不能寫出錯誤的事務邏輯,比如銀行的轉賬不能只加錢不減錢,這是應用層面的一致性要求。

CAP

CAP定理是分布式系統(tǒng)理論的基礎。CAP告訴我們,對于一個分布式系統(tǒng)(或者由于網絡隔離等原因產生的分區(qū)系統(tǒng)),它無法同時保證一致性、可用性和分區(qū)容忍性,而是必須要舍棄其中的一個。

p.s. 對于分布式系統(tǒng)一般我們是不可能舍棄分區(qū)容忍性的(因為分區(qū)的情況是無法避免的),所以一般是根據業(yè)務,在一致性和可用性中二選一。

這里說的一致性,具體在數據庫上,就是分布式數據庫中,每一個節(jié)點對于同一個數據必須有相同的拷貝(每個庫里的同一個數據內容必須是一致的)。

分布式事務

現在我們來看一看,當數據分布式儲存后,操作所帶來的一些問題。

眾所周知,現在大型服務出于性能和容災的考慮,都會使用分布式的服務架構,這意味著一個服務會有多個數據庫,分開儲存不同的數據,這種情況下就很容易出現數據不一致的問題了,一個最簡單的例子:

A要B給轉100元。但是A和B的記錄被分在了不同的數據庫實例上,如果這時候執(zhí)行的某個事務中途出現了bug,如果沒有一個好的處理方式,回滾將會是一件難以面對的事情。

所以我們可以看到,在分布式環(huán)境下,事務的設計方案變得更加復雜,也更加重要了,下面我們來談談分布式事務的一些常見實現方式:

兩階段提交(2PC)

原理

兩階段提交是一種提交協議,在這種協議下,事務的實現被拆分成了幾個不同的模塊,一般分為協調器和若干的事務執(zhí)行者,如下圖:

[圖片上傳失敗...(image-5fa697-1517751460929)]

在分布式系統(tǒng)中,每個節(jié)點雖然可以知道自己操作是否成功,但是卻無法得知其他節(jié)點上操作是否成功,因此當一個事務跨越了多個節(jié)點的時候,就需要一個協調者,能夠掌控到所有節(jié)點的執(zhí)行情況,進而保證事務的ACID特性。

現在我們來分析2PC協議條件下,轉賬問題是如何被解決的(我們假設A是你的支付寶余額,B是你的余額寶)。

  1. A發(fā)起請求到協調器,協調器開始工作

  2. 準備憑證

    • 協調器將prepare信息寫到本地日志,這就是回滾日志了。
    • 向所有的參與者發(fā)起prepare信息,當然對于不同的執(zhí)行者,這個prepare信息是不同的,這取決于他們的數據實例上要發(fā)生什么樣的變動,比如這個例子中,A得到的prepare消息是通知支付寶余額數據庫扣除100元,而B得到的prepare消息是通知余額寶數據庫增加100元。
  3. 執(zhí)行者收到prepare消息之后,執(zhí)行本機的具體事務,但不會commit,如果成功則向協調者發(fā)送yes回執(zhí),否則發(fā)送no

  4. 協調者判斷收集到的所有回執(zhí),如果均為yes,就向所有的執(zhí)行者發(fā)送commit消息,執(zhí)行器收到該消息后就會正式執(zhí)行提交。反之,如果收到任何一個no,就向所有的實行者發(fā)送abort消息,執(zhí)行器收到后會放棄提交并回滾相應的改動。

協調器上保存的回滾日志,可以用于某個執(zhí)行器失敗后恢復的工作的場景,此時執(zhí)行器可能會再次向協調器發(fā)送回執(zhí)來確定自己的執(zhí)行狀態(tài)。

問題

2PC實現的思路倒是很簡單,不過這個思路中存在著幾個非常嚴重的問題,因此幾乎不被使用:

  1. 涉及多次節(jié)點間的通信,假設網絡延遲比較高,通信時長基本是不可忍受的
  2. 事務時間變長了,也意味著資源上鎖的時間變長了,性能大打折扣
  3. 如果參與者多了,協調器的工作效率會下降,而整個流程也變得復雜起來

其實分布式事務的種種實現方案基本都借鑒了2PC的思路,但很快人們就發(fā)現一個問題,在分布式的系統(tǒng)中,如果仍然采用事務模型來進行數據的修改,性能將受到不可避免的影響,這在高并發(fā)的場景下是不能接受的。

最終一致性(柔性事務)

剛才我們講了分布式事務在高并發(fā)場景下的敗北,其實根據CAP原則我們很容易明白,想要保證可用性的同時保證一致性是不可能的,于是現在大多數的分布式系統(tǒng)中都對一致性做出了妥協:

我們不追求整個操作過程中每一時刻的一致性(強一致性),轉而追求最終結果的一致性(最終一致性)。

也即是說,在整個事務執(zhí)行的流程中,我們是可以接受的短暫的數據不一致的,只要最后的結果沒問題就行。

至此,我們對于事務的研究,從滿足ACID的剛性事務,拓展到BASE(基本可用,軟狀態(tài),最終一致性)的柔性事務。

BASE

BASE原則是在分布式場景下,為了保證高可用性,而做出的一種“妥協性”思想??偟膩碚f是允許局部的錯誤和故障,但要保證全局的穩(wěn)定。事實上當前大多數的分布式系統(tǒng),或者說大多數的大型系統(tǒng)里,都在運用這種思想了。

在展開柔性事務之前,我們先來補充一些基礎知識。

重試與冪等

在接下來講到的各種思路中,我們都無法避免一個問題,那就是接口調用或者說操作的失敗,分布式情況下系統(tǒng)的狀態(tài)往往不如單機條件下確定,所以可能經常需要重試,而不是一失敗就回滾。

因此我們必須盡可能的避免重試對系統(tǒng)穩(wěn)定性和性能的影響,于是有了冪等這個概念:

冪等

  • 數學定義:f(x) = f(f(x))的性質
  • 編程定義:對同一個系統(tǒng),使用同樣的條件,一次請求和重復的多次請求對系統(tǒng)資源的影響是一致的

然后我們需要探討一下保證冪等常用的思路,我們以微博點贊這個操作為實際例子來看一下(點贊是不能重復的):

  1. MVCC

    數據更新時需要比較持有數據的版本號,版本號不一致的話是無法操作成功的。
    每個版本只有一次執(zhí)行成功的機會,一旦失敗了就要重新獲取版本號。

    這樣每次點贊操作都對應著一個不同的版本號,即便失敗重復嘗試,也不會出現點贊數錯誤增加或減少的情況。

  2. 去重

    這個主要依賴數據庫的索引唯一性(鍵),以點贊操作為例,可以對[user_id,weibo_id]這個組合做一張“點贊操作表”,如果成功點贊,就添加一條新記錄。

    如果出現了錯誤的重試,因為表的索引是唯一的,已經有了記錄自后就不會再次插入,自然也就不會出現錯誤的情況了。

異步確保

2PC的處理過程中一個很大的問題是,存在大量的同步等待,這便意味著操作之間的強耦合,一旦發(fā)生了失敗或是超時,造成的影響往往是災難性的。但是分布式情況下,超時和失敗又是很可能出現的情況,所以2PC手段沒法保證系統(tǒng)的可用性。

那么怎么優(yōu)化呢?可以將操作解耦,使用消息隊列(或者某種可靠的通信機制)來連接不同的實例上的操作。這樣的通信機制使操作異步化,于是我們還需要一個能夠確保消息執(zhí)行成功的確保機制,以上兩點的綜合就是現在最常用的柔性事務解決方案,我們暫且叫它“異步確?!保ㄒ驗檫@種方案并非有一個統(tǒng)一的叫法),核心思路其實就是:用消息隊列保證最終一致性。

下面我們一步一步深入,了解這種方案的基本思想和流程。

問題

我們依然使用經典的轉賬問題來展開討論:A要向B轉100元,但是A和B的賬戶在不同的實例上存儲。

用異步確保的思想,操作的流程應該如此處理:

  1. A所在的實例扣除A賬戶100元
  2. 向B所在的實例發(fā)送操作消息,通知它給B的賬戶增加100元

這是一個很理想的情況,其實我們有很多的問題要處理。

首先是原子性,其實很容易發(fā)現,無論順序如何,如果1和2這兩個操作有任何一個失敗了,那另一個操作也必然變得沒有意義,所以必須保證1和2這兩個操作的整體原子性。

這里很多人會想,直接利用剛性事務的ACID特性,把1和2放在同一個事務里不就ok了。但這是不可能的,原因如下:

  • 網絡的2將軍問題:發(fā)送消息如果失敗了,發(fā)送方并沒有辦法知道,是接收方沒收到消息,還是接收方返回響應的時候出現了故障,其實已經收到了?
  • 在DB事務里插入網絡操作,如果出現延遲,會導致事務執(zhí)行時間變長,對DB性能影響極大,嚴重的話可能block整個DB。

所以事情沒那么簡單,所以在我們得做不少額外的工作才能解決這個問題,下面是現在常用的解決思路:消息表。

先說生產方(A的實例)

  1. 生產方添加一張消息表,用于記錄發(fā)送的消息以及消息的回執(zhí)等內容。

  2. 生產者在向消費者發(fā)送業(yè)務操作數據時,同時也要在消息表里增加一個消息記錄,這兩個都是對生產者DB的操作,我們要把它們放在同一個事務里來保證一致性。舉個例子,轉賬問題在A端上這個操作的sql就是這樣的(有點隨意,會意即可):

    begin transaction;
    update account set amount = ($amount - 100) where user = A;
    insert into message values('b','account','-100');
    end transaction;
    
  3. 對于這張消息表,我們需要一個維護者,它的職責是,不斷地把表中未發(fā)送的消息放入消息隊列,另外檢測消息的執(zhí)行是否超時或失敗,如果遇到這種異常情況,就進行重試。注意:允許消息重復,但是不能丟失,順序也不會打亂。

再說消費方(B的實例)

  1. 消費方的接口(我們稱為下游接口),必須實現冪等。這是因為生產方可能會發(fā)來很多的重試消息,我們必須保證重試操作不會對系統(tǒng)產生不良影響。如果之前說的冪等手段不適用,可以簡單的為消費方準備一個判重表,利用判重表的Insert操作來實現冪等(如果這么做,請注意在業(yè)務中保證消費操作和Insert判重表操作的原子性)。

  2. 消費方完成操作后,利用消息隊列向生產方發(fā)送確認消息就ok。

可以看到這個實現方案對于業(yè)務的生產方來說,需要維護很多額外的操作,尤其是需要設計維護消息表,可能還要做后臺任務處理等,某種程度上這會增加業(yè)務端不必要的邏輯耦合,以及性能負擔。

簡要工作流程如下圖所示:

[圖片上傳失敗...(image-ec7c6-1517751460929)]

事務消息

正如上文所說,異步確保的思路中,大多數操作其實與業(yè)務無關,可以封裝到消息隊列中去。于是產生了“事務消息”這一概念,也就衍生了很多能夠很好的支持分布式事務消息相關操作的消息隊列或者中間件,如RocketMQ和Notify。

我們來看看事務消息是如何優(yōu)化和整合異步確保的邏輯的。

首先,把消息發(fā)送分成了2個階段:準備和確認階段,于是生產方步驟變?yōu)槿缦?步:

  1. 發(fā)送prepared消息給MQ
  2. 執(zhí)行本地事務
  3. 根據本地事務執(zhí)行結果,確認或者取消prepared消息

這里有一個問題,就是如果1和2失敗了,還是很容易回滾和取消的,但是第三步失敗或者超時了,要怎么做呢?

以RocketMQ為例,MQ會定期地掃描所有的prepared消息,詢問發(fā)送方,到底是要確認發(fā)送這條消息,還是要取消這條消息?這點底層是通過讓生產方實現一個約定好的Check接口來實現的,有點像訂閱者模式。

我們可以看出來,異步回調中,掃描消息表,確認或重發(fā)消息這個步驟被消息隊列實現了,減少了業(yè)務方開發(fā)的難度。

對于消費方,事務消息支持重試的特性,也就是說不必生產者去主動發(fā)起重試消息,消息隊列可以自動幫你重試這些操作,可以說是非常解放生產力了。

如果有極端情況,比如消費端異常,無論怎么重試都失敗,是否要回滾呢?其實最好的辦法就是人工介入,人工去處理這種概率極低的case,比開發(fā)一個高復雜的自動回滾系統(tǒng)要可靠的多,也更簡單。

事務補償(TCC)

除了比較常用的異步確保,我們再介紹一種常見的實現柔性事務的思路,稱為事務補償。

總結之前的內容,我們不難發(fā)現,分布式事務的難點在于,一方執(zhí)行事務成功之后,無法確定其他參與方對應的事務是否能夠成功(除非犧牲系統(tǒng)可用性)。

事務補償的想法和回滾日志有些類似。既然我們沒辦法同時保證所有的參與方事務執(zhí)行都成功,不如就讓他們隨意執(zhí)行,誰成功了就提交本地事務。但是每個參與方的每個操作,都要注冊(注意是注冊,不是自動生成)一個對應的補償操作,這個補償操作由人為定義,用于撤銷已執(zhí)行事務帶來的影響。

當某一方的事務執(zhí)行失敗時,所有已經成功提交了事務的參與方,需要按照順序(提交的倒序)去執(zhí)行各自的補償事務,來將整個系統(tǒng)“回滾”到之前的狀態(tài)。

補償型思路的一個典型實現是TCC(Try-Confirm-Cancel)事務,其實說是事務,不如說是一種業(yè)務模式,因為Try,Confirm,Cancel這三個操作都必須由業(yè)務方實現。

  • Try:資源預留&鎖定。事務發(fā)起方將調用服務提供方的Try方法來鎖定業(yè)務所需要的所有資源。
  • Confirm:確認執(zhí)行業(yè)務邏輯操作。這里使用的資源一定都是在Try中預留的資源,Try + Confirm 組合起來是一次完整的業(yè)務邏輯。
  • Cancel:取消執(zhí)行業(yè)務邏輯。這里和普通的補償性事務不同,因為Try階段只是預留資源,并未真正執(zhí)行操作,因此取消操作只需要釋放Try階段預留的資源,而不需要執(zhí)行數據庫操作來補償。

其實TCC可以認為是應用層的2CP協議。網上關于TCC的相關邏輯說法很多,也比較混亂,這里找到一個比較通俗普遍的例子來解釋TCC的流程。當然實際應用中,根據業(yè)務的場景不同,TCC的實現也不同:它只是一種思路,而并非是一種規(guī)范。

例子仍然是轉賬問題,我們把范圍稍微擴大一點,現在我們有三個用戶A,B,C分別位于三個不同的數據庫實例上,現在A,B要分別向C轉賬40元(一共80元)。

  1. Try階段:嘗試執(zhí)行。

    • 業(yè)務檢查(一致性):檢查A,B,C的賬戶狀態(tài)是否正常,以及A,B的賬戶余額是否都不低于40元。
    • 預留資源(準隔離性):賬戶A、B的余額均凍結40元。這樣保證其他并發(fā)事務不會把A、B的余額扣成負數。
  2. Confirm階段:確認執(zhí)行。

    • 真正執(zhí)行事務:執(zhí)行實際的業(yè)務操作:A、B賬戶減少40元,C賬戶增加80元。(這一步還是需要消息傳遞機制)
  3. Cancel階段:取消執(zhí)行。

    • 釋放A,B賬戶上被成功凍結的金額。

小結

分布式的結構下,事務的實現依然沒有一個放之四海而皆準的標準。但是可以看到一個統(tǒng)一的原則,那就是盡可能的讓服務變得更具有彈性,能夠靈活地應對多種情況。

總的來說,分布式事務更大的挑戰(zhàn)在于,相關業(yè)務邏輯的開發(fā)思路:可用性與一致性的平衡。

參考文章

本文是學習和整理自下列文章:

如有侵權,請聯系我刪除相關內容。如有錯誤,歡迎評論糾正。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容