搞懂分佈式技術17,18:分佈式事務總結

搞懂分佈式技術17:淺析分佈式事務

衆所周知,數據庫能實現本地事務,也就是在同一個數據庫中,你能夠容許一組操做要麼全都正確執行,要麼全都不執行。這裏特別強調了本地事務,也就是目前的數據庫只能支持同一個數據庫中的事務。但如今的系統每每採用微服務架構,業務系統擁有獨立的數據庫,所以就出現了跨多個數據庫的事務需求,這種事務即爲「分佈式事務」。那麼在目前數據庫不支持跨庫事務的狀況下,咱們應該如何實現分佈式事務呢?本文首先會爲你們梳理分佈式事務的基本概念和理論基礎,而後介紹幾種目前經常使用的分佈式事務解決方案。廢話很少說,那就開始吧~mysql

什麼是事務?

事務由一組操做構成,咱們但願這組操做可以所有正確執行,若是這一組操做中的任意一個步驟發生錯誤,那麼就須要回滾以前已經完成的操做。也就是同一個事務中的全部操做,要麼全都正確執行,要麼全都不要執行。web

事務的四大特性 ACID

說到事務,就不得不提一下事務著名的四大特性。算法

原子性原子性要求,事務是一個不可分割的執行單元,事務中的全部操做要麼全都執行,要麼全都不執行。sql

一致性一致性要求,事務在開始前和結束後,數據庫的完整性約束沒有被破壞。數據庫

隔離性事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另外一個正在運行過程當中的事務的數據。緩存

持久性持久性要求,一個事務完成以後,事務的執行結果必須是持久化保存的。即便數據庫發生崩潰,在數據庫恢復後事務提交的結果仍然不會丟失。安全

注意:事務只能保證數據庫的高可靠性,即數據庫自己發生問題後,事務提交後的數據仍然能恢復;而若是不是數據庫自己的故障,如硬盤損壞了,那麼事務提交的數據可能就丟失了。這屬於『高可用性』的範疇。所以,事務只能保證數據庫的『高可靠性』,而『高可用性』須要整個系統共同配合實現。服務器

事務的隔離級別

這裏擴展一下,對事務的隔離性作一個詳細的解釋。網絡

在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離,也就是多個事務是串行執行的,彼此之間不會受到任何干擾。這確實可以徹底保證數據的安全性,但在實際業務系統中,這種方式性能不高。所以,數據庫定義了四種隔離級別,隔離級別和數據庫的性能是呈反比的,隔離級別越低,數據庫性能越高,而隔離級別越高,數據庫性能越差。架構

事務併發執行會出現的問題

咱們先來看一下在不一樣的隔離級別下,數據庫可能會出現的問題:

更新丟失當有兩個併發執行的事務,更新同一行數據,那麼有可能一個事務會把另外一個事務的更新覆蓋掉。當數據庫沒有加任何鎖操做的狀況下會發生。

髒讀一個事務讀到另外一個還沒有提交的事務中的數據。該數據可能會被回滾從而失效。若是第一個事務拿着失效的數據去處理那就發生錯誤了。

不可重複讀不可重複度的含義:一個事務對同一行數據讀了兩次,卻獲得了不一樣的結果。它具體分爲以下兩種狀況:

虛讀:在事務1兩次讀取同一記錄的過程當中,事務2對該記錄進行了修改,從而事務1第二次讀到了不同的記錄。 幻讀:事務1在兩次查詢的過程當中,事務2對該表進行了插入、刪除操做,從而事務1第二次查詢的結果發生了變化。 不可重複讀 與 髒讀 的區別?髒讀讀到的是還沒有提交的數據,而不可重複讀讀到的是已經提交的數據,只不過在兩次讀的過程當中數據被另外一個事務改過了。

數據庫的四種隔離級別

數據庫一共有以下四種隔離級別:

1.Read uncommitted 讀未提交在該級別下,一個事務對一行數據修改的過程當中,不容許另外一個事務對該行數據進行修改,但容許另外一個事務對該行數據讀。所以本級別下,不會出現更新丟失,但會出現髒讀、不可重複讀。

2.Read committed 讀提交在該級別下,未提交的寫事務不容許其餘事務訪問該行,所以不會出現髒讀;可是讀取數據的事務容許其餘事務的訪問該行數據,所以會出現不可重複讀的狀況。

3.Repeatable read 重複讀在該級別下,讀事務禁止寫事務,但容許讀事務,所以不會出現同一事務兩次讀到不一樣的數據的狀況(不可重複讀),且寫事務禁止其餘一切事務。

4.Serializable 序列化該級別要求全部事務都必須串行執行,所以能避免一切因併發引發的問題,但效率很低。

隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大。對於多數應用程序,能夠優先考慮把數據庫系統的隔離級別設爲Read Committed。它可以避免髒讀取,並且具備較好的併發性能。儘管它會致使不可重複讀、幻讀和第二類丟失更新這些併發問題,在可能出現這類問題的個別場合,能夠由應用程序採用悲觀鎖或樂觀鎖來控制。

什麼是分佈式事務?

到此爲止,所介紹的事務都是基於單數據庫的本地事務,目前的數據庫僅支持單庫事務,並不支持跨庫事務。而隨着微服務架構的普及,一個大型業務系統每每由若干個子系統構成,這些子系統又擁有各自獨立的數據庫。每每一個業務流程須要由多個子系統共同完成,並且這些操做可能須要在一個事務中完成。在微服務系統中,這些業務場景是廣泛存在的。此時,咱們就須要在數據庫之上經過某種手段,實現支持跨數據庫的事務支持,這也就是你們常說的「分佈式事務」。

這裏舉一個分佈式事務的典型例子——用戶下單過程。當咱們的系統採用了微服務架構後,一個電商系統每每被拆分紅以下幾個子系統:商品系統、訂單系統、支付系統、積分系統等。整個下單的過程以下:

用戶經過商品系統瀏覽商品,他看中了某一項商品,便點擊下單 此時訂單系統會生成一條訂單 訂單建立成功後,支付系統提供支付功能 當支付完成後,由積分系統爲該用戶增長積分 上述步驟二、三、4須要在一個事務中完成。對於傳統單體應用而言,實現事務很是簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional註解標識該方法便可。Spring經過數據庫的事務支持,保證這些步驟要麼全都執行完成,要麼全都不執行。但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個數據庫,此時咱們必須在數據庫和應用系統之間,經過某項黑科技,實現分佈式事務的支持。

CAP理論

CAP理論說的是:在一個分佈式系統中,最多隻能知足C、A、P中的兩個需求。

CAP的含義:

C:Consistency 一致性同一數據的多個副本是否實時相同。 A:Availability 可用性可用性:必定時間內 & 系統返回一個明確的結果 則稱爲該系統可用。 P:Partition tolerance 分區容錯性將同一服務分佈在多個系統中,從而保證某一個系統宕機,仍然有其餘系統提供相同的服務。 CAP理論告訴咱們,在分佈式系統中,C、A、P三個條件中咱們最多隻能選擇兩個。那麼問題來了,究竟選擇哪兩個條件較爲合適呢?

對於一個業務系統來講,可用性和分區容錯性是必需要知足的兩個條件,而且這二者是相輔相成的。業務系統之因此使用分佈式系統,主要緣由有兩個:

提高總體性能當業務量猛增,單個服務器已經沒法知足咱們的業務需求的時候,就須要使用分佈式系統,使用多個節點提供相同的功能,從而總體上提高系統的性能,這就是使用分佈式系統的第一個緣由。

實現分區容錯性單一節點 或 多個節點處於相同的網絡環境下,那麼會存在必定的風險,萬一該機房斷電、該地區發生天然災害,那麼業務系統就全面癱瘓了。爲了防止這一問題,採用分佈式系統,將多個子系統分佈在不一樣的地域、不一樣的機房中,從而保證系統高可用性。

這說明分區容錯性是分佈式系統的根本,若是分區容錯性不能知足,那使用分佈式系統將失去意義。

此外,可用性對業務系統也尤其重要。在大談用戶體驗的今天,若是業務系統時常出現「系統異常」、響應時間過長等狀況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚枚舉,系統的間歇性不可用會立馬致使用戶流向競爭對手。所以,咱們只能經過犧牲一致性來換取系統的可用性和分區容錯性。這也就是下面要介紹的BASE理論。

BASE理論

CAP理論告訴咱們一個悲慘但不得不接受的事實——咱們只能在C、A、P中選擇兩個條件。而對於業務系統而言,咱們每每選擇犧牲一致性來換取系統的可用性和分區容錯性。不過這裏要指出的是,所謂的「犧牲一致性」並非徹底放棄數據一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。

BA:Basic Available 基本可用 整個系統在某些不可抗力的狀況下,仍然可以保證「可用性」,即必定時間內仍然可以返回一個明確的結果。只不過「基本可用」和「高可用」的區別是: 「必定時間」能夠適當延長當舉行大促時,響應時間能夠適當延長 給部分用戶返回一個降級頁面給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。 S:Soft State:柔性狀態同一數據的不一樣副本的狀態,能夠不須要實時一致。 E:Eventual Consisstency:最終一致性同一數據的不一樣副本的狀態,能夠不須要實時一致,但必定要保證通過必定時間後仍然是一致的。

酸鹼平衡

ACID可以保證事務的強一致性,即數據是實時一致的。這在本地事務中是沒有問題的,在分佈式事務中,強一致性會極大影響分佈式系統的性能,所以分佈式系統中遵循BASE理論便可。但分佈式系統的不一樣業務場景對一致性的要求也不一樣。如交易場景下,就要求強一致性,此時就須要遵循ACID理論,而在註冊成功後發送短信驗證碼等場景下,並不須要實時一致,所以遵循BASE理論便可。所以要根據具體業務場景,在ACID和BASE之間尋求平衡

分佈式事務協議

下面介紹幾種實現分佈式事務的協議。

理解2PC和3PC協議

爲了解決分佈式一致性問題,前人在性能和數據一致性的反反覆覆權衡過程當中總結了許多典型的協議和算法。其中比較著名的有二階提交協議(2 Phase Commitment Protocol),三階提交協議(3 Phase Commitment Protocol)。

2PC

分佈式事務最經常使用的解決方案就是二階段提交。在分佈式系統中,每一個節點雖然能夠知曉本身的操做時成功或者失敗,卻沒法知道其餘節點的操做的成功或失敗。當一個事務跨越多個節點時,爲了保持事務的ACID特性,須要引入一個做爲協調者的組件來統一掌控全部參與者節點的操做結果並最終指示這些節點是否要把操做結果進行真正的提交。

所以,二階段提交的算法思路能夠歸納爲:參與者將操做成敗通知協調者,再由協調者根據全部參與者的反饋情報決定各參與者是否要提交操做仍是停止操做。

所謂的兩個階段是指:第一階段:準備階段(投票階段)和第二階段:提交階段(執行階段)。

第一階段:投票階段

該階段的主要目的在於打探數據庫集羣中的各個參與者是否可以正常的執行事務,具體步驟以下:

  1. 協調者向全部的參與者發送事務執行請求,並等待參與者反饋事務執行結果。

  2. 事務參與者收到請求以後,執行事務,但不提交,並記錄事務日誌。

  3. 參與者將本身事務執行狀況反饋給協調者,同時阻塞等待協調者的後續指令。

第二階段:事務提交階段

在第一階段協調者的詢盤以後,各個參與者會回覆本身事務的執行狀況,這時候存在三種可能:

  1. 全部的參與者回覆可以正常執行事務。

  2. 一個或多個參與者回覆事務執行失敗。

  3. 協調者等待超時。

對於第一種狀況,協調者將向全部的參與者發出提交事務的通知,具體步驟以下:

  1. 協調者向各個參與者發送commit通知,請求提交事務。

  2. 參與者收到事務提交通知以後,執行commit操做,而後釋放佔有的資源。

  3. 參與者向協調者返回事務commit結果信息。

 

對於第2、三種狀況,協調者均認爲參與者沒法正常成功執行事務,爲了整個集羣數據的一致性,因此要向各個參與者發送事務回滾通知,具體步驟以下:

  1. 協調者向各個參與者發送事務rollback通知,請求回滾事務。

  2. 參與者收到事務回滾通知以後,執行rollback操做,而後釋放佔有的資源。

  3. 參與者向協調者返回事務rollback結果信息。

 

兩階段提交協議解決的是分佈式數據庫數據強一致性問題,其原理簡單,易於實現,可是缺點也是顯而易見的,主要缺點以下:

單點問題:協調者在整個兩階段提交過程當中扮演着舉足輕重的做用,一旦協調者所在服務器宕機,那麼就會影響整個數據庫集羣的正常運行,好比在第二階段中,若是協調者由於故障不能正常發送事務提交或回滾通知,那麼參與者們將一直處於阻塞狀態,整個數據庫集羣將沒法提供服務。

同步阻塞:兩階段提交執行過程當中,全部的參與者都須要遵從協調者的統一調度,期間處於阻塞狀態而不能從事其餘操做,這樣效率及其低下。

數據不一致性:兩階段提交協議雖然爲分佈式數據強一致性所設計,但仍然存在數據不一致性的可能,好比在第二階段中,假設協調者發出了事務commit的通知,可是由於網絡問題該通知僅被一部分參與者所收到並執行了commit操做,其他的參與者則由於沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。

3PC

針對兩階段提交存在的問題,三階段提交協議經過引入一個「預詢盤」階段,以及超時策略來減小整個集羣的阻塞時間,提高系統性能。三階段提交的三個階段分別爲:can_commit,pre_commit,do_commit。

第一階段:can_commit

該階段協調者會去詢問各個參與者是否可以正常執行事務,參與者根據自身狀況回覆一個預估值,相對於真正的執行事務,這個過程是輕量的,具體步驟以下:

  1. 協調者向各個參與者發送事務詢問通知,詢問是否能夠執行事務操做,並等待回覆。

  2. 各個參與者依據自身情況回覆一個預估值,若是預估本身可以正常執行事務就返回肯定信息,並進入預備狀態,不然返回否認信息。

第二階段:pre_commit

本階段協調者會根據第一階段的詢盤結果採起相應操做,詢盤結果主要有三種:

  1. 全部的參與者都返回肯定信息。

  2. 一個或多個參與者返回否認信息。

  3. 協調者等待超時。

針對第一種狀況,協調者會向全部參與者發送事務執行請求,具體步驟以下:

  1. 協調者向全部的事務參與者發送事務執行通知。

  2. 參與者收到通知後,執行事務,但不提交。

  3. 參與者將事務執行狀況返回給客戶端。

在上面的步驟中,若是參與者等待超時,則會中斷事務。 針對第2、三種狀況,協調者認爲事務沒法正常執行,因而向各個參與者發出abort通知,請求退出預備狀態,具體步驟以下:

  1. 協調者向全部事務參與者發送abort通知

  2. 參與者收到通知後,中斷事務

 

第三階段:do_commit

若是第二階段事務未中斷,那麼本階段協調者將會依據事務執行返回的結果來決定提交或回滾事務,分爲三種狀況:

  1. 全部的參與者都能正常執行事務。

  2. 一個或多個參與者執行事務失敗。

  3. 協調者等待超時。

針對第一種狀況,協調者向各個參與者發起事務提交請求,具體步驟以下:

  1. 協調者向全部參與者發送事務commit通知。

  2. 全部參與者在收到通知以後執行commit操做,並釋放佔有的資源。

  3. 參與者向協調者反饋事務提交結果。

 

針對第2、三種狀況,協調者認爲事務沒法正常執行,因而向各個參與者發送事務回滾請求,具體步驟以下:

  1. 協調者向全部參與者發送事務rollback通知。

  2. 全部參與者在收到通知以後執行rollback操做,並釋放佔有的資源。

  3. 參與者向協調者反饋事務提交結果。

 

在本階段若是由於協調者或網絡問題,致使參與者遲遲不能收到來自協調者的commit或rollback請求,那麼參與者將不會如兩階段提交中那樣陷入阻塞,而是等待超時後繼續commit。相對於兩階段提交雖然下降了同步阻塞,但仍然沒法避免數據的不一致性。

 

Reference

https://zhuanlan.zhihu.com/p/25933039

http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

http://blog.csdn.net/jasonsungblog/article/details/49017955

http://blog.csdn.net/suifeng3051/article/details/52691210

https://my.oschina.net/wangzhenchao/blog/736909

 

搞懂分佈式技術18:分佈式事務經常使用解決方案

分佈式事務的解決方案

分佈式事務的解決方案有以下幾種:

  • 全局消息

  • 基於可靠消息服務的分佈式事務

  • TCC

  • 最大努力通知

方案1:全局事務(DTP模型)

全局事務基於DTP模型實現。DTP是由X/Open組織提出的一種分佈式事務模型——X/Open Distributed Transaction Processing Reference Model。它規定了要實現分佈式事務,須要三種角色:

  • AP:Application 應用系統它就是咱們開發的業務系統,在咱們開發的過程當中,能夠使用資源管理器提供的事務接口來實現分佈式事務。

  • TM:Transaction Manager 事務管理器

    • 分佈式事務的實現由事務管理器來完成,它會提供分佈式事務的操做接口供咱們的業務系統調用。這些接口稱爲TX接口。

    • 事務管理器還管理着全部的資源管理器,經過它們提供的XA接口來同一調度這些資源管理器,以實現分佈式事務。

    • DTP只是一套實現分佈式事務的規範,並無定義具體如何實現分佈式事務,TM能夠採用2PC、3PC、Paxos等協議實現分佈式事務。

  • RM:Resource Manager 資源管理器

    • 可以提供數據服務的對象均可以是資源管理器,好比:數據庫、消息中間件、緩存等。大部分場景下,數據庫即爲分佈式事務中的資源管理器。

    • 資源管理器可以提供單數據庫的事務能力,它們經過XA接口,將本數據庫的提交、回滾等能力提供給事務管理器調用,以幫助事務管理器實現分佈式的事務管理。

    • XA是DTP模型定義的接口,用於向事務管理器提供該資源管理器(該數據庫)的提交、回滾等能力。

    • DTP只是一套實現分佈式事務的規範,RM具體的實現是由數據庫廠商來完成的。

實際方案:基於XA協議的兩階段提交(或者3PC,Paxos等)

XA是一個分佈式事務協議,由Tuxedo提出。XA中大體分爲兩部分:事務管理器和本地資源管理器。其中本地資源管理器每每由數據庫實現,好比Oracle、DB2這些商業數據庫都實現了XA接口,而事務管理器做爲全局的調度者,負責各個本地資源的提交和回滾。XA實現分佈式事務的原理以下:

總的來講,XA協議比較簡單,並且一旦商業數據庫實現了XA協議,使用分佈式事務的成本也比較低。可是,XA也有致命的缺點,那就是性能不理想,特別是在交易下單鏈路,每每併發量很高,XA沒法知足高併發場景。XA目前在商業數據庫支持的比較理想,在mysql數據庫中支持的不太理想,mysql的XA實現,沒有記錄prepare階段日誌,主備切換回致使主庫與備庫數據不一致。許多nosql也沒有支持XA,這讓XA的應用場景變得很是狹隘。

 

方案2:基於可靠消息服務的分佈式事務(事務消息中間件

這種實現分佈式事務的方式須要經過消息中間件來實現。假設有A和B兩個系統,分別能夠處理任務A和任務B。此時系統A中存在一個業務流程,須要將任務A和任務B在同一個事務中處理。下面來介紹基於消息中間件來實現這種分佈式事務。

  • 在系統A處理任務A前,首先向消息中間件發送一條消息

  • 消息中間件收到後將該條消息持久化,但並不投遞。此時下游系統B仍然不知道該條消息的存在。

  • 消息中間件持久化成功後,便向系統A返回一個確認應答

  • 系統A收到確認應答後,則能夠開始處理任務A;

  • 任務A處理完成後,向消息中間件發送Commit請求。該請求發送完成後,對系統A而言,該事務的處理過程就結束了,此時它能夠處理別的任務了。但commit消息可能會在傳輸途中丟失,從而消息中間件並不會向系統B投遞這條消息,從而系統就會出現不一致性。這個問題由消息中間件的事務回查機制完成,下文會介紹。

  • 消息中間件收到Commit指令後,便向系統B投遞該消息,從而觸發任務B的執行;

  • 當任務B執行完成後,系統B向消息中間件返回一個確認應答,告訴消息中間件該消息已經成功消費,此時,這個分佈式事務完成。

上述過程能夠得出以下幾個結論:

  1. 消息中間件扮演者分佈式事務協調者的角色。

  2. 系統A完成任務A後,到任務B執行完成之間,會存在必定的時間差。在這個時間差內,整個系統處於數據不一致的狀態,但這短暫的不一致性是能夠接受的,由於通過短暫的時間後,系統又能夠保持數據一致性,知足BASE理論

上述過程當中,若是任務A處理失敗,那麼須要進入回滾流程,以下圖所示:

 

  • 若系統A在處理任務A時失敗,那麼就會向消息中間件發送Rollback請求。和發送Commit請求同樣,系統A發完以後即可以認爲回滾已經完成,它即可以去作其餘的事情。

  • 消息中間件收到回滾請求後,直接將該消息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B。

此時系統又處於一致性狀態,由於任務A和任務B都沒有執行。

上面所介紹的Commit和Rollback都屬於理想狀況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那麼當出現這種狀況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制

 

 

系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口供消息中間件調用。當消息中間件收到一條事務型消息後便開始計時,若是到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

  • 提交若得到的狀態是「提交」,則將該消息投遞給系統B。

  • 回滾若得到的狀態是「回滾」,則直接將條消息丟棄。

  • 處理中若得到的狀態是「處理中」,則繼續等待。

消息中間件的超時詢問機制可以防止上游系統因在傳輸過程當中丟失Commit/Rollback指令而致使的系統不一致狀況,並且能下降上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令後即可以處理其餘任務,無需等待確認應答。而Commit/Rollback指令丟失的狀況經過超時詢問機制來彌補,這樣大大下降上游系統的阻塞時間,提高系統的併發度。

下面來講一說消息投遞過程的可靠性保證。當上遊系統執行完任務並向消息中間件提交了Commit指令後,即可以處理其餘任務了,此時它能夠認爲事務已經完成,接下來消息中間件必定會保證消息被下游系統成功消費掉!那麼這是怎麼作到的呢?這由消息中間件的投遞流程來保證。

消息中間件向下遊系統投遞完消息後便進入阻塞等待狀態,下游系統便當即進行任務的處理,任務處理完成後便向消息中間件返回應答。消息中間件收到確認應答後便認爲該事務處理完畢!

若是消息在投遞過程當中丟失,或消息的確認應答在返回途中丟失,那麼消息中間件在等待確認應答超時以後就會從新投遞,直到下游消費者返回消費成功響應爲止。固然,通常消息中間件能夠設置消息重試的次數和時間間隔,好比:當第一次投遞失敗後,每隔五分鐘重試一次,一共重試3次。若是重試3次以後仍然投遞失敗,那麼這條消息就須要人工干預。

 

 

 

有的同窗可能要問:消息投遞失敗後爲何不回滾消息,而是不斷嘗試從新投遞?

這就涉及到整套分佈式事務系統的實現成本問題。咱們知道,當系統A將向消息中間件發送Commit指令後,它便去作別的事情了。若是此時消息投遞失敗,須要回滾的話,就須要讓系統A事先提供回滾接口,這無疑增長了額外的開發成本,業務系統的複雜度也將提升。對於一個業務系統的設計目標是,在保證性能的前提下,最大限度地下降系統複雜度,從而可以下降系統的運維成本。

不知你們是否發現,上游系統A向消息中間件提交Commit/Rollback消息採用的是異步方式,也就是當上遊系統提交完消息後即可以去作別的事情,接下來提交、回滾就徹底交給消息中間件來完成,而且徹底信任消息中間件,認爲它必定能正確地完成事務的提交或回滾。然而,消息中間件向下遊系統投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統後,它會阻塞等待,等下游系統成功處理完任務返回確認應答後才取消阻塞等待。爲何這二者在設計上是不一致的呢?

首先,上游系統和消息中間件之間採用異步通訊是爲了提升系統併發度。業務系統直接和用戶打交道,用戶體驗尤其重要,所以這種異步通訊方式可以極大程度地下降用戶等待時間。此外,異步通訊相對於同步通訊而言,沒有了長時間的阻塞等待,所以系統的併發性也大大增長。但異步通訊可能會引發Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

那麼,消息中間件和下游系統之間爲何要採用同步通訊呢?

異步能提高系統性能,但隨之會增長系統複雜度;而同步雖然下降系統併發度,但實現成本較低。所以,在對併發度要求不是很高的狀況下,或者服務器資源較爲充裕的狀況下,咱們能夠選擇同步來下降系統的複雜度。咱們知道,消息中間件是一個獨立於業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它通常部署在獨立的服務器集羣上,具備良好的可擴展性,因此沒必要太過於擔憂它的性能,若是處理速度沒法知足咱們的要求,能夠增長機器來解決。並且,即便消息中間件處理速度有必定的延遲那也是能夠接受的,由於前面所介紹的BASE理論就告訴咱們了,咱們追求的是最終一致性,而非實時一致性,所以消息中間件產生的時延致使事務短暫的不一致是能夠接受的。

方案3:最大努力通知(按期校對)也叫本地消息表

最大努力通知也被稱爲按期校對,其實在方案二中已經包含,這裏再單獨介紹,主要是爲了知識體系的完整性。這種方案也須要消息中間件的參與,其過程以下:

 

  • 上游系統在完成任務後,向消息中間件同步地發送一條消息,確保消息中間件成功持久化這條消息,而後上游系統能夠去作別的事情了;

  • 消息中間件收到消息後負責將該消息同步投遞給相應的下游系統,並觸發下游系統的任務執行;

  • 當下遊系統處理成功後,向消息中間件反饋確認應答,消息中間件即可以將該條消息刪除,從而該事務完成。

上面是一個理想化的過程,但在實際場景中,每每會出現以下幾種意外狀況:

  1. 消息中間件向下遊系統投遞消息失敗

  2. 上游系統向消息中間件發送消息失敗

對於第一種狀況,消息中間件具備重試機制,咱們能夠在消息中間件中設置消息的重試次數和重試時間間隔,對於網絡不穩定致使的消息投遞失敗的狀況,每每重試幾回後消息即可以成功投遞,若是超過了重試的上限仍然投遞失敗,那麼消息中間件再也不投遞該消息,而是記錄在失敗消息表中,消息中間件須要提供失敗消息的查詢接口下游系統會按期查詢失敗消息,並將其消費,這就是所謂的「按期校對」。

若是重複投遞和按期校對都不能解決問題,每每是由於下游系統出現了嚴重的錯誤,此時就須要人工干預

對於第二種狀況,須要在上游系統中創建消息重發機制。能夠在上游系統創建一張本地消息表,並將 任務處理過程向本地消息表中插入消息(保存的是要發送的內容和發送給誰的地址信息等) 這兩個步驟放在一個本地事務中完成。若是向本地消息表插入消息失敗,那麼就會觸發回滾,以前的任務處理結果就會被取消。若是這兩步都執行成功,那麼該本地事務就完成了。接下來會有一個專門的消息發送者不斷地發送本地消息表中的消息,若是發送失敗它會返回重試。固然,也要給消息發送者設置重試的上限,通常而言,達到重試上限仍然發送失敗,那就意味着消息中間件出現嚴重的問題,此時也只有人工干預才能解決問題

對於不支持事務型消息的消息中間件,若是要實現分佈式事務的話,就能夠採用這種方式。它可以經過重試機制+按期校對實現分佈式事務,但相比於第二種方案,它達到數據一致性的週期較長,並且還須要在上游系統中實現消息重試發佈機制,以確保消息成功發佈給消息中間件,這無疑增長了業務系統的開發成本,使得業務系統不夠純粹,而且這些額外的業務邏輯無疑會佔用業務系統的硬件資源,從而影響性能。

所以,儘可能選擇支持事務型消息的消息中間件來實現分佈式事務,如RocketMQ。

方案4:TCC(兩階段型、補償型)

跨應用的業務操做原子性要求,實際上是比較常見的。好比在第三方支付場景中的組合支付,用戶在電商網站購物後,要同時使用餘額和紅包支付該筆訂單,而餘額系統和紅包系統分別是不一樣的應用系統,支付系統在調用這兩個系統進行支付時,就須要保證餘額扣減和紅包使用要麼同時成功,要麼同時失敗。

TCC事務的出現正是爲了解決應用拆分帶來的跨應用業務操做原子性的問題。固然,因爲常規的XA事務(2PC,2 Phase Commit, 兩階段提交)性能上不盡如人意,也有經過TCC事務來解決數據庫拆分的使用場景(如帳務拆分),這個本文後續部分再詳述。

故從整個系統架構的角度來看,分佈式事務的不一樣方案是存在層次結構的。

TCC的機制

明眼一看就知道,TCC應該是三個英文單詞的首字母縮寫而來。沒錯,TCC分別對應Try、Confirm和Cancel三種操做, 這三種操做的業務含義以下:

Try:預留業務資源
Confirm:確認執行業務操做
Cancel:取消執行業務操做

稍稍對照下關係型數據庫事務的三種操做:DML、Commit和Rollback,會發現和TCC有殊途同歸之妙。在一個跨應用的業務操做中, Try操做是先將多個應用中的業務資源預留和鎖定住,爲後續的確認打下基礎,相似的,DML操做要鎖定數據庫記錄行,持有數據庫資源; Confirm操做是在Try操做中涉及的全部應用均成功以後進行確認,使用預留的業務資源,和Commit相似; 而Cancel則是當Try操做中涉及的全部應用沒有所有成功,須要將已成功的應用進行取消(即Rollback回滾)。 其中Confirm和Cancel操做是一對反向業務操做。

簡而言之,TCC是應用層的2PC(2 Phase Commit, 兩階段提交),若是你將應用看作資源管理器的話。 詳細來講,TCC每項操做須要作的事情以下:

一、Try:嘗試執行業務。 完成全部業務檢查(一致性) 預留必須業務資源(準隔離性) 二、Confirm:確認執行業務。 真正執行業務 不作任何業務檢查 只使用Try階段預留的業務資源 三、Cancel:取消執行業務 釋放Try階段預留的業務資源

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

主業務服務:主業務服務爲整個業務活動的發起方,如前面提到的組合支付場景,支付系統便是主業務服務。

從業務服務從業務服務負責提供TCC業務操做,是整個業務活動的操做方從業務服務必須實現Try、Confirm和Cancel三個接口,供主業務服務調用。因爲Confirm和Cancel操做可能被重複調用,故要求Confirm和Cancel兩個接口必須是冪等的。前面的組合支付場景中的餘額系統和紅包系統即爲從業務服務。

業務活動管理器:業務活動管理器管理控制整個業務活動,包括記錄維護TCC全局事務的事務狀態和每一個從業務服務的子事務狀態,並在業務活動提交時確認全部的TCC型操做的confirm操做,在業務活動取消時調用全部TCC型操做的cancel操做

可見整個TCC事務對於主業務服務來講是透明的,其中業務活動管理器和從業務服務各自幹了一部分工做。

TCC的優勢和限制

TCC事務的優勢以下: 解決了跨應用業務操做的原子性問題,在諸如組合支付、帳務拆分場景很是實用。 TCC實際上把數據庫層的二階段提交上提到了應用層來實現,對於數據庫來講是一階段提交,規避了數據庫層的2PC性能低下問題。

TCC事務的缺點,主要就一個: TCC的Try、Confirm和Cancel操做功能需業務提供開發成本高 固然,對TCC事務的這個缺點是不是缺點,是一個見仁見智的事情。

一個案例理解

TCC說實話,TCC的理論有點讓人費解。故接下來將以帳務拆分爲例,對TCC事務的流程作一個描述,但願對理解TCC有所幫助。 帳務拆分的業務場景以下,分別位於三個不一樣分庫的賬戶A、B、C,A和B一塊兒向C轉賬共80元:分佈式事務之說說TCC事務

一、Try:嘗試執行業務。 完成全部業務檢查(一致性):檢查A、B、C的賬戶狀態是否正常,賬戶A的餘額是否很多於30元,賬戶B的餘額是否很多於50元。 預留必須業務資源(準隔離性):賬戶A的凍結金額增長30元,賬戶B的凍結金額增長50元,這樣就保證不會出現其餘併發進程扣減了這兩個賬戶的餘額而致使在後續的真正轉賬操做過程當中,賬戶A和B的可用餘額不夠的狀況。

二、Confirm:確認執行業務。 真正執行業務:若是Try階段賬戶A、B、C狀態正常,且賬戶A、B餘額夠用,則執行賬戶A給帳戶C轉帳30元、賬戶B給帳戶C轉帳50元的轉賬操做。

不作任何業務檢查:這時已經不須要作業務檢查,Try階段已經完成了業務檢查。 只使用Try階段預留的業務資源:只須要使用Try階段賬戶A和賬戶B凍結的金額便可。

三、Cancel:取消執行業務 釋放Try階段預留的業務資源:若是Try階段部分紅功,好比賬戶A的餘額夠用,且凍結相應金額成功,賬戶B的餘額不夠而凍結失敗,則須要對賬戶A作Cancel操做,將賬戶A被凍結的金額解凍掉。

相關文章
相關標籤/搜索