前言
眾所周知,數(shù)據庫能實現(xiàn)本地事務拱礁,也就是在同一個數(shù)據庫中琢锋,你可以允許一組操作要么全都正確執(zhí)行辕漂,要么全都不執(zhí)行。這里特別強調了本地事務吴超,也就是目前的數(shù)據庫只能支持同一個數(shù)據庫中的事務钉嘹。但現(xiàn)在的系統(tǒng)往往采用微服務架構,業(yè)務系統(tǒng)擁有獨立的數(shù)據庫鲸阻,因此就出現(xiàn)了跨多個數(shù)據庫的事務需求跋涣,這種事務即為“分布式事務”。那么在目前數(shù)據庫不支持跨庫事務的情況下鸟悴,我們該如何實現(xiàn)分布式事務呢陈辱?本文首先會為大家梳理分布式事務的基本概念和理論基礎,然后介紹幾種目前常用的分布式事務解決方案细诸。
什么是事務沛贪?
事務由一組操作構成,我們希望這組操作能夠全部正確執(zhí)行震贵,如果這一組操作中的任意一個步驟發(fā)生錯誤利赋,那么就需要回滾之前已經完成的操作。也就是同一個事務中的所有操作猩系,要么全都正確執(zhí)行媚送,要么全都不要執(zhí)行。
事務的四大特性 ACID
說到事務寇甸,就得提到事務著名的四大特性塘偎。
原子性 原子性要求,事務是一個不可分割的執(zhí)行單元幽纷,事務中的所有操作要么全都執(zhí)行式塌,要么全都不執(zhí)行博敬。
一致性 一致性要求友浸,事務在開始前和結束后,數(shù)據庫的完整性約束沒有被破壞偏窝。
隔離性 事務的執(zhí)行是相互獨立的收恢,它們不會相互干擾,一個事務不會看到另一個正在運行過程中的事務的數(shù)據祭往。
-
持久性 持久性要求伦意,一個事務完成之后,事務的執(zhí)行結果必須是持久化保存的硼补。即使數(shù)據庫發(fā)生崩潰驮肉,在數(shù)據庫恢復后事務提交的結果仍然不會丟失。
注意:事務只能保證數(shù)據庫的高可靠性已骇,即數(shù)據庫本身發(fā)生問題后离钝,事務提交后的數(shù)據仍然能恢復票编;而如果不是數(shù)據庫本身的故障,如硬盤損壞了卵渴,那么事務提交的數(shù)據可能就丟失了慧域。這屬于『高可用性』的范疇。因此浪读,事務只能保證數(shù)據庫的『高可靠性』昔榴,而『高可用性』需要整個系統(tǒng)共同配合實現(xiàn)。
事務的隔離級別
這里擴展一下碘橘,對事務的隔離性做一個詳細的解釋互订。
在事務的四大特性ACID中,要求的隔離性是一種嚴格意義上的隔離痘拆,也就是多個事務是串行執(zhí)行的颅眶,彼此之間不會受到任何干擾价匠。這確實能夠完全保證數(shù)據的安全性,但在實際業(yè)務系統(tǒng)中,這種方式性能不高砾赔。因此,數(shù)據庫定義了四種隔離級別笔时,隔離級別和數(shù)據庫的性能是呈反比的颓屑,隔離級別越低,數(shù)據庫性能越高识颊,而隔離級別越高诚镰,數(shù)據庫性能越差。
事務并發(fā)執(zhí)行會出現(xiàn)的問題
我們先來看一下在不同的隔離級別下祥款,數(shù)據庫可能會出現(xiàn)的問題:
更新丟失 當有兩個并發(fā)執(zhí)行的事務清笨,更新同一行數(shù)據,那么有可能一個事務會把另一個事務的更新覆蓋掉刃跛。 當數(shù)據庫沒有加任何鎖操作的情況下會發(fā)生抠艾。
臟讀 一個事務讀到另一個尚未提交的事務中的數(shù)據。 該數(shù)據可能會被回滾從而失效桨昙。 如果第一個事務拿著失效的數(shù)據去處理那就發(fā)生錯誤了检号。
-
不可重復讀 不可重復度的含義:一個事務對同一行數(shù)據讀了兩次,卻得到了不同的結果蛙酪。它具體分為如下兩種情況:
虛讀:在事務1兩次讀取同一記錄的過程中齐苛,事務2對該記錄進行了修改,從而事務1第二次讀到了不一樣的記錄桂塞。
幻讀:事務1在兩次查詢的過程中凹蜂,事務2對該表進行了插入、刪除操作,從而事務1第二次查詢的結果發(fā)生了變化玛痊。
不可重復讀 與 臟讀 的區(qū)別泥彤? 臟讀讀到的是尚未提交的數(shù)據,而不可重復讀讀到的是已經提交的數(shù)據卿啡,只不過在兩次讀的過程中數(shù)據被另一個事務改過了吟吝。
數(shù)據庫的四種隔離級別
數(shù)據庫一共有如下四種隔離級別:
Read uncommitted 讀未提交 在該級別下,一個事務對一行數(shù)據修改的過程中颈娜,不允許另一個事務對該行數(shù)據進行修改剑逃,但允許另一個事務對該行數(shù)據讀。 因此本級別下官辽,不會出現(xiàn)更新丟失蛹磺,但會出現(xiàn)臟讀、不可重復讀同仆。
Read committed 讀提交 在該級別下萤捆,未提交的寫事務不允許其他事務訪問該行,因此不會出現(xiàn)臟讀俗批;但是讀取數(shù)據的事務允許其他事務的訪問該行數(shù)據俗或,因此會出現(xiàn)不可重復讀的情況。
Repeatable read 重復讀 在該級別下岁忘,讀事務禁止寫事務辛慰,但允許讀事務,因此不會出現(xiàn)同一事務兩次讀到不同的數(shù)據的情況(不可重復讀)干像,且寫事務禁止其他一切事務帅腌。
Serializable 序列化 該級別要求所有事務都必須串行執(zhí)行,因此能避免一切因并發(fā)引起的問題麻汰,但效率很低速客。
隔離級別越高,越能保證數(shù)據的完整性和一致性五鲫,但是對并發(fā)性能的影響也越大溺职。對于多數(shù)應用程序,可以優(yōu)先考慮把數(shù)據庫系統(tǒng)的隔離級別設為Read Committed臣镣。它能夠避免臟讀取辅愿,而且具有較好的并發(fā)性能智亮。盡管它會導致不可重復讀忆某、幻讀和第二類丟失更新這些并發(fā)問題,在可能出現(xiàn)這類問題的個別場合阔蛉,可以由應用程序采用悲觀鎖或樂觀鎖來控制弃舒。
什么是分布式事務?
到此為止,所介紹的事務都是基于單數(shù)據庫的本地事務聋呢,目前的數(shù)據庫僅支持單庫事務苗踪,并不支持跨庫事務。而隨著微服務架構的普及削锰,一個大型業(yè)務系統(tǒng)往往由若干個子系統(tǒng)構成通铲,這些子系統(tǒng)又擁有各自獨立的數(shù)據庫。往往一個業(yè)務流程需要由多個子系統(tǒng)共同完成器贩,而且這些操作可能需要在一個事務中完成颅夺。在微服務系統(tǒng)中,這些業(yè)務場景是普遍存在的蛹稍。此時吧黄,我們就需要在數(shù)據庫之上通過某種手段,實現(xiàn)支持跨數(shù)據庫的事務支持唆姐,這也就是大家常說的“分布式事務”拗慨。
這里舉一個分布式事務的典型例子——用戶下單過程。 當我們的系統(tǒng)采用了微服務架構后奉芦,一個電商系統(tǒng)往往被拆分成如下幾個子系統(tǒng):商品系統(tǒng)赵抢、訂單系統(tǒng)、支付系統(tǒng)声功、積分系統(tǒng)等昌讲。整個下單的過程如下:
用戶通過商品系統(tǒng)瀏覽商品,他看中了某一項商品减噪,便點擊下單
此時訂單系統(tǒng)會生成一條訂單
訂單創(chuàng)建成功后短绸,支付系統(tǒng)提供支付功能
當支付完成后,由積分系統(tǒng)為該用戶增加積分
上述步驟2筹裕、3醋闭、4需要在一個事務中完成。對于傳統(tǒng)單體應用而言朝卒,實現(xiàn)事務非常簡單证逻,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional注解標識該方法即可抗斤。Spring通過數(shù)據庫的事務支持囚企,保證這些步驟要么全都執(zhí)行完成,要么全都不執(zhí)行瑞眼。但在這個微服務架構中龙宏,這三個步驟涉及三個系統(tǒng),涉及三個數(shù)據庫伤疙,此時我們必須在數(shù)據庫和應用系統(tǒng)之間银酗,通過某項黑科技辆影,實現(xiàn)分布式事務的支持。
CAP理論
CAP理論說的是:在一個分布式系統(tǒng)中黍特,最多只能滿足C蛙讥、A、P中的兩個需求灭衷。
CAP的含義:
C:Consistency 一致性 同一數(shù)據的多個副本是否實時相同次慢。
A:Availability 可用性 可用性:一定時間內 & 系統(tǒng)返回一個明確的結果 則稱為該系統(tǒng)可用。
P:Partition tolerance 分區(qū)容錯性 將同一服務分布在多個系統(tǒng)中翔曲,從而保證某一個系統(tǒng)宕機经备,仍然有其他系統(tǒng)提供相同的服務。
CAP理論告訴我們部默,在分布式系統(tǒng)中侵蒙,C、A傅蹂、P三個條件中我們最多只能選擇兩個纷闺。那么問題來了,究竟選擇哪兩個條件較為合適呢份蝴?
對于一個業(yè)務系統(tǒng)來說犁功,可用性和分區(qū)容錯性是必須要滿足的兩個條件,并且這兩者是相輔相成的婚夫。業(yè)務系統(tǒng)之所以使用分布式系統(tǒng)浸卦,主要原因有兩個:
提升整體性能 當業(yè)務量猛增,單個服務器已經無法滿足我們的業(yè)務需求的時候案糙,就需要使用分布式系統(tǒng)限嫌,使用多個節(jié)點提供相同的功能,從而整體上提升系統(tǒng)的性能时捌,這就是使用分布式系統(tǒng)的第一個原因怒医。
實現(xiàn)分區(qū)容錯性 單一節(jié)點 或 多個節(jié)點處于相同的網絡環(huán)境下,那么會存在一定的風險奢讨,萬一該機房斷電稚叹、該地區(qū)發(fā)生自然災害,那么業(yè)務系統(tǒng)就全面癱瘓了拿诸。為了防止這一問題扒袖,采用分布式系統(tǒng),將多個子系統(tǒng)分布在不同的地域亩码、不同的機房中季率,從而保證系統(tǒng)高可用性。
這說明分區(qū)容錯性是分布式系統(tǒng)的根本蟀伸,如果分區(qū)容錯性不能滿足蚀同,那使用分布式系統(tǒng)將失去意義缅刽。
此外啊掏,可用性對業(yè)務系統(tǒng)也尤為重要蠢络。在大談用戶體驗的今天,如果業(yè)務系統(tǒng)時常出現(xiàn)“系統(tǒng)異吵倜郏”刹孔、響應時間過長等情況,這使得用戶對系統(tǒng)的好感度大打折扣娜睛,在互聯(lián)網行業(yè)競爭激烈的今天髓霞,相同領域的競爭者不甚枚舉,系統(tǒng)的間歇性不可用會立馬導致用戶流向競爭對手畦戒。因此方库,我們只能通過犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。這也就是下面要介紹的BASE理論障斋。
BASE理論
CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C纵潦、A、P中選擇兩個條件垃环。而對于業(yè)務系統(tǒng)而言邀层,我們往往選擇犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。不過這里要指出的是遂庄,所謂的“犧牲一致性”并不是完全放棄數(shù)據一致性寥院,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論涛目。
-
BA:Basic Available 基本可用
整個系統(tǒng)在某些不可抗力的情況下秸谢,仍然能夠保證“可用性”,即一定時間內仍然能夠返回一個明確的結果霹肝。只不過“基本可用”和“高可用”的區(qū)別是:
“一定時間”可以適當延長 當舉行大促時钮追,響應時間可以適當延長
給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務器壓力阿迈。但要注意元媚,返回降級頁面仍然是返回明確結果。
S:Soft State:柔性狀態(tài) 同一數(shù)據的不同副本的狀態(tài)苗沧,可以不需要實時一致刊棕。
E:Eventual Consisstency:最終一致性 同一數(shù)據的不同副本的狀態(tài),可以不需要實時一致待逞,但一定要保證經過一定時間后仍然是一致的甥角。
酸堿平衡
ACID能夠保證事務的強一致性,即數(shù)據是實時一致的识樱。這在本地事務中是沒有問題的嗤无,在分布式事務中震束,強一致性會極大影響分布式系統(tǒng)的性能,因此分布式系統(tǒng)中遵循BASE理論即可当犯。但分布式系統(tǒng)的不同業(yè)務場景對一致性的要求也不同垢村。如交易場景下,就要求強一致性嚎卫,此時就需要遵循ACID理論嘉栓,而在注冊成功后發(fā)送短信驗證碼等場景下,并不需要實時一致拓诸,因此遵循BASE理論即可侵佃。因此要根據具體業(yè)務場景,在ACID和BASE之間尋求平衡奠支。
分布式事務協(xié)議
下面介紹幾種實現(xiàn)分布式事務的協(xié)議馋辈。
兩階段提交協(xié)議 2PC
分布式系統(tǒng)的一個難點是如何保證架構下多個節(jié)點在進行事務性操作的時候保持一致性。為實現(xiàn)這個目的倍谜,二階段提交算法的成立基于以下假設:
該分布式系統(tǒng)中迈螟,存在一個節(jié)點作為協(xié)調者(Coordinator),其他節(jié)點作為參與者(Cohorts)枢劝。且節(jié)點之間可以進行網絡通信井联。
所有節(jié)點都采用預寫式日志,且日志被寫入后即被保持在可靠的存儲設備上您旁,即使節(jié)點損壞不會導致日志數(shù)據的消失烙常。
所有節(jié)點不會永久性損壞,即使損壞后仍然可以恢復鹤盒。
1. 第一階段(投票階段):
協(xié)調者節(jié)點向所有參與者節(jié)點詢問是否可以執(zhí)行提交操作(vote)蚕脏,并開始等待各參與者節(jié)點的響應。
參與者節(jié)點執(zhí)行詢問發(fā)起為止的所有事務操作侦锯,并將Undo信息和Redo信息寫入日志驼鞭。(注意:若成功這里其實每個參與者已經執(zhí)行了事務操作)
各參與者節(jié)點響應協(xié)調者節(jié)點發(fā)起的詢問。如果參與者節(jié)點的事務操作實際執(zhí)行成功尺碰,則它返回一個”同意”消息挣棕;如果參與者節(jié)點的事務操作實際執(zhí)行失敗,則它返回一個”中止”消息亲桥。
2. 第二階段(提交執(zhí)行階段):
當協(xié)調者節(jié)點從所有參與者節(jié)點獲得的相應消息都為”同意”時:
協(xié)調者節(jié)點向所有參與者節(jié)點發(fā)出”正式提交(commit)”的請求洛心。
參與者節(jié)點正式完成操作,并釋放在整個事務期間內占用的資源题篷。
參與者節(jié)點向協(xié)調者節(jié)點發(fā)送”完成”消息词身。
協(xié)調者節(jié)點受到所有參與者節(jié)點反饋的”完成”消息后,完成事務番枚。
如果任一參與者節(jié)點在第一階段返回的響應消息為”中止”法严,或者 協(xié)調者節(jié)點在第一階段的詢問超時之前無法獲取所有參與者節(jié)點的響應消息時:
協(xié)調者節(jié)點向所有參與者節(jié)點發(fā)出”回滾操作(rollback)”的請求损敷。
參與者節(jié)點利用之前寫入的Undo信息執(zhí)行回滾,并釋放在整個事務期間內占用的資源深啤。
參與者節(jié)點向協(xié)調者節(jié)點發(fā)送”回滾完成”消息拗馒。
協(xié)調者節(jié)點受到所有參與者節(jié)點反饋的”回滾完成”消息后,取消事務墓塌。
不管最后結果如何瘟忱,第二階段都會結束當前事務奥额。
二階段提交看起來確實能夠提供原子性的操作苫幢,但是不幸的事,二階段提交還是有幾個缺點的:
執(zhí)行過程中垫挨,所有參與節(jié)點都是事務阻塞型的韩肝。當參與者占有公共資源時,其他第三方節(jié)點訪問公共資源不得不處于阻塞狀態(tài)九榔。
參與者發(fā)生故障哀峻。協(xié)調者需要給每個參與者額外指定超時機制,超時后整個事務失敗哲泊。(沒有多少容錯機制)
協(xié)調者發(fā)生故障剩蟀。參與者會一直阻塞下去。需要額外的備機進行容錯切威。(這個可以依賴后面要講的Paxos協(xié)議實現(xiàn)HA)
二階段無法解決的問題:協(xié)調者再發(fā)出commit消息之后宕機育特,而唯一接收到這條消息的參與者同時也宕機了。那么即使協(xié)調者通過選舉協(xié)議產生了新的協(xié)調者先朦,這條事務的狀態(tài)也是不確定的缰冤,沒人知道事務是否被已經提交。
為此喳魏,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協(xié)議(3PC)棉浸。
三階段提交協(xié)議 3PC
與兩階段提交不同的是,三階段提交有兩個改動點刺彩。
引入超時機制迷郑。同時在協(xié)調者和參與者中都引入超時機制。
在第一階段和第二階段中插入一個準備階段创倔。保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的嗡害。
也就是說,除了引入超時機制之外三幻,3PC把2PC的準備階段再次一分為二就漾,這樣三階段提交就有CanCommit、PreCommit念搬、DoCommit三個階段抑堡。
1. CanCommit階段
3PC的CanCommit階段其實和2PC的準備階段很像摆出。協(xié)調者向參與者發(fā)送commit請求,參與者如果可以提交就返回Yes響應首妖,否則返回No響應偎漫。
事務詢問 協(xié)調者向參與者發(fā)送CanCommit請求。詢問是否可以執(zhí)行事務提交操作有缆。然后開始等待參與者的響應象踊。
響應反饋 參與者接到CanCommit請求之后,正常情況下棚壁,如果其自身認為可以順利執(zhí)行事務杯矩,則返回Yes響應,并進入預備狀態(tài)袖外。否則反饋No
2. PreCommit階段
協(xié)調者根據參與者的反應情況來決定是否可以記性事務的PreCommit操作史隆。根據響應情況,有以下兩種可能曼验。 假如協(xié)調者從所有的參與者獲得的反饋都是Yes響應泌射,那么就會執(zhí)行事務的預執(zhí)行。
發(fā)送預提交請求 協(xié)調者向參與者發(fā)送PreCommit請求鬓照,并進入Prepared階段熔酷。
事務預提交 參與者接收到PreCommit請求后,會執(zhí)行事務操作豺裆,并將undo和redo信息記錄到事務日志中拒秘。
響應反饋 如果參與者成功的執(zhí)行了事務操作,則返回ACK響應留储,同時開始等待最終指令翼抠。
假如有任何一個參與者向協(xié)調者發(fā)送了No響應,或者等待超時之后获讳,協(xié)調者都沒有接到參與者的響應阴颖,那么就執(zhí)行事務的中斷。
發(fā)送中斷請求 協(xié)調者向所有參與者發(fā)送abort請求丐膝。
中斷事務 參與者收到來自協(xié)調者的abort請求之后(或超時之后量愧,仍未收到協(xié)調者的請求),執(zhí)行事務的中斷帅矗。
3. doCommit階段 該階段進行真正的事務提交偎肃,也可以分為以下兩種情況。
該階段進行真正的事務提交浑此,也可以分為以下兩種情況累颂。
3.1 執(zhí)行提交
發(fā)送提交請求 協(xié)調接收到參與者發(fā)送的ACK響應,那么他將從預提交狀態(tài)進入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請求紊馏。
事務提交 參與者接收到doCommit請求之后料饥,執(zhí)行正式的事務提交。并在完成事務提交之后釋放所有事務資源朱监。
響應反饋 事務提交完之后岸啡,向協(xié)調者發(fā)送Ack響應。
完成事務 協(xié)調者接收到所有參與者的ack響應之后赫编,完成事務巡蘸。
3.2 中斷事務 協(xié)調者沒有接收到參與者發(fā)送的ACK響應(可能是接受者發(fā)送的不是ACK響應,也可能響應超時)擂送,那么就會執(zhí)行中斷事務悦荒。
發(fā)送中斷請求 協(xié)調者向所有參與者發(fā)送abort請求
事務回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執(zhí)行事務的回滾操作团甲,并在完成回滾之后釋放所有的事務資源逾冬。
反饋結果 參與者完成事務回滾之后黍聂,向協(xié)調者發(fā)送ACK消息
中斷事務 協(xié)調者接收到參與者反饋的ACK消息之后躺苦,執(zhí)行事務的中斷。
分布式事務的解決方案
分布式事務的解決方案有如下幾種:
全局消息
基于可靠消息服務的分布式事務
TCC
最大努力通知
方案1:全局事務(DTP模型)
全局事務基于DTP模型實現(xiàn)产还。DTP是由X/Open組織提出的一種分布式事務模型——X/Open Distributed Transaction Processing Reference Model匹厘。它規(guī)定了要實現(xiàn)分布式事務,需要三種角色:
AP:Application 應用系統(tǒng) 它就是我們開發(fā)的業(yè)務系統(tǒng)脐区,在我們開發(fā)的過程中愈诚,可以使用資源管理器提供的事務接口來實現(xiàn)分布式事務。
-
TM:Transaction Manager 事務管理器
分布式事務的實現(xiàn)由事務管理器來完成牛隅,它會提供分布式事務的操作接口供我們的業(yè)務系統(tǒng)調用炕柔。這些接口稱為TX接口。
事務管理器還管理著所有的資源管理器媒佣,通過它們提供的XA接口來同一調度這些資源管理器匕累,以實現(xiàn)分布式事務。
DTP只是一套實現(xiàn)分布式事務的規(guī)范默伍,并沒有定義具體如何實現(xiàn)分布式事務欢嘿,TM可以采用2PC、3PC也糊、Paxos等協(xié)議實現(xiàn)分布式事務。
-
RM:Resource Manager 資源管理器
能夠提供數(shù)據服務的對象都可以是資源管理器,比如:數(shù)據庫徐矩、消息中間件烹玉、緩存等。大部分場景下钞馁,數(shù)據庫即為分布式事務中的資源管理器虑省。
資源管理器能夠提供單數(shù)據庫的事務能力斗搞,它們通過XA接口,將本數(shù)據庫的提交僻焚、回滾等能力提供給事務管理器調用膝擂,以幫助事務管理器實現(xiàn)分布式的事務管理。
XA是DTP模型定義的接口架馋,用于向事務管理器提供該資源管理器(該數(shù)據庫)的提交、回滾等能力叉寂。
DTP只是一套實現(xiàn)分布式事務的規(guī)范,RM具體的實現(xiàn)是由數(shù)據庫廠商來完成的屏鳍。
- 有沒有基于DTP模型的分布式事務中間件勘纯?
- DTP模型有啥優(yōu)缺點钓瞭?
方案2:基于可靠消息服務的分布式事務
這種實現(xiàn)分布式事務的方式需要通過消息中間件來實現(xiàn)山涡。假設有A和B兩個系統(tǒng)鸭丛,分別可以處理任務A和任務B。此時系統(tǒng)A中存在一個業(yè)務流程瘾带,需要將任務A和任務B在同一個事務中處理穿挨。下面來介紹基于消息中間件來實現(xiàn)這種分布式事務科盛。
在系統(tǒng)A處理任務A前贞绵,首先向消息中間件發(fā)送一條消息
消息中間件收到后將該條消息持久化,但并不投遞谴垫。此時下游系統(tǒng)B仍然不知道該條消息的存在。
消息中間件持久化成功后乳怎,便向系統(tǒng)A返回一個確認應答蚪缀;
系統(tǒng)A收到確認應答后恕出,則可以開始處理任務A;
任務A處理完成后金蜀,向消息中間件發(fā)送Commit請求的畴。該請求發(fā)送完成后苗傅,對系統(tǒng)A而言渣慕,該事務的處理過程就結束了逊桦,此時它可以處理別的任務了抑进。 但commit消息可能會在傳輸途中丟失寺渗,從而消息中間件并不會向系統(tǒng)B投遞這條消息信殊,從而系統(tǒng)就會出現(xiàn)不一致性涡拘。這個問題由消息中間件的事務回查機制完成,下文會介紹跷车。
消息中間件收到Commit指令后朽缴,便向系統(tǒng)B投遞該消息密强,從而觸發(fā)任務B的執(zhí)行誓斥;
當任務B執(zhí)行完成后,系統(tǒng)B向消息中間件返回一個確認應答毕谴,告訴消息中間件該消息已經成功消費涝开,此時舀武,這個分布式事務完成银舱。
上述過程中咬像,如果任務A處理失敗县昂,那么需要進入回滾流程,如下圖所示:上述過程可以得出如下幾個結論: 1. 消息中間件扮演者分布式事務協(xié)調者的角色寻馏。 2. 系統(tǒng)A完成任務A后核偿,到任務B執(zhí)行完成之間漾岳,會存在一定的時間差尼荆。在這個時間差內耀找,整個系統(tǒng)處于數(shù)據不一致的狀態(tài),但這短暫的不一致性是可以接受的蓄愁,因為經過短暫的時間后撮抓,系統(tǒng)又可以保持數(shù)據一致性丹拯,滿足BASE理論乖酬。
若系統(tǒng)A在處理任務A時失敗,那么就會向消息中間件發(fā)送Rollback請求芒澜。和發(fā)送Commit請求一樣撰糠,系統(tǒng)A發(fā)完之后便可以認為回滾已經完成辩昆,它便可以去做其他的事情汁针。
消息中間件收到回滾請求后施无,直接將該消息丟棄猾骡,而不投遞給系統(tǒng)B兴想,從而不會觸發(fā)系統(tǒng)B的任務B嫂便。
此時系統(tǒng)又處于一致性狀態(tài),因為任務A和任務B都沒有執(zhí)行岸售。
上面所介紹的Commit和Rollback都屬于理想情況凸丸,但在實際系統(tǒng)中屎慢,Commit和Rollback指令都有可能在傳輸途中丟失抛人。那么當出現(xiàn)這種情況的時候妖枚,消息中間件是如何保證數(shù)據一致性呢绝页?——答案就是超時詢問機制续誉。
系統(tǒng)A除了實現(xiàn)正常的業(yè)務流程外酷鸦,還需提供一個事務詢問的接口臼隔,供消息中間件調用摔握。當消息中間件收到一條事務型消息后便開始計時氨淌,如果到了超時時間也沒收到系統(tǒng)A發(fā)來的Commit或Rollback指令的話盛正,就會主動調用系統(tǒng)A提供的事務詢問接口詢問該系統(tǒng)目前的狀態(tài)蛮艰。該接口會返回三種結果:
提交 若獲得的狀態(tài)是“提交”壤蚜,則將該消息投遞給系統(tǒng)B袜刷。
回滾 若獲得的狀態(tài)是“回滾”著蟹,則直接將條消息丟棄萧豆。
處理中 若獲得的狀態(tài)是“處理中”涮雷,則繼續(xù)等待洪鸭。
消息中間件的超時詢問機制能夠防止上游系統(tǒng)因在傳輸過程中丟失Commit/Rollback指令而導致的系統(tǒng)不一致情況览爵,而且能降低上游系統(tǒng)的阻塞時間蜓竹,上游系統(tǒng)只要發(fā)出Commit/Rollback指令后便可以處理其他任務俱济,無需等待確認應答姨蝴。而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補左医,這樣大大降低上游系統(tǒng)的阻塞時間浮梢,提升系統(tǒng)的并發(fā)度秕硝。
下面來說一說消息投遞過程的可靠性保證远豺。 當上游系統(tǒng)執(zhí)行完任務并向消息中間件提交了Commit指令后躯护,便可以處理其他任務了棺滞,此時它可以認為事務已經完成继准,接下來消息中間件一定會保證消息被下游系統(tǒng)成功消費掉移必!那么這是怎么做到的呢避凝?這由消息中間件的投遞流程來保證管削。
消息中間件向下游系統(tǒng)投遞完消息后便進入阻塞等待狀態(tài)含思,下游系統(tǒng)便立即進行任務的處理含潘,任務處理完成后便向消息中間件返回應答遏弱。消息中間件收到確認應答后便認為該事務處理完畢漱逸!
如果消息在投遞過程中丟失,或消息的確認應答在返回途中丟失诀黍,那么消息中間件在等待確認應答超時之后就會重新投遞眯勾,直到下游消費者返回消費成功響應為止吃环。當然模叙,一般消息中間件可以設置消息重試的次數(shù)和時間間隔范咨,比如:當?shù)谝淮瓮哆f失敗后渠啊,每隔五分鐘重試一次,一共重試3次拄氯。如果重試3次之后仍然投遞失敗镣煮,那么這條消息就需要人工干預典唇。有的同學可能要問:消息投遞失敗后為什么不回滾消息,而是不斷嘗試重新投遞炎咖?
這就涉及到整套分布式事務系統(tǒng)的實現(xiàn)成本問題塘装。 我們知道蹦肴,當系統(tǒng)A將向消息中間件發(fā)送Commit指令后阴幌,它便去做別的事情了矛双。如果此時消息投遞失敗议忽,需要回滾的話栈幸,就需要讓系統(tǒng)A事先提供回滾接口,這無疑增加了額外的開發(fā)成本芍锚,業(yè)務系統(tǒng)的復雜度也將提高并炮。對于一個業(yè)務系統(tǒng)的設計目標是逃魄,在保證性能的前提下,最大限度地降低系統(tǒng)復雜度壹若,從而能夠降低系統(tǒng)的運維成本嗅钻。
不知大家是否發(fā)現(xiàn),上游系統(tǒng)A向消息中間件提交Commit/Rollback消息采用的是異步方式店展,也就是當上游系統(tǒng)提交完消息后便可以去做別的事情养篓,接下來提交、回滾就完全交給消息中間件來完成赂蕴,并且完全信任消息中間件,認為它一定能正確地完成事務的提交或回滾。然而碧注,消息中間件向下游系統(tǒng)投遞消息的過程是同步的嚣伐。也就是消息中間件將消息投遞給下游系統(tǒng)后,它會阻塞等待萍丐,等下游系統(tǒng)成功處理完任務返回確認應答后才取消阻塞等待轩端。為什么這兩者在設計上是不一致的呢?
首先逝变,上游系統(tǒng)和消息中間件之間采用異步通信是為了提高系統(tǒng)并發(fā)度基茵。業(yè)務系統(tǒng)直接和用戶打交道,用戶體驗尤為重要壳影,因此這種異步通信方式能夠極大程度地降低用戶等待時間拱层。此外,異步通信相對于同步通信而言宴咧,沒有了長時間的阻塞等待根灯,因此系統(tǒng)的并發(fā)性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題掺栅,這就由消息中間件的超時詢問機制來彌補烙肺。
那么,消息中間件和下游系統(tǒng)之間為什么要采用同步通信呢柿冲?
異步能提升系統(tǒng)性能茬高,但隨之會增加系統(tǒng)復雜度;而同步雖然降低系統(tǒng)并發(fā)度假抄,但實現(xiàn)成本較低怎栽。因此,在對并發(fā)度要求不是很高的情況下宿饱,或者服務器資源較為充裕的情況下熏瞄,我們可以選擇同步來降低系統(tǒng)的復雜度。 我們知道谬以,消息中間件是一個獨立于業(yè)務系統(tǒng)的第三方中間件强饮,它不和任何業(yè)務系統(tǒng)產生直接的耦合,它也不和用戶產生直接的關聯(lián)为黎,它一般部署在獨立的服務器集群上邮丰,具有良好的可擴展性,所以不必太過于擔心它的性能铭乾,如果處理速度無法滿足我們的要求剪廉,可以增加機器來解決。而且炕檩,即使消息中間件處理速度有一定的延遲那也是可以接受的斗蒋,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性泉沾,因此消息中間件產生的時延導致事務短暫的不一致是可以接受的捞蚂。
方案3:最大努力通知(定期校對)
最大努力通知也被稱為定期校對,其實在方案二中已經包含跷究,這里再單獨介紹姓迅,主要是為了知識體系的完整性。這種方案也需要消息中間件的參與揭朝,其過程如下:
上游系統(tǒng)在完成任務后队贱,向消息中間件同步地發(fā)送一條消息,確保消息中間件成功持久化這條消息潭袱,然后上游系統(tǒng)可以去做別的事情了;
消息中間件收到消息后負責將該消息同步投遞給相應的下游系統(tǒng)锋恬,并觸發(fā)下游系統(tǒng)的任務執(zhí)行屯换;
當下游系統(tǒng)處理成功后,向消息中間件反饋確認應答与学,消息中間件便可以將該條消息刪除彤悔,從而該事務完成。
上面是一個理想化的過程索守,但在實際場景中晕窑,往往會出現(xiàn)如下幾種意外情況:
消息中間件向下游系統(tǒng)投遞消息失敗
上游系統(tǒng)向消息中間件發(fā)送消息失敗
對于第一種情況,消息中間件具有重試機制卵佛,我們可以在消息中間件中設置消息的重試次數(shù)和重試時間間隔杨赤,對于網絡不穩(wěn)定導致的消息投遞失敗的情況,往往重試幾次后消息便可以成功投遞截汪,如果超過了重試的上限仍然投遞失敗疾牲,那么消息中間件不再投遞該消息,而是記錄在失敗消息表中衙解,消息中間件需要提供失敗消息的查詢接口阳柔,下游系統(tǒng)會定期查詢失敗消息,并將其消費蚓峦,這就是所謂的“定期校對”舌剂。
如果重復投遞和定期校對都不能解決問題,往往是因為下游系統(tǒng)出現(xiàn)了嚴重的錯誤暑椰,此時就需要人工干預霍转。
對于第二種情況,需要在上游系統(tǒng)中建立消息重發(fā)機制干茉∏从牵可以在上游系統(tǒng)建立一張本地消息表,并將 任務處理過程 和 向本地消息表中插入消息 這兩個步驟放在一個本地事務中完成。如果向本地消息表插入消息失敗沾谓,那么就會觸發(fā)回滾委造,之前的任務處理結果就會被取消。如果這量步都執(zhí)行成功均驶,那么該本地事務就完成了昏兆。接下來會有一個專門的消息發(fā)送者不斷地發(fā)送本地消息表中的消息,如果發(fā)送失敗它會返回重試妇穴。當然爬虱,也要給消息發(fā)送者設置重試的上限,一般而言腾它,達到重試上限仍然發(fā)送失敗跑筝,那就意味著消息中間件出現(xiàn)嚴重的問題,此時也只有人工干預才能解決問題瞒滴。
對于不支持事務型消息的消息中間件曲梗,如果要實現(xiàn)分布式事務的話,就可以采用這種方式妓忍。它能夠通過重試機制+定期校對實現(xiàn)分布式事務虏两,但相比于第二種方案,它達到數(shù)據一致性的周期較長世剖,而且還需要在上游系統(tǒng)中實現(xiàn)消息重試發(fā)布機制定罢,以確保消息成功發(fā)布給消息中間件,這無疑增加了業(yè)務系統(tǒng)的開發(fā)成本旁瘫,使得業(yè)務系統(tǒng)不夠純粹祖凫,并且這些額外的業(yè)務邏輯無疑會占用業(yè)務系統(tǒng)的硬件資源,從而影響性能境蜕。
因此蝙场,盡量選擇支持事務型消息的消息中間件來實現(xiàn)分布式事務,如RocketMQ粱年。
方案4:TCC(兩階段型售滤、補償型)
TCC即為Try Confirm Cancel,它屬于補償型分布式事務台诗。顧名思義完箩,TCC實現(xiàn)分布式事務一共有三個步驟:
-
Try:嘗試待執(zhí)行的業(yè)務
- 這個過程并未執(zhí)行業(yè)務,只是完成所有業(yè)務的一致性檢查拉队,并預留好執(zhí)行所需的全部資源
-
Confirm:執(zhí)行業(yè)務
- 這個過程真正開始執(zhí)行業(yè)務弊知,由于Try階段已經完成了一致性檢查,因此本過程直接執(zhí)行粱快,而不做任何檢查秩彤。并且在執(zhí)行的過程中叔扼,會使用到Try階段預留的業(yè)務資源。
-
Cancel:取消執(zhí)行的業(yè)務
- 若業(yè)務執(zhí)行失敗漫雷,則進入Cancel階段瓜富,它會釋放所有占用的業(yè)務資源,并回滾Confirm階段執(zhí)行的操作降盹。
下面以一個轉賬的例子來解釋下TCC實現(xiàn)分布式事務的過程与柑。
假設用戶A用他的賬戶余額給用戶B發(fā)一個100元的紅包,并且余額系統(tǒng)和紅包系統(tǒng)是兩個獨立的系統(tǒng)蓄坏。
-
Try
創(chuàng)建一條轉賬流水价捧,并將流水的狀態(tài)設為交易中
將用戶A的賬戶中扣除100元(預留業(yè)務資源)
Try成功之后,便進入Confirm階段
Try過程發(fā)生任何異常涡戳,均進入Cancel階段
-
Confirm
向B用戶的紅包賬戶中增加100元
將流水的狀態(tài)設為交易已完成
Confirm過程發(fā)生任何異常结蟋,均進入Cancel階段
Confirm過程執(zhí)行成功,則該事務結束
-
Cancel
將用戶A的賬戶增加100元
將流水的狀態(tài)設為交易失敗
在傳統(tǒng)事務機制中渔彰,業(yè)務邏輯的執(zhí)行和事務的處理椎眯,是在不同的階段由不同的部件來完成的:業(yè)務邏輯部分訪問資源實現(xiàn)數(shù)據存儲,其處理是由業(yè)務系統(tǒng)負責胳岂;事務處理部分通過協(xié)調資源管理器以實現(xiàn)事務管理,其處理由事務管理器來負責舔稀。二者沒有太多交互的地方乳丰,所以,傳統(tǒng)事務管理器的事務處理邏輯内贮,僅需要著眼于事務完成(commit/rollback)階段产园,而不必關注業(yè)務執(zhí)行階段。
TCC全局事務必須基于RM本地事務來實現(xiàn)全局事務
TCC服務是由Try/Confirm/Cancel業(yè)務構成的夜郁, 其Try/Confirm/Cancel業(yè)務在執(zhí)行時什燕,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數(shù)據竞端。這些存取操作屎即,必須要參與RM本地事務,以使其更改的數(shù)據要么都commit事富,要么都rollback技俐。
這一點不難理解,考慮一下如下場景:
假設圖中的服務B沒有基于RM本地事務(以RDBS為例统台,可通過設置auto-commit為true來模擬)雕擂,那么一旦[B:Try]操作中途執(zhí)行失敗,TCC事務框架后續(xù)決定回滾全局事務時贱勃,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經寫到DB井赌、哪些操作還沒有寫到DB:假設[B:Try]業(yè)務有5個寫庫操作谤逼,[B:Cancel]業(yè)務則需要逐個判斷這5個操作是否生效,并將生效的操作執(zhí)行反向操作仇穗。
不幸的是流部,由于[B:Cancel]業(yè)務也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯仪缸,則后續(xù)的[B:Cancel]執(zhí)行任務更加繁重贵涵。因為,相比第一次[B:Cancel]操作恰画,后續(xù)的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經執(zhí)行宾茂、哪幾個還沒有執(zhí)行,這就涉及到了冪等性問題拴还。而對冪等性的保障跨晴,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因為沒有RM本地事務的支持而存在類似問題片林。端盆。》逊猓可想而知焕妙,如果不基于RM本地事務,TCC事務框架是無法有效的管理TCC全局事務的弓摘。
反之焚鹊,基于RM本地事務的TCC事務,這種情況則會很容易處理:[B:Try]操作中途執(zhí)行失敗韧献,TCC事務框架將其參與RM本地事務直接rollback即可末患。后續(xù)TCC事務框架決定回滾全局事務時,在知道“[B:Try]操作涉及的RM本地事務已經rollback”的情況下锤窑,根本無需執(zhí)行[B:Cancel]操作璧针。
換句話說,基于RM本地事務實現(xiàn)TCC事務框架時渊啰,一個TCC型服務的cancel業(yè)務要么執(zhí)行探橱,要么不執(zhí)行,不需要考慮部分執(zhí)行的情況虽抄。
TCC事務框架應該提供Confirm/Cancel服務的冪等性保障
一般認為走搁,服務的冪等性,是指針對同一個服務的多次(n>1)請求和對它的單次(n=1)請求迈窟,二者具有相同的副作用私植。
在TCC事務模型中,Confirm/Cancel業(yè)務可能會被重復調用车酣,其原因很多曲稼。比如索绪,全局事務在提交/回滾時會調用各TCC服務的Confirm/Cancel業(yè)務邏輯。執(zhí)行這些Confirm/Cancel業(yè)務時贫悄,可能會出現(xiàn)如網絡中斷的故障而使得全局事務不能完成瑞驱。因此,故障恢復機制后續(xù)仍然會重新提交/回滾這些未完成的全局事務窄坦,這樣就會再次調用參與該全局事務的各TCC服務的Confirm/Cancel業(yè)務邏輯唤反。
既然Confirm/Cancel業(yè)務可能會被多次調用,就需要保障其冪等性鸭津。 那么彤侍,應該由TCC事務框架來提供冪等性保障?還是應該由業(yè)務系統(tǒng)自行來保障冪等性呢逆趋? 個人認為盏阶,應該是由TCC事務框架來提供冪等性保障。如果僅僅只是極個別服務存在這個問題的話闻书,那么由業(yè)務系統(tǒng)來負責也是可以的名斟;然而,這是一類公共問題魄眉,毫無疑問砰盐,所有TCC服務的Confirm/Cancel業(yè)務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決坑律;而且楞卡,考慮一下由業(yè)務系統(tǒng)來負責冪等性需要考慮的問題,就會發(fā)現(xiàn)脾歇,這無疑增大了業(yè)務系統(tǒng)的復雜度。