天天看點

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

文章目錄

  • ​​1.分布式事務概念​​
  • ​​1.1.問題引入​​
  • ​​1.2.加入沒有分布式事務​​
  • ​​1.3.小結​​
  • ​​2.柔性事務 vs 剛性事務​​
  • ​​3.兩階段送出​​
  • ​​3.1.兩階段送出(2PC)​​
  • ​​3.1.1.第一階段​​
  • ​​3.3.2.第二階段​​
  • ​​3.3.3.失敗的場景​​
  • ​​3.2.小結​​
  • ​​3.3.兩階段送出(2PC)不足​​
  • ​​3.3.1.性能問題​​
  • ​​3.2.2.協調者單點故障問題​​
  • ​​3.2.2.丢失消息導緻的不一緻問題​​
  • ​​4.TCC (Try-Confirm-Cancle TCC兩階段補償性方案)​​
  • ​​4.1.TCC的機制​​
  • ​​4.2.使用場景:​​
  • ​​5.異步確定型​​
  • ​​6.分布式事務常見架構選擇​​
  • ​​6.1.GTS–分布式事務解決方案​​
  • ​​6.2.TX-LCN–分布式事務解決方案​​
  • ​​6.3.seata​​

1.分布式事務概念

1.1.問題引入

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

前陣子從支付寶轉賬1萬塊錢到餘額寶,這是日常生活的一件普通小事,但作為網際網路研發人員的職業病,我就思考支付寶扣除1萬之後,如果系統挂掉怎麼辦,這時餘額寶賬戶并沒有增加1萬,資料就會出現不一緻狀況了。

上述場景在各個類型的系統中都能找到相似影子,比如在電商系統中,當有使用者下單後,除了在訂單表插入一條記錄外,對應商品表的這個商品數量必須減1吧,怎麼保證?!

在搜尋廣告系統中,當使用者點選某廣告後,除了在點選事件表中增加一條記錄外,還得去商家賬戶表中找到這個商家并扣除廣告費吧,怎麼保證?!等等,相信大家或多或多少都能碰到相似情景。

本質上問題可以抽象為:當一個表資料操作成功後,怎麼保證另一個表的資料也必須要操作成功。當然啦,這兩個資料表不在一個資料源中

1.2.加入沒有分布式事務

在一系列微服務系統當中,假如不存在分布式事務,會發生什麼呢?讓我們以網際網路中常用的交易業務為例子:

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

上圖中包含了庫存和訂單兩個獨立的微服務,每個微服務維護了自己的資料庫。在交易系統的業務邏輯中,一個商品在下單之前需要先調用庫存服務,進行扣除庫存,再調用訂單服務,建立訂單記錄。

正常情況下,兩個資料庫各自更新成功,兩邊資料維持着一緻性。

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

但是,在非正常情況下,有可能庫存的扣減完成了,随後的訂單記錄卻因為某些原因插入失敗。這個時候,兩邊資料就失去了應有的一緻性。

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

1.3.小結

事務: 指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行.

本地事務: SqlSessionfactory --> 一個資料庫範圍類事務管理.

分布式事務:跨了多個資料庫事務管理,在微服務架構每個服務都有自己資料庫,在微服務架構中必然要用到分布式事務.

2.柔性事務 vs 剛性事務

剛性事務是指嚴格遵循ACID原則的事務, 例如單機環境下的資料庫事務.

柔性事務是指遵循BASE理論的事務, 通常用在分布式環境中, 常見的實作方式有:

①兩階段送出(2PC)
②TCC補償型送出
③基于消息的異步確定型
④最大努力通知型      
通常對本地事務采用剛性事務, 分布式事務使用柔性事務.

3.兩階段送出

分布式事務用于在分布式系統中保證不同節點之間的資料一緻性。分布式事務的實作有很多種,最具有代表性的是由Oracle Tuxedo系統提出的XA分布式事務協定。

XA協定包含兩階段送出(2PC),這裡我們重點介紹兩階段送出的具體過程

3.1.兩階段送出(2PC)

兩階段送出(Two Phase Commit, 2PC), 具有強一緻性, 是系統的一種典型實作.

兩階段送出, 常見的标準是XA, JTA等. 例如Oracle的資料庫支援XA.

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

在魔獸世界這款遊戲中,副本組團打BOSS的時候,為了更友善隊長與隊員們之間的協作,隊長可以發起一個“就位确認”的操作:

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

當隊員收到就位确認提示後,如果已經就位,就選擇“是”,如果還沒就位,就選擇“否”。

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

當隊長收到了所有人的就位确認,就會向所有隊員們釋出消息,告訴他們開始打BOSS。

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

相應的,在隊長發起就位确認的時候,有可能某些隊員還并沒有就位:

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

以上就是魔獸世界當中組團打BOSS的确認流程。這個流程和XA分布式事務協定的兩階段送出非常相似。

那麼XA協定究竟是什麼樣子呢?在XA協定中包含着兩個角色:事務協調者和事務參與者。讓我們來看一看他們之間的互動流程:

3.1.1.第一階段
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

在XA分布式事務的第一階段,作為事務協調者的節點會首先向所有的參與者節點發送Prepare請求。

在接到Prepare請求之後,每一個參與者節點會各自執行與事務有關的資料更新,寫入Undo Log和Redo Log。如果參與者執行成功,暫時不送出事務,而是向事務協調節點傳回“完成”消息。

當事務協調者接到了所有參與者的傳回消息,整個分布式事務将會進入第二階段。

3.3.2.第二階段
《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

在XA分布式事務的第二階段,如果事務協調節點在之前所收到都是正向傳回,那麼它将會向所有事務參與者發出Commit請求。

接到Commit請求之後,事務參與者節點會各自進行本地的事務送出,并釋放鎖資源。當本地事務完成送出後,将會向事務協調者傳回“完成”消息。

當事務協調者接收到所有事務參與者的“完成”回報,整個分布式事務完成。

3.3.3.失敗的場景

 第一階段

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

 第二階段

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

在XA的第一階段,如果某個事務參與者回報失敗消息,說明該節點的本地事務執行不成功,必須復原。

于是在第二階段,事務協調節點向所有的事務參與者發送Abort請求。接收到Abort請求之後,各個事務參與者節點需要在本地進行事務的復原操作,復原操作依照Undo Log來進行。

以上就是XA兩階段送出協定的詳細過程

3.2.小結

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
  • 1) 我們的應用程式(client)發起一個開始請求到TC;
  • 2) TC先将消息寫到本地日志,之後向所有的Si發起消息。以支付寶轉賬到餘額寶為例,TC給A的prepare消息是通知支付寶資料庫相應賬目扣款1萬,TC給B的prepare消息是通知餘額寶資料庫相應賬目增加1w。為什麼在執行任務前需要先寫本地日志,主要是為了故障後恢複用,本地日志起到現實生活中憑證 的效果,如果沒有本地日志(憑證),出問題容易死無對證;
  • 3) Si收到消息後,執行具體本機事務,但不會進行commit,如果成功傳回,不成功傳回。同理,傳回前都應把要傳回的消息寫到日志裡,當作憑證。
  • 4) TC收集所有執行器傳回的消息,如果所有執行器都傳回yes,那麼給所有執行器發生送commit消息,執行器收到commit後執行本地事務的commit操作;如果有任一個執行器傳回no,那麼給所有執行器發送abort消息,執行器收到abort消息後執行事務abort操作。
注:TC或Si把發送或接收到的消息先寫到日志裡,主要是為了故障後恢複用。如某一Si從故障中恢複後,先檢查本機的日志,如果已收到,則送出,如果則復原。如果是,則再向TC詢問一下,确定下一步。如果什麼都沒有,則很可能在階段Si就崩潰了,是以需要復原。

現如今實作基于兩階段送出的分布式事務也沒那麼困難了,如果使用java,那麼可以使用開源軟體atomikos(http://www.atomikos.com/)來快速實作。

3.3.兩階段送出(2PC)不足

不過但凡使用過的上述兩階段送出的同學都可以發現性能實在是太差,根本不适合高并發的系統。為什麼?

3.3.1.性能問題

XA協定遵循強一緻性。在事務執行過程中,各個節點占用着資料庫資源,隻有當所有節點準備完畢,事務協調者才會通知送出,參與者送出後釋放資源。這樣的過程有着非常明顯的性能問題。

3.2.2.協調者單點故障問題

事務協調者是整個XA模型的核心,一旦事務協調者節點挂掉,參與者收不到送出或是復原通知,參與者會一直處于中間狀态無法完成事務。

3.2.2.丢失消息導緻的不一緻問題

在XA協定的第二個階段,如果發生局部網絡問題,一部分事務參與者收到了送出消息,另一部分事務參與者沒收到送出消息,那麼就導緻了節點之間資料的不一緻。

4.TCC (Try-Confirm-Cancle TCC兩階段補償性方案)

TCC事務的出現正是為了解決應用拆分帶來的跨應用業務操作原子性的問題。當然,由于正常的XA事務(2PC,2 Phase Commit, 兩階段送出)性能上不盡如人意,也有通過TCC事務來解決資料庫拆分的使用場景。

4.1.TCC的機制

個參與者需要實作3個操作:Try、Confirm 和 Cancel,3個操作對應2個階段,Try 方法是一階段的資源檢測和預留階段,Confirm 和 Cancel 對應二階段的送出和復原。

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

圖中,事務開啟的時候,由發起方去觸發一階段的方法,然後根據各個參與者的傳回狀态,決定二階段是調 Confirm 還是 Cancel 方法。

我們先套一個業務場景進去,如下圖所示

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

那頁面點了支付按鈕,調用支付服務,那我們背景要實作下面三個步驟

[1] 訂單服務-修改訂單狀态
[2] 賬戶服務-扣減金錢
[3] 庫存服務-扣減庫存      

達到事務的效果,要麼一起成功,要麼一起失敗!就要采取TCC分布式事務方案!

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案
TCC又可以被稱為兩階段補償事務,第一階段try隻是預留資源,第二階段要明确的告訴服務提供者,這個資源你到底要不要,對應第二階段的confirm/cancel,用來清除第一階段的影響,是以叫補償型事務。

再打個比方,說TCC太高大上是吧,講RM中的prepare、commit、rollback接口,總知道吧。可以類比的這麼了解

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

那差别在哪呢?

rollback、commit、prepare,站在開發者層面是感覺不到的,資料庫幫你做了資源的操作!

而try、confirm、cancel,站在開發者層面是能感覺到的,這三個方法的業務邏輯,即對資源的操作,開發者是要自己去實作的!

好,下面套入我們的場景,怎麼做呢。比如,你的訂單服務中本來隻有一個接口

//修改代碼狀态
orderClient.updateStatus();      

都要拆為三個接口,即

orderClient.tryUpateStatus();
orderClient.confirmUpateStatus();
orderClient.cancelUpateStatus();      
注意了:面試官如果問你,TCC有什麼缺點?這就是很嚴重的缺點,對代碼入侵性大!每套業務邏輯、都要按try(請求資源)、confirm(操作資源)、cancel(取消資源),拆分為三個接口!

具體每個階段,每個服務業務邏輯是什麼樣的呢?

假設,庫存數量本來是50,那麼可銷售庫存也是50。賬戶餘額為50,可用餘額也為50。使用者下單,買了1個單價為1元的商品。流程如下:

Try階段:

訂單服務:修改訂單的狀态為【支付中】
賬戶服務:賬戶餘額不變,可用餘額減1,然後将1這個數字當機在一個單獨的字段裡
庫存服務:庫存數量不變,可銷售庫存減1,然後将1這個數字當機在一個單獨的字段裡      

confirm階段

訂單服務:修改訂單的狀态為【支付完成】
賬戶服務:賬戶餘額變為(目前值減當機字段的值),可用餘額不變(Try階段減過了),當機字段清0。
庫存服務:庫存變為(目前值減當機字段的值),可銷售庫存不變(Try階段減過了),當機字段清0。      

cancel階段

訂單服務:修改訂單的狀态為【未支付】
賬戶服務:賬戶餘額不變,可用餘額變為(目前值加當機字段的值),當機字段清0。
庫存服務:庫存不變,可銷售庫存變為(目前值加當機字段的值),當機字段清0。      

僞代碼

接下來從代碼程式來說明,為了便于示範,将入參略去。

本來,你支付服務的代碼是長下面這樣的

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

那麼,用上TCC模型後,代碼變成下面這樣

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

注意了,這種寫法其實嚴格上來說,不是不行。看你業務場景,因為存在一些瑕疵,看你自己有沒辦法接受

(1)cancel或者confirm出現異常了,你怎麼處理?

例如在cancel階段執行如下三行代碼

orderClient.cancelUpdateStatus();
accountClient.cancelDecrease();
repositoryClient.cancelDecrease();      

你第二行出現異常了,第三行沒跑就退出了,怎麼辦?你要對此進行業務補償!

(2)大量邏輯重複

你看啊,我們的執行架構其實是這樣的

try{
    xxclient.try();
}catch(Throwable t){
    xxclient.cancel();
    throw t;
}
xxclient.confirm();      

有沒辦法讓這個架子交給架構去執行,我們告訴架構,你在每個階段要執行哪些方法就好!

是以,需要引入TCC分布式事務架構,事務的Try、Confirm、Cancel三個狀态交給架構來感覺!你隻要告訴架構,Try要執行啥,Confirm要執行啥,Cancel要執行啥!如果Cancel過程出現異常了,架構有内部的補償措施給你恢複資料!

以分布式tcc架構​

​hmily​

​​為例,如果出現cancel異常或者confirm異常的情況,在try階段會儲存好日志,Hmily有内置的排程線程池來進行恢複,不用擔心。

那hmily,怎麼感覺狀态的呢?也很簡單,就是切面程式設計,核心邏輯如下幾行

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

我們在使用過程中,隻要通過@Tcc注解告訴架構confirm方法執行啥,cancel方法執行啥即可!其他的交給架構幫你處理!

4.2.使用場景:

嚴格一緻性
  執行時間短
  實時性要求高      

5.異步確定型

一個業務場景,也是很常見的一個異步調用場景:

支付寶往餘額寶轉錢      

即将服務A假設為支付寶,服務B假設為餘額寶。于是呢,我們的支付寶往餘額寶轉100塊錢是怎麼做的呢?特别容易,借助消息隊列即可,如下圖所示

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

一緻性解決

OK,上面這一版有一個緻命的問題!如下所示

事務開始
(1)給支付寶賬戶zhangsan,扣100元
(2)将(給餘額寶賬戶zhangsan,加100元)封裝為消息,發送給消息隊列
事務結束      

敢問你,如何保證第一步和第二步是在同一個事務裡完成的。換句話說,第一步操作的是資料庫,第二步操作的是一個消息隊列,你如何保證這兩步之間的一緻性?

記住了,任何涉及到資料庫和中間件之間的業務邏輯操作,都需要考慮二者之間的一緻性。比如,你先操作了資料庫,再操作緩存,資料庫和緩存之間一緻性如何解決?好吧,改變思路,加一張事務表,如下圖所示

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

注意了,此時事務的内容為

事務開始
(1)給支付寶賬戶zhangsan,扣100元
(2)給事件表插入一條記錄
事務結束      

此時是對同一資料庫的兩張表操作,是以可以用資料庫的事務進行保證。另外,起一個定時程式,定時掃描事務表,發現一個狀态為’UNFINISHED’的事件,就進行封裝為消息,發送到消息中間件,然後将狀态改為’FINISHED’.

幂等性解決

注意了,這一版還存在一個幂等性問題!

仔細看,定時程式做了如下三個操作

(1)定時掃描事務表,發現一個狀态為'UNFINISHED'的事件
(2)将事件資訊,封裝為消息,發送到消息中間件
(3)将事件狀态改為'FINISHED'      

OK,假設在步驟(2)的時候,發送完消息體,還未執行步驟(3),定時程式陣亡了!然後重新開機定時程式,發現剛那個事務的狀态依然為’UNFINISHED’,是以重新發送。這樣,就會出現重複消費問題。是以,幂等性也是需要保證的!

在消費者端,也維護一個帶主鍵的表,可以選txid為主鍵,如下圖所示

《SpringCloud Alibaba 微服務架構》專題(十九)-分布式事務解決方案

如果一旦出現重複消費,則在事務裡直接報出主鍵沖突錯誤,進而保證了幂等性!

6.分布式事務常見架構選擇

6.1.GTS–分布式事務解決方案

GTS是一款分布式事務中間件,由阿裡巴巴中間件部門研發,可以為微服務架構中的分布式事務提供一站式解決方案。

GTS的核心優勢:

性能超強

GTS通過大量創新,解決了事務ACID特性與高性能、高可用、低侵入不可兼得的問題。單事務分支的平均響應時間在2ms左右,3台伺服器組成的叢集可以支撐3萬TPS以上的分布式事務請求。

應用侵入性極低

GTS對業務低侵入,業務代碼最少隻需要添加一行注解(@TxcTransaction)聲明事務即可。業務與事務分離,将微服務從事務中解放出來,微服務關注于業務本身,不再需要考慮反向接口、幂等、復原政策等複雜問題,極大降低了微服務開發的難度與工作量。

完整解決方案

GTS支援多種主流的服務架構,包括EDAS,Dubbo,Spring Cloud等。

有些情況下,應用需要調用第三方系統的接口,而第三方系統沒有接入GTS。此時需要用到GTS的MT模式。GTS的MT模式可以等價于TCC模式,使用者可以根據自身業務需求自定義每個事務階段的具體行為。MT模式提供了更多的靈活性,可能性,以達到特殊場景下的自定義優化及特殊功能的實作。

容錯能力強

GTS解決了XA事務協調器單點問題,實作真正的高可用,可以保證各種異常情況下的嚴格資料一緻。

但是不開源!!

6.2.TX-LCN–分布式事務解決方案

介紹:“LCN并不生産事務,LCN隻是本地事務的協調者”

LCN分布式事務架構的核心功能是對本地事務的協調控制,架構本身并不建立事務,隻是對本地事務做協調控制。是以該架構與其他第三方的架構相容性強,支援所有的關系型資料庫事務,支援多資料源,支援與第三方資料庫架構一塊使用(例如 sharding-jdbc),在使用架構的時候隻需要添加分布式事務的注解即可,對業務的侵入性低。LCN架構主要是為微服務架構提供分布式事務的支援,在微服務架構上做了進一步的事務機制優化,在一些負載場景上LCN事務機制要比本地事務機制的性能更好,4.0以後架構開方了插件機制可以讓更多的第三方架構支援進來。

特點:

①支援各種基于spring的db架構
 ②相容SpringCloud、Dubbo、motan
 ③使用簡單,低依賴,代碼完全開源
 ④基于切面的強一緻性事務架構
 ⑤高可用,子產品可以依賴RPC子產品做叢集化,TxManager也可以做叢集化
 ⑥支援本地事務和分布式事務共存
 ⑦支援事務補償機制,增加事務補償決策提醒
 ⑧添加插件拓展機制      
選擇 GTS比較N但是不開源,是以選擇tx-lcn

6.3.seata

2019 年 1 月,阿裡巴巴中間件團隊發起了開源項目 ​​Fescar​​(Fast & EaSy Commit And Rollback),和社群一起共建開源分布式事務解決方案。Fescar 的願景是讓分布式事務的使用像本地事務的使用一樣,簡單和高效,并逐漸解決開發者們遇到的分布式事務方面的所有難題。

Fescar 開源後,螞蟻金服加入 Fescar 社群參與共建,并在 Fescar 0.4.0 版本中貢獻了 TCC 模式。

為了打造更中立、更開放、生态更加豐富的分布式事務開源社群,經過社群核心成員的投票,大家決定對 Fescar 進行品牌更新,并更名為 Seata,意為:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事務解決方案。