天天看點

阿裡大牛深入分析分布式柔性事務

ACID

什麼是ACID?

原子性(Atomicity )

一個事務中所有操作都必須全部完成,要麼全部不完成

一緻性( Consistency )

在操作過程中不會破壞資料的完整性

拿轉賬為例,A有500元,B有300元,如果在一個事務裡A成功轉給B50元,那麼不管并發多少,不管發生什麼,隻要事務執行成功了,那麼最後A賬戶一定是450元,B賬戶一定是350元

隔離性或獨立性( Isolation)

事務與事務之間不會互相影響,事務将假定隻有它自己在操作資料庫,其它事務不知曉

持久性(Durabilily)

一單事務完成了,那麼事務對資料所做的變更就完全儲存在了資料庫中,即使發生停電,系統當機也是如此

簡稱就是ACID

ACID是如何保證單機事務,比如DB突然斷電如何保證資料一緻性?

使用SQL Server來舉例, SQL Server 資料庫是由兩個檔案組成的,一個資料庫檔案和一個日志檔案,通常情況下,日志檔案都要比資料庫檔案大很多。資料庫進行任何寫入操作的時候都是要先寫日志的,同樣的道理,我們在執行事務的時候資料庫首先會記錄下這個事務的redo記錄檔,然後才開始真正操作資料庫,在操作之前首先會把日志檔案寫入磁盤,那麼當突然斷電的時候,即使操作沒有完成,在重新啟動資料庫時候,資料庫會根據目前資料的情況進行undo復原或者是redo前滾,這樣就保證了資料的強一緻性

CAP定理

ACID是針對單庫下保證事務的理論,如果是多庫即分布式下ACID将沒有能力,這時就要使用CAP

什麼是CAP定理?

什麼是CAP?CAP原則或者叫CAP定理,都是指他

如果服務要求高可用性就需要采用分布式模式,來備援資料寫多份,寫多份就會帶來一緻性問題,一緻性問題又會帶來性能問題,那麼就此陷入了無解的死循環;是以隻能取之中兩個

一緻性(Consistency) :資料是一緻更新的,所有資料節點的變動都是同步的,同時發生,同時生效

根據一緻性的強弱程度不同,可以将一緻性級别 5 種,參考:zookeeper.noteZookeeper 強一緻性

可用性(Availability) :性能好+可靠性,在叢集中一部分節點故障後,叢集整體是否還能響應用戶端的讀寫請求

分區容錯性(Partition tolerance) :系統可以跨網絡分區線性的伸縮和擴充,一個分布式系統裡面,節點組成的網絡本來應該是連通的。然而可能因為一些故障,使得有些節點之間不連通了,整個網絡就分成了幾塊區域。資料就散布在了這些不連通的區域中。這就叫分區。

分區相當于對通信的時限要求。系統如果不能在時限内達成資料一緻性,就意味着發生了分區的故障,必須就目前操作在C和A之間做出選擇

該理論已被證明:任何分布式系統隻可同時滿足兩點,無法三者兼顧;是以應該根據應用場景進行适當取舍

當你一個資料項隻在一個節點中儲存,那麼分區出現後,和這個節點不連通的部分就通路不到這個資料了。這時分區就是無法容忍的。(分區容錯性差)

提高分區容忍性的辦法就是一個資料項複制到多個節點上,那麼出現分區之後,這一資料項就可能分布到各個區裡。容忍性就提高了。然而,要把資料複制到多個節點,就會帶來一緻性的問題,就是多個節點上面的資料可能是不一緻生效的。(分區容錯性好,一緻性就差)

要保證一緻,每次寫操作就都要等待全部節點寫成功,

而這等待又會帶來可用性的問題,即效率不好,要等嘛。(分區容錯性好,一緻性好,可用性就差)

總的來說就是,資料存在的節點越多,分區容忍性越高,但要複制更新的資料就越多,一緻性就越難保證。為了保證一緻性,更新所有節點資料所需要的時間就越長,可用性就會降低

注意:在分布式系統中,在任何資料庫設計中,一個Web應用至多隻能同時支援上面的兩個屬性。顯然,任何橫向擴充政策都要依賴于資料分區。是以,設計人員必須在一緻性與可用性之間做出選擇。原因已在上面說明了

Zookeeper 保證的是CP ,對于服務發現而言,可用性比資料一緻性更加重要,而 Eureka 設計則遵循AP原則

BASE理論

犧牲CAP定理中所說的高一緻性,獲得可用性

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

什麼是BASE理論?

Basically Available(基本業務可用性(支援分區失敗))

Soft state(軟狀态,狀态允許有短時間不同步,異步)

Eventuallyconsistent(最終一緻性(最終資料是一緻的,但不是實時一緻))

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

酸堿平衡(ACID-BASEBalance)

真實系統應當是ACID與BASE的混合體,是以真實系統應當是酸堿平衡的

DTP模型——暫未做細節了解

X/Open DTP(X/OpenDistributed Transaction Processing Reference Model) 是X/Open 這個組織定義的一套分布式事務的标準

什麼是分布式事務?

簡單了解,多機事務

多資料源事務,對應多個 DB / MQ 操作需要保持一緻的事務

分布式事務的重要性

随着微服務分布式架構的使用普及,分布式事務越來越成為一個繞不過去的問題,

隻要系統使用分布式架構,分布式事務問題或遲或早它就會存在

分布式事務解決方案

>兩階段送出(2PC)

優點:盡量保證了資料的強一緻,适合對資料強一緻要求很高的關鍵領域

缺點:實作複雜,犧牲了可用性(犧牲了一部分可用性來換取的一緻性),對性能影響較大,不适合高并發高性能場景

>補償事務(TCC)

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

缺點:缺點還是比較明顯的,在事務發起方遠端調用事務參與方的confirm、cancel方法中都有可能失敗。TCC屬于應用層的一種補償方式,是以需要程式員在實作的時候多寫很多補償的代碼,在一些場景中,一些業務流程可能用TCC不太好定義及處理

>本地消息表(異步確定)——遵循BASE理論,采用的是最終一緻性

也就是使用獨立消息服務 + MQ實作最終消息一緻性,這種實作方式應該是業界使用最多的

這個方案即不會出現像2PC那樣複雜的實作(當調用鍊很長的時候,2PC的可用性是非常低的),

也不會像TCC那樣可能出現确認或者復原不了的情況

優點:一種非常經典的實作,避免了分布式事務,實作了最終一緻性

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

什麼是剛性事務,什麼是柔性事務?

剛性事務

剛性事務滿足 ACID 理論

工作在資料資源層,也就是比如資料庫,MQ中間件

實作方式以 xa 2pc為代表

柔性事務

柔性事務滿足 BASE 理論,基本上是可用的,資料最終會一緻

工作在資料業務層,也就是比如應用服務、代碼層面上

實作方式以可靠消息最終一緻性異步確定型最大努力通知型TCC補償型為代表

剛性事務的優缺點、特點及适用場景

特點

屬于标準的分布式解決方案,全局事務送出型,

XA 2PC兩階段送出這些方式都會在事務整個過程全局鎖定資源,對資源占用大

優點

高強度保證 ACID

缺點

效率低,也就是性能低,全局資源的鎖定,造成所有參與的資料資源鎖定狀态貫穿整個事務過程,對資源的占用時間跨度大,成本高,

另外對資源有要求,資料源隻有實作了XA規範才能接入,目前并不是所有的資源都支援XA規範

柔性事務的優缺點、特點及适用場景

特點

屬于用代碼控制處理邏輯的方式來保證分布式事務

可靠消息最終一緻性異步確定型最大努力通知型 TCC補償型這些方式基本使用定期詢問、檢查、重發、小粒度鎖等方式實作分布式事務,對資源占用做到最低

優點

對資源占用低

缺點

為了實作松弛的限制下事務一緻,需要添加多種子系統用于定期詢問、檢查、重發等工作,整個系統體積會增大

柔性事務

>柔性事務的服務模式

服務模式屬于在各種分布式事務解決方案中需要實作的一些能力,比如可查詢操作表示要提供查詢接口,幂等操作表示要實作幂等性支援重複消費下不影響業務資料

可查詢操作

幂等操作

TCC操作

兩階段型操作!= 兩階段送出協定2PC操作

TCC也屬于一種兩階段型操作

可補償操作

>柔性事務的解決方案

***可靠消息最終一緻(異步確定型)——遵循BASE理論

PS:其實很多分布式事務場景可以通過這種方式實作事務上的最終一緻

消息中間件的作用很多,例如異步通訊、解耦、并發緩沖、流量消峰等,但是傳統的使用方式下消息中間件是不可靠的,原因是發送方 MQ 監聽方三者是通過網絡連接配接的,有網絡的地方就是不穩定的。MQ 叢集隻是解決 MQ 元件自身的高可用,降低消息進來後消息丢失的風險。

阿裡大牛深入分析分布式柔性事務

問題描述

MQ 是網絡連接配接的,不可靠,業務操作成功後一定要保證消息發出去,并且給對方消費,否則就屬于事務不一緻

兩個應用系統 A、B,B 通過 MQ 消費 A 發送的消息,

假設 A 是訂單系統處理完自己的業務後,資料存儲到 A-DB 然後發送消息到 MQ 讓會計系統 B 關聯記錄會計憑證,

又假設 A 做完訂單業務後,發送 MQ 時因為網絡問題沒發成功或者 MQ 持久化出問題等原因消息丢了,

那麼這一訂單就丢失了會計憑證資料,A 和 B屬于事務不一緻,這是不允許發生的事

有一種說法是先發送 MQ 消息,成功後回頭再做業務,這問題就更大了,如果消息ok了,回頭業務做失敗了,就等于記錄了會計憑證而沒有實際訂單業務,問題更加嚴重

阿裡大牛深入分析分布式柔性事務

方案流程(需要引入三個子系統消息狀态确認子系統消息恢複子系統消息服務管理子系統)

先提一個設想,如果我們能先發一個預消息到 MQ,但是此時消息狀态是暫存,隻有狀态為待發送才能投遞到監聽端去消費,回頭等業務做成功了再修改 MQ 消息狀态為待發送,這樣 MQ 才支援投遞消息到消費端去消費,這樣确實可行。但目前開源的常用 MQ 産品沒有這種功能,好像 RocketMQ 提供,沒有去确認,開發難度大,是以使用自建獨立消息服務方式進行實作

執行業務前發送預消息到消息服務做消息入庫,此時消息狀态為待發送,

業務系統得到消息服務的正确傳回後再做業務,業務做完就将業務狀态走 dubbo 異步通信方式通知到消息服務,消息服務根據狀态決定消息是發送MQ還是删除或隻是标記消息狀态,業務庫不用記錄發送消息業務的狀态,這裡存在的異常點是可能因為業務處理失敗或者業務處理成功而因為網絡原因沒能成功将業務狀态發送給消息服務還或者業務處理成功而調用消息服務發送業務狀态時因消息服務處理時間長而讀取逾時等,都會造成消息服務對應的消息庫中堆積大量的消息狀态為待發送的消息,這個異常點的解決都由消息狀态确認子系統在消息服務中定期查詢消息庫中超過指定時間并且消息狀态不是消費成功的消息,再調用業務系統的查詢接口驗證消息對應的業務狀态,此時如果業務成功,就同步消息庫的業務狀态同時發送MQ,此時消息狀态為發送中,如果業務失敗,根據業務決定是否保留消息,這裡一定要考慮資料增長問題,提前做好拆分的設計,使用分區、分表等方式在資料量龐大的時候也能快速讀寫,不影響性能

如果消息服務受到業務系統的業務狀态是成功,那麼馬上将對應消息發送給 MQ,以 RabbitMQ 為例,這裡消息服務和MQ 的互動可能遇上這幾類問題:

1、消息直接無法到達MQ

比如網絡斷了就會引起,這時直接走時間階梯等待後重發即可,如果出現 connectionclosed 錯誤,直接增加connection數即可

connectionFactory.setChannelCacheSize(100);

2、消息已經發送到達MQ,但傳回的時候出現異常

rabbitmq提供了确認ack機制,可以用來确認消息是否有傳回,是以可以根據發送回執 ack 決定是否要時間階梯重發

 rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {

如果消息沒有到 exchange,則 ack=false

如果消息到達 exchange,則 ack=true

如果發送時根本找不到 exchange,則會觸發 returncallback 函數 if (!ack) { //調用時間階梯重發邏輯 } else { //可以記錄消息服務中的消息狀态為投遞成功,此時基本認為消息會被監聽方消費掉,監聽方拿到消息就

//auto 簽收掉,如果消費失敗,就寄希望于消息狀态确認子系統下一次再将消息發送到 mq 中 } });rabbitTemplate.setReturnCallback((message, replyCode, replyText, tmpExchange,tmpRoutingKey) -> { try { Thread.sleep(Constants.ONE_SECOND); } catch(InterruptedException e) { e.printStackTrace(); } log.info("send messagefailed: " + replyCode + " " + replyText);

//等待一段時間,再發rabbitTemplate.send(message); });

3、消息送達後,消息服務自己挂了

可能消息發送到 MQ 成功後,正當回傳給消息服務時,消息服務自己挂了,導緻監聽方可能消費成功而消息服務還認為發送失敗處于待發送狀态

這其實沒有關系,反正監聽方是幂等的,哪怕消息服務重發消息,也不會出問題,而且監聽方消費成功後會直接跨調消息服務告訴這條消息消費成功,同樣可以覆寫掉這個問題,并沒有關系,不用處理

4、監聽方消費失敗

這就依靠于消息狀态确認子系統重發消息解決就好了

然後講 MQ監聽方環節,監聽方拿到消息就 auto 簽收,MQ消息随即 remove,監聽方調具體的消費服務,這裡監聽方相當于是一個 MQ 的監聽網關,自身可以做更多的服務編排工作,具體消費調用後面的消費服務,消費服務處理成功後監聽方反過來調用消息服務,标記消息成功消費,如果消費服務出了任何問題包括網絡問題導緻消費失敗,

都由消息恢複子系統定時将消息庫中逾時的并且狀态為發送中的消息拿出來再丢到MQ中,等于是重發機制,

每次重發要比之前的間隔更長,提供可配置的時間階梯,這樣更加有利于等待被動方系統故障恢複,消費服務一定要是幂等的,這樣重複消費也沒問題,如果消息超過重發次數就移到死亡隊清單中,這裡可以添加消息服務管理子系統檢視死亡消息有多少,可能有些是因為消費方系統故障原因,Bug 修複後可以操作重發這些死亡消息,也可以針對某一類型的死亡消息批量重發等等,如果監聽方這邊有性能問題,可以考慮用redis記錄消息狀态,這樣監聽方校驗消息是否消費成功就更高效

最後一點,業務方對消息服務dubbo發送業務狀态需要使用異步方式,因為如果消息服務處理成功後因為網絡傳回逾時,将會導緻業務方事務復原,這樣就不一緻了,注意預發送消息不能用異步,因為它不僅證明此時鍊路沒問題,而且它在消費服務中的結果要為後面的業務服務的。綜上所述業務方調消息放要使用dubbo的異步方式

這樣就能實作在不改造困難大的 MQ 的前提下實作可靠消息最終一緻,

缺點或者不足方面:各排程子系統周期觸發間的資料不一緻延遲,及使用了多個服務和資料庫有不少的開發成本增加了系統的體積,但它是一勞永逸,複用的

阿裡大牛深入分析分布式柔性事務

注意點及優化建議

>如果業務方和消費方是這種訂單-快遞的場景,調用方式就是1:1,消費方性能問題倒還好,如果壓力大可考慮消息服務對應的庫和消費方的消息庫使用redis等記憶體庫,如果用redis,特别注意,甯願犧牲大部分性能也要配置 aof 持久化為always ,最大程度保證資料不丢

>消息服務對應的消息表一定要考慮資料增長問題,提前做好拆分的設計,使用分區、分表等方式在資料量龐大的時候也能快速讀寫,不影響性能

>業務系統傳遞業務操作狀态給消息服務時,一定要用異步方式,比如是 dubbo 協定就用 dubbo 異步通信

<!-- 異步功能實作 -->

<dubbo:referenceid="demoServicemy2"inter>

<dubbo:methodname="getPerson" async="true" return="false"/> <!-- 異步,不等待傳回值 -->

</dubbo:reference>

>如果消息監聽消費服務中為了實作幂等而開銷比較大的話,可以考慮在消費端資料庫中也增加一份消息表,記錄消息處理狀态,消費時直接擷取消息狀态就能決定知否直接傳回結果

>各種消息子系統其實都是定時任務,排程,這裡建議使用分布式定時架構技術

***最大努力通知(定期校對,通知型)

和可靠消息最終一緻方案針對解決的問題一樣,可靠消息最終一緻方案最終資料會一緻,最多隻是存在時間上的延遲而已,最大努力通知方案的思想是盡可能的進行多次通知被動方系統,不保證被動方資料一定會一緻,同時不管被動方資料是否一緻反正主動方資料變更後就不動了,最多隻是定期或者每天提供一份資料對賬的途經,可能是 http 查詢接口,可能是下載下傳對賬檔案供被動方 ftp 拿下來對賬,以便被動方同步資料,糾正不一緻的錯誤資料

阿裡大牛深入分析分布式柔性事務

适合的場景

首先他不适合那種被動方對資料一緻性有較高強度要求的場景,因為有可能通知不到被動方造成資料不一緻

同時主動方系統一定要有資料标準的能力,因為兩邊出現差異時都是以主動方資料為準進行同步修複的

适合用在對于業務最終一緻性的時間敏感度低的場景下,也就是說能忍受一段時間内可能一天的資料不一緻,同時比較适合跨企業級的系統,因為不太可能進行大量改造添加元件來滿足一緻性的要求,銀行日清對賬檔案就是這種方案的展現

方案流程

主動方處理完業務流程後通過服務 http 方式或者 MQ 通知被動方,如果失敗再按照設定的時間階梯型通知規則,N次失敗後記錄成死亡消息,同時主動方也要為被動方提供業務資料的查詢接口或者每個周期後産生一個對賬檔案,提供給被動方用于資料不一緻時進行校正,被動方也是一樣要實作幂等

***TCC(兩階段型、補償型)

TCC 其實就是采用的補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應的确認和補償(撤銷)操作

TCC 分别指的是 trying、confirming、canceling 三個階段,

Try是先把多個應用中的業務資源預留和鎖定住,為後續的确認送出打下基礎

Confirm 是将Try 操作中涉及的所有應用的所有鎖定資源全部送出處理

Cancel 是将Try 操作中涉及的所有應用的所有鎖定資源全部復原處理

TRYING階段隻要完全執行成功,預設就是要高強度能保證CONFIRMING階段不能出錯,

也就是說要在業務邏輯層面在trying環節盡最大努力做到各種檢查和鎖定,以確定confirming不出問題

TCC 三個過程就像資料庫對事務的處理過程,對應資料庫的 lock、commit、rollback

阿裡大牛深入分析分布式柔性事務

适合的場景

TCC 方案對事務控制的特點是準實時的,适合用在對實時性要求較高的場景下,

比如訂單支付環節,使用者一旦下單付款成功,訂單系統的訂單狀态将馬上顯示支付完成,同時對應的賬戶系統的賬戶餘額就要對應減少,不能出現太長的先後順序,也不能允許因為任何環節出錯而等待消息重發、對賬等方式再進行資料同步修複,這樣的話,這一段時間的資料将不一緻,這種場景下是不能允許的

上面這種場景就不适合可靠消息最終一緻性方案,使用 TCC 比較适合

TCC方案的優點 / 特點

對資源的鎖定程度較少,盡可能的直接将需要的資料部分劃出去鎖定起來,這是軟鎖,與 XA 兩階段送出做全局鎖的方案是不同的——這将直接在資料資源層面形成等待、阻塞,引起吞吐能力下降,造成系統性能不好

TCC事務的缺點

TCC 的Try、Confirm和Cancel操作功能需業務提供,并且開發成本高

TCC方案的原理——個人總結

TCC 實質上是應用層的2PC(2 PhaseCommit, 兩階段送出),好比把 XA 兩階段送出那種在資料資源層做的事務管理工作提到了資料應用層,每個應用可以看作一個資料總管,他們的工作對應如下的描述

1、Try:嘗試執行業務

完成所有業務檢查(一緻性)

預留鎖定必須的業務資源(準隔離性)

2、Confirm:确認執行業務

不再做業務檢查

記錄成功,并隻使用Try階段預留鎖定的業務資源做确認操作

預設 Confirm階段是不會出錯的。即:隻要Try成功,Confirm一定得成功,也就是隻能通過在Try階段進行強有力的邏輯控制,保障Confirm不出事

3、Cancel:取消執行業務

記錄復原,并釋放Try階段預留鎖定的業務資源

一個完整的TCC事務參與方包括三部分

主業務服務

主業務服務為整個業務活動的發起方,如訂單支付中訂單支付系統屬于主業務服務,其他的積分、賬戶都屬于從業務服務

從業務服務

從業務服務負責提供TCC業務操作,是整個業務活動的操作方。從業務服務必須實作Try、Confirm和Cancel三個接口,供主業務服務調用。由于Confirm和Cancel操作可能被重複調用,故要求Confirm和Cancel兩個接口必須是幂等的

業務活動管理器

業務活動管理器管理控制整個業務活動,包括記錄維護TCC全局事務的事務狀态和每個從業務服務的子事務狀态,在業務活動送出時确認所有的TCC型操作的confirm操作,

在業務活動取消時調用所有TCC型操作的cancel操作

整個TCC事務對于主業務服務來說是透明的,其中業務活動管理器和從業務服務各自幹了一部分工作

一個案例了解

接下來将以賬務拆分為例,對TCC事務的流程做一個描述,業務場景如下,

分别位于三個不同分庫的帳戶A、B、C,A和B一起向C轉帳共80元:

1、Try:嘗試執行業務

完成所有業務檢查(一緻性):檢查A、B、C的帳戶狀态是否正常,帳戶A的餘額是否不少于30元,帳戶B的餘額是否不少于50元。

預留必須業務資源(準隔離性):帳戶A的當機金額增加30元,帳戶B的當機金額增加50元,這樣就保證不會出現其他并發程序扣減了這兩個帳戶的餘額而導緻在後續的真正轉帳操作過程中,帳戶A和B的可用餘額不夠的情況。

2、Confirm:确認執行業務

真正執行業務:如果Try階段帳戶A、B、C狀态正常,且帳戶A、B餘額夠用,則執行帳戶A給賬戶C轉賬30元、帳戶B給賬戶C轉賬50元的轉帳操作。

不做任何業務檢查:這時已經不需要做業務檢查,Try階段已經完成了業務檢查。

隻使用Try階段預留的業務資源:隻需要使用Try階段帳戶A和帳戶B當機的金額即可。

3、Cancel:取消執行業務

釋放Try階段預留的業務資源:如果Try階段部分成功,比如帳戶A的餘額夠用,且當機相應金額成功,帳戶B的餘額不夠而當機失敗,則需要對帳戶A做Cancel操作,将帳戶A被當機的金額解凍掉。

幂等性的實作方式

1、通過唯一鍵值做處理,即每次調用的時候傳入唯一鍵值,通過唯一鍵值判斷業務是否被操作,如果已被操作,則不再重複操作

2、通過狀态機處理,給業務資料設定狀态,通過業務狀态判斷是否需要重複執行