眾所周知,數(shù)據(jù)庫能實(shí)現(xiàn)本地事務(wù),也就是在同一個數(shù)據(jù)庫中,你可以允許一組操作要么全都正確執(zhí)行,要么全都不執(zhí)行。這里特別強(qiáng)調(diào)了本地事務(wù),也就是目前的數(shù)據(jù)庫只能支持同一個數(shù)據(jù)庫中的事務(wù)。但現(xiàn)在的系統(tǒng)往往采用微服務(wù)架構(gòu),業(yè)務(wù)系統(tǒng)擁有獨(dú)立的數(shù)據(jù)庫,因此就出現(xiàn)了跨多個數(shù)據(jù)庫的事務(wù)需求,這種事務(wù)即為“分布式事務(wù)”。那么在目前數(shù)據(jù)庫不支持跨庫事務(wù)的情況下,我們應(yīng)該如何實(shí)現(xiàn)分布式事務(wù)呢?本文首先會為大家梳理分布式事務(wù)的基本概念和理論基礎(chǔ),然后介紹幾種目前常用的分布式事務(wù)解決方案。廢話不多說,那就開始吧~
什么是事務(wù)?
事務(wù)由一組操作構(gòu)成,我們希望這組操作能夠全部正確執(zhí)行,如果這一組操作中的任意一個步驟發(fā)生錯誤,那么就需要回滾之前已經(jīng)完成的操作。也就是同一個事務(wù)中的所有操作,要么全都正確執(zhí)行,要么全都不要執(zhí)行。
事務(wù)的四大特性 ACID
說到事務(wù),就不得不提一下事務(wù)著名的四大特性。
- 原子性 原子性要求,事務(wù)是一個不可分割的執(zhí)行單元,事務(wù)中的所有操作要么全都執(zhí)行,要么全都不執(zhí)行。
- 一致性 一致性要求,事務(wù)在開始前和結(jié)束后,數(shù)據(jù)庫的完整性約束沒有被破壞。
- 隔離性 事務(wù)的執(zhí)行是相互獨(dú)立的,它們不會相互干擾,一個事務(wù)不會看到另一個正在運(yùn)行過程中的事務(wù)的數(shù)據(jù)。
- 持久性 持久性要求,一個事務(wù)完成之后,事務(wù)的執(zhí)行結(jié)果必須是持久化保存的。即使數(shù)據(jù)庫發(fā)生崩潰,在數(shù)據(jù)庫恢復(fù)后事務(wù)提交的結(jié)果仍然不會丟失。
注意:事務(wù)只能保證數(shù)據(jù)庫的高可靠性,即數(shù)據(jù)庫本身發(fā)生問題后,事務(wù)提交后的數(shù)據(jù)仍然能恢復(fù);而如果不是數(shù)據(jù)庫本身的故障,如硬盤損壞了,那么事務(wù)提交的數(shù)據(jù)可能就丟失了。這屬于『高可用性』的范疇。因此,事務(wù)只能保證數(shù)據(jù)庫的『高可靠性』,而『高可用性』需要整個系統(tǒng)共同配合實(shí)現(xiàn)。
事務(wù)的隔離級別
這里擴(kuò)展一下,對事務(wù)的隔離性做一個詳細(xì)的解釋。
在事務(wù)的四大特性ACID中,要求的隔離性是一種嚴(yán)格意義上的隔離,也就是多個事務(wù)是串行執(zhí)行的,彼此之間不會受到任何干擾。這確實(shí)能夠完全保證數(shù)據(jù)的安全性,但在實(shí)際業(yè)務(wù)系統(tǒng)中,這種方式性能不高。因此,數(shù)據(jù)庫定義了四種隔離級別,隔離級別和數(shù)據(jù)庫的性能是呈反比的,隔離級別越低,數(shù)據(jù)庫性能越高,而隔離級別越高,數(shù)據(jù)庫性能越差。
事務(wù)并發(fā)執(zhí)行會出現(xiàn)的問題
我們先來看一下在不同的隔離級別下,數(shù)據(jù)庫可能會出現(xiàn)的問題:
- 更新丟失 當(dāng)有兩個并發(fā)執(zhí)行的事務(wù),更新同一行數(shù)據(jù),那么有可能一個事務(wù)會把另一個事務(wù)的更新覆蓋掉。 當(dāng)數(shù)據(jù)庫沒有加任何鎖操作的情況下會發(fā)生。
- 臟讀 一個事務(wù)讀到另一個尚未提交的事務(wù)中的數(shù)據(jù)。 該數(shù)據(jù)可能會被回滾從而失效。 如果第一個事務(wù)拿著失效的數(shù)據(jù)去處理那就發(fā)生錯誤了。
- 不可重復(fù)讀 不可重復(fù)度的含義:一個事務(wù)對同一行數(shù)據(jù)讀了兩次,卻得到了不同的結(jié)果。它具體分為如下兩種情況:
- 虛讀:在事務(wù)1兩次讀取同一記錄的過程中,事務(wù)2對該記錄進(jìn)行了修改,從而事務(wù)1第二次讀到了不一樣的記錄。
- 幻讀:事務(wù)1在兩次查詢的過程中,事務(wù)2對該表進(jìn)行了插入、刪除操作,從而事務(wù)1第二次查詢的結(jié)果發(fā)生了變化。
不可重復(fù)讀 與 臟讀 的區(qū)別? 臟讀讀到的是尚未提交的數(shù)據(jù),而不可重復(fù)讀讀到的是已經(jīng)提交的數(shù)據(jù),只不過在兩次讀的過程中數(shù)據(jù)被另一個事務(wù)改過了。
數(shù)據(jù)庫的四種隔離級別
數(shù)據(jù)庫一共有如下四種隔離級別:
- Read uncommitted 讀未提交 在該級別下,一個事務(wù)對一行數(shù)據(jù)修改的過程中,不允許另一個事務(wù)對該行數(shù)據(jù)進(jìn)行修改,但允許另一個事務(wù)對該行數(shù)據(jù)讀。 因此本級別下,不會出現(xiàn)更新丟失,但會出現(xiàn)臟讀、不可重復(fù)讀。
- Read committed 讀提交 在該級別下,未提交的寫事務(wù)不允許其他事務(wù)訪問該行,因此不會出現(xiàn)臟讀;但是讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)的訪問該行數(shù)據(jù),因此會出現(xiàn)不可重復(fù)讀的情況。
- Repeatable read 重復(fù)讀 在該級別下,讀事務(wù)禁止寫事務(wù),但允許讀事務(wù),因此不會出現(xiàn)同一事務(wù)兩次讀到不同的數(shù)據(jù)的情況(不可重復(fù)讀),且寫事務(wù)禁止其他一切事務(wù)。
- Serializable 序列化 該級別要求所有事務(wù)都必須串行執(zhí)行,因此能避免一切因并發(fā)引起的問題,但效率很低。
隔離級別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對并發(fā)性能的影響也越大。對于多數(shù)應(yīng)用程序,可以優(yōu)先考慮把數(shù)據(jù)庫系統(tǒng)的隔離級別設(shè)為Read Committed。它能夠避免臟讀取,而且具有較好的并發(fā)性能。盡管它會導(dǎo)致不可重復(fù)讀、幻讀和第二類丟失更新這些并發(fā)問題,在可能出現(xiàn)這類問題的個別場合,可以由應(yīng)用程序采用悲觀鎖或樂觀鎖來控制。
什么是分布式事務(wù)?
到此為止,所介紹的事務(wù)都是基于單數(shù)據(jù)庫的本地事務(wù),目前的數(shù)據(jù)庫僅支持單庫事務(wù),并不支持跨庫事務(wù)。而隨著微服務(wù)架構(gòu)的普及,一個大型業(yè)務(wù)系統(tǒng)往往由若干個子系統(tǒng)構(gòu)成,這些子系統(tǒng)又擁有各自獨(dú)立的數(shù)據(jù)庫。往往一個業(yè)務(wù)流程需要由多個子系統(tǒng)共同完成,而且這些操作可能需要在一個事務(wù)中完成。在微服務(wù)系統(tǒng)中,這些業(yè)務(wù)場景是普遍存在的。此時,我們就需要在數(shù)據(jù)庫之上通過某種手段,實(shí)現(xiàn)支持跨數(shù)據(jù)庫的事務(wù)支持,這也就是大家常說的“分布式事務(wù)”。
這里舉一個分布式事務(wù)的典型例子——用戶下單過程。 當(dāng)我們的系統(tǒng)采用了微服務(wù)架構(gòu)后,一個電商系統(tǒng)往往被拆分成如下幾個子系統(tǒng):商品系統(tǒng)、訂單系統(tǒng)、支付系統(tǒng)、積分系統(tǒng)等。整個下單的過程如下:
- 用戶通過商品系統(tǒng)瀏覽商品,他看中了某一項(xiàng)商品,便點(diǎn)擊下單
- 此時訂單系統(tǒng)會生成一條訂單
- 訂單創(chuàng)建成功后,支付系統(tǒng)提供支付功能
- 當(dāng)支付完成后,由積分系統(tǒng)為該用戶增加積分
上述步驟2、3、4需要在一個事務(wù)中完成。對于傳統(tǒng)單體應(yīng)用而言,實(shí)現(xiàn)事務(wù)非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional注解標(biāo)識該方法即可。Spring通過數(shù)據(jù)庫的事務(wù)支持,保證這些步驟要么全都執(zhí)行完成,要么全都不執(zhí)行。但在這個微服務(wù)架構(gòu)中,這三個步驟涉及三個系統(tǒng),涉及三個數(shù)據(jù)庫,此時我們必須在數(shù)據(jù)庫和應(yīng)用系統(tǒng)之間,通過某項(xiàng)黑科技,實(shí)現(xiàn)分布式事務(wù)的支持。
CAP理論
CAP理論說的是:在一個分布式系統(tǒng)中,最多只能滿足C、A、P中的兩個需求。
CAP的含義:
- C:Consistency 一致性 同一數(shù)據(jù)的多個副本是否實(shí)時相同。
- A:Availability 可用性 可用性:一定時間內(nèi) & 系統(tǒng)返回一個明確的結(jié)果 則稱為該系統(tǒng)可用。
- P:Partition tolerance 分區(qū)容錯性 將同一服務(wù)分布在多個系統(tǒng)中,從而保證某一個系統(tǒng)宕機(jī),仍然有其他系統(tǒng)提供相同的服務(wù)。
CAP理論告訴我們,在分布式系統(tǒng)中,C、A、P三個條件中我們最多只能選擇兩個。那么問題來了,究竟選擇哪兩個條件較為合適呢?
對于一個業(yè)務(wù)系統(tǒng)來說,可用性和分區(qū)容錯性是必須要滿足的兩個條件,并且這兩者是相輔相成的。業(yè)務(wù)系統(tǒng)之所以使用分布式系統(tǒng),主要原因有兩個:
- 提升整體性能 當(dāng)業(yè)務(wù)量猛增,單個服務(wù)器已經(jīng)無法滿足我們的業(yè)務(wù)需求的時候,就需要使用分布式系統(tǒng),使用多個節(jié)點(diǎn)提供相同的功能,從而整體上提升系統(tǒng)的性能,這就是使用分布式系統(tǒng)的第一個原因。
- 實(shí)現(xiàn)分區(qū)容錯性 單一節(jié)點(diǎn) 或 多個節(jié)點(diǎn)處于相同的網(wǎng)絡(luò)環(huán)境下,那么會存在一定的風(fēng)險,萬一該機(jī)房斷電、該地區(qū)發(fā)生自然災(zāi)害,那么業(yè)務(wù)系統(tǒng)就全面癱瘓了。為了防止這一問題,采用分布式系統(tǒng),將多個子系統(tǒng)分布在不同的地域、不同的機(jī)房中,從而保證系統(tǒng)高可用性。
這說明分區(qū)容錯性是分布式系統(tǒng)的根本,如果分區(qū)容錯性不能滿足,那使用分布式系統(tǒng)將失去意義。
此外,可用性對業(yè)務(wù)系統(tǒng)也尤為重要。在大談用戶體驗(yàn)的今天,如果業(yè)務(wù)系統(tǒng)時常出現(xiàn)“系統(tǒng)異?!薄㈨憫?yīng)時間過長等情況,這使得用戶對系統(tǒng)的好感度大打折扣,在互聯(lián)網(wǎng)行業(yè)競爭激烈的今天,相同領(lǐng)域的競爭者不甚枚舉,系統(tǒng)的間歇性不可用會立馬導(dǎo)致用戶流向競爭對手。因此,我們只能通過犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。這也就是下面要介紹的BASE理論。
BASE理論
CAP理論告訴我們一個悲慘但不得不接受的事實(shí)——我們只能在C、A、P中選擇兩個條件。而對于業(yè)務(wù)系統(tǒng)而言,我們往往選擇犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。不過這里要指出的是,所謂的“犧牲一致性”并不是完全放棄數(shù)據(jù)一致性,而是犧牲強(qiáng)一致性換取弱一致性。下面來介紹下BASE理論。
- BA:Basic Available 基本可用
- 整個系統(tǒng)在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內(nèi)仍然能夠返回一個明確的結(jié)果。只不過“基本可用”和“高可用”的區(qū)別是:
- “一定時間”可以適當(dāng)延長 當(dāng)舉行大促時,響應(yīng)時間可以適當(dāng)延長
- 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務(wù)器壓力。但要注意,返回降級頁面仍然是返回明確結(jié)果。
- 整個系統(tǒng)在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內(nèi)仍然能夠返回一個明確的結(jié)果。只不過“基本可用”和“高可用”的區(qū)別是:
- S:Soft State:柔性狀態(tài) 同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實(shí)時一致。
- E:Eventual Consisstency:最終一致性 同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實(shí)時一致,但一定要保證經(jīng)過一定時間后仍然是一致的。
酸堿平衡
ACID能夠保證事務(wù)的強(qiáng)一致性,即數(shù)據(jù)是實(shí)時一致的。這在本地事務(wù)中是沒有問題的,在分布式事務(wù)中,強(qiáng)一致性會極大影響分布式系統(tǒng)的性能,因此分布式系統(tǒng)中遵循BASE理論即可。但分布式系統(tǒng)的不同業(yè)務(wù)場景對一致性的要求也不同。如交易場景下,就要求強(qiáng)一致性,此時就需要遵循ACID理論,而在注冊成功后發(fā)送短信驗(yàn)證碼等場景下,并不需要實(shí)時一致,因此遵循BASE理論即可。因此要根據(jù)具體業(yè)務(wù)場景,在ACID和BASE之間尋求平衡。
分布式事務(wù)協(xié)議
下面介紹幾種實(shí)現(xiàn)分布式事務(wù)的協(xié)議。
兩階段提交協(xié)議 2PC
分布式系統(tǒng)的一個難點(diǎn)是如何保證架構(gòu)下多個節(jié)點(diǎn)在進(jìn)行事務(wù)性操作的時候保持一致性。為實(shí)現(xiàn)這個目的,二階段提交算法的成立基于以下假設(shè):
- 該分布式系統(tǒng)中,存在一個節(jié)點(diǎn)作為協(xié)調(diào)者(Coordinator),其他節(jié)點(diǎn)作為參與者(Cohorts)。且節(jié)點(diǎn)之間可以進(jìn)行網(wǎng)絡(luò)通信。
- 所有節(jié)點(diǎn)都采用預(yù)寫式日志,且日志被寫入后即被保持在可靠的存儲設(shè)備上,即使節(jié)點(diǎn)損壞不會導(dǎo)致日志數(shù)據(jù)的消失。
- 所有節(jié)點(diǎn)不會永久性損壞,即使損壞后仍然可以恢復(fù)。
1. 第一階段(投票階段):
- 協(xié)調(diào)者節(jié)點(diǎn)向所有參與者節(jié)點(diǎn)詢問是否可以執(zhí)行提交操作(vote),并開始等待各參與者節(jié)點(diǎn)的響應(yīng)。
- 參與者節(jié)點(diǎn)執(zhí)行詢問發(fā)起為止的所有事務(wù)操作,并將Undo信息和Redo信息寫入日志。(注意:若成功這里其實(shí)每個參與者已經(jīng)執(zhí)行了事務(wù)操作)
- 各參與者節(jié)點(diǎn)響應(yīng)協(xié)調(diào)者節(jié)點(diǎn)發(fā)起的詢問。如果參與者節(jié)點(diǎn)的事務(wù)操作實(shí)際執(zhí)行成功,則它返回一個"同意"消息;如果參與者節(jié)點(diǎn)的事務(wù)操作實(shí)際執(zhí)行失敗,則它返回一個"中止"消息。
2. 第二階段(提交執(zhí)行階段):
當(dāng)協(xié)調(diào)者節(jié)點(diǎn)從所有參與者節(jié)點(diǎn)獲得的相應(yīng)消息都為"同意"時:
- 協(xié)調(diào)者節(jié)點(diǎn)向所有參與者節(jié)點(diǎn)發(fā)出"正式提交(commit)"的請求。
- 參與者節(jié)點(diǎn)正式完成操作,并釋放在整個事務(wù)期間內(nèi)占用的資源。
- 參與者節(jié)點(diǎn)向協(xié)調(diào)者節(jié)點(diǎn)發(fā)送"完成"消息。
- 協(xié)調(diào)者節(jié)點(diǎn)受到所有參與者節(jié)點(diǎn)反饋的"完成"消息后,完成事務(wù)。
如果任一參與者節(jié)點(diǎn)在第一階段返回的響應(yīng)消息為"中止",或者 協(xié)調(diào)者節(jié)點(diǎn)在第一階段的詢問超時之前無法獲取所有參與者節(jié)點(diǎn)的響應(yīng)消息時:
- 協(xié)調(diào)者節(jié)點(diǎn)向所有參與者節(jié)點(diǎn)發(fā)出"回滾操作(rollback)"的請求。
- 參與者節(jié)點(diǎn)利用之前寫入的Undo信息執(zhí)行回滾,并釋放在整個事務(wù)期間內(nèi)占用的資源。
- 參與者節(jié)點(diǎn)向協(xié)調(diào)者節(jié)點(diǎn)發(fā)送"回滾完成"消息。
- 協(xié)調(diào)者節(jié)點(diǎn)受到所有參與者節(jié)點(diǎn)反饋的"回滾完成"消息后,取消事務(wù)。
不管最后結(jié)果如何,第二階段都會結(jié)束當(dāng)前事務(wù)。
二階段提交看起來確實(shí)能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點(diǎn)的:
- 執(zhí)行過程中,所有參與節(jié)點(diǎn)都是事務(wù)阻塞型的。當(dāng)參與者占有公共資源時,其他第三方節(jié)點(diǎn)訪問公共資源不得不處于阻塞狀態(tài)。
- 參與者發(fā)生故障。協(xié)調(diào)者需要給每個參與者額外指定超時機(jī)制,超時后整個事務(wù)失敗。(沒有多少容錯機(jī)制)
- 協(xié)調(diào)者發(fā)生故障。參與者會一直阻塞下去。需要額外的備機(jī)進(jìn)行容錯。(這個可以依賴后面要講的Paxos協(xié)議實(shí)現(xiàn)HA)
- 二階段無法解決的問題:協(xié)調(diào)者再發(fā)出commit消息之后宕機(jī),而唯一接收到這條消息的參與者同時也宕機(jī)了。那么即使協(xié)調(diào)者通過選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者,這條事務(wù)的狀態(tài)也是不確定的,沒人知道事務(wù)是否被已經(jīng)提交。
為此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三階段提交協(xié)議(3PC)。
三階段提交協(xié)議 3PC
與兩階段提交不同的是,三階段提交有兩個改動點(diǎn)。
- 引入超時機(jī)制。同時在協(xié)調(diào)者和參與者中都引入超時機(jī)制。
- 在第一階段和第二階段中插入一個準(zhǔn)備階段。保證了在最后提交階段之前各參與節(jié)點(diǎn)的狀態(tài)是一致的。
也就是說,除了引入超時機(jī)制之外,3PC把2PC的準(zhǔn)備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。
1. CanCommit階段
3PC的CanCommit階段其實(shí)和2PC的準(zhǔn)備階段很像。協(xié)調(diào)者向參與者發(fā)送commit請求,參與者如果可以提交就返回Yes響應(yīng),否則返回No響應(yīng)。
- 事務(wù)詢問 協(xié)調(diào)者向參與者發(fā)送CanCommit請求。詢問是否可以執(zhí)行事務(wù)提交操作。然后開始等待參與者的響應(yīng)。
- 響應(yīng)反饋 參與者接到CanCommit請求之后,正常情況下,如果其自身認(rèn)為可以順利執(zhí)行事務(wù),則返回Yes響應(yīng),并進(jìn)入預(yù)備狀態(tài)。否則反饋No
2. PreCommit階段
協(xié)調(diào)者根據(jù)參與者的反應(yīng)情況來決定是否可以記性事務(wù)的PreCommit操作。根據(jù)響應(yīng)情況,有以下兩種可能。 假如協(xié)調(diào)者從所有的參與者獲得的反饋都是Yes響應(yīng),那么就會執(zhí)行事務(wù)的預(yù)執(zhí)行。
- 發(fā)送預(yù)提交請求 協(xié)調(diào)者向參與者發(fā)送PreCommit請求,并進(jìn)入Prepared階段。
- 事務(wù)預(yù)提交 參與者接收到PreCommit請求后,會執(zhí)行事務(wù)操作,并將undo和redo信息記錄到事務(wù)日志中。
- 響應(yīng)反饋 如果參與者成功的執(zhí)行了事務(wù)操作,則返回ACK響應(yīng),同時開始等待最終指令。
假如有任何一個參與者向協(xié)調(diào)者發(fā)送了No響應(yīng),或者等待超時之后,協(xié)調(diào)者都沒有接到參與者的響應(yīng),那么就執(zhí)行事務(wù)的中斷。
- 發(fā)送中斷請求 協(xié)調(diào)者向所有參與者發(fā)送abort請求。
- 中斷事務(wù) 參與者收到來自協(xié)調(diào)者的abort請求之后(或超時之后,仍未收到協(xié)調(diào)者的請求),執(zhí)行事務(wù)的中斷。
3. doCommit階段 該階段進(jìn)行真正的事務(wù)提交,也可以分為以下兩種情況。
該階段進(jìn)行真正的事務(wù)提交,也可以分為以下兩種情況。
3.1 執(zhí)行提交
- 發(fā)送提交請求 協(xié)調(diào)接收到參與者發(fā)送的ACK響應(yīng),那么他將從預(yù)提交狀態(tài)進(jìn)入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請求。
- 事務(wù)提交 參與者接收到doCommit請求之后,執(zhí)行正式的事務(wù)提交。并在完成事務(wù)提交之后釋放所有事務(wù)資源。
- 響應(yīng)反饋 事務(wù)提交完之后,向協(xié)調(diào)者發(fā)送Ack響應(yīng)。
- 完成事務(wù) 協(xié)調(diào)者接收到所有參與者的ack響應(yīng)之后,完成事務(wù)。
3.2 中斷事務(wù) 協(xié)調(diào)者沒有接收到參與者發(fā)送的ACK響應(yīng)(可能是接受者發(fā)送的不是ACK響應(yīng),也可能響應(yīng)超時),那么就會執(zhí)行中斷事務(wù)。
- 發(fā)送中斷請求 協(xié)調(diào)者向所有參與者發(fā)送abort請求
- 事務(wù)回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執(zhí)行事務(wù)的回滾操作,并在完成回滾之后釋放所有的事務(wù)資源。
- 反饋結(jié)果 參與者完成事務(wù)回滾之后,向協(xié)調(diào)者發(fā)送ACK消息
- 中斷事務(wù) 協(xié)調(diào)者接收到參與者反饋的ACK消息之后,執(zhí)行事務(wù)的中斷。
分布式事務(wù)的解決方案
分布式事務(wù)的解決方案有如下幾種:
- 全局消息
- 基于可靠消息服務(wù)的分布式事務(wù)
- TCC
- 最大努力通知
方案1:全局事務(wù)(DTP模型)
全局事務(wù)基于DTP模型實(shí)現(xiàn)。DTP是由X/Open組織提出的一種分布式事務(wù)模型——X/Open Distributed Transaction Processing Reference Model。它規(guī)定了要實(shí)現(xiàn)分布式事務(wù),需要三種角色:
- AP:Application 應(yīng)用系統(tǒng) 它就是我們開發(fā)的業(yè)務(wù)系統(tǒng),在我們開發(fā)的過程中,可以使用資源管理器提供的事務(wù)接口來實(shí)現(xiàn)分布式事務(wù)。
- TM:Transaction Manager 事務(wù)管理器
- 分布式事務(wù)的實(shí)現(xiàn)由事務(wù)管理器來完成,它會提供分布式事務(wù)的操作接口供我們的業(yè)務(wù)系統(tǒng)調(diào)用。這些接口稱為TX接口。
- 事務(wù)管理器還管理著所有的資源管理器,通過它們提供的XA接口來同一調(diào)度這些資源管理器,以實(shí)現(xiàn)分布式事務(wù)。
- DTP只是一套實(shí)現(xiàn)分布式事務(wù)的規(guī)范,并沒有定義具體如何實(shí)現(xiàn)分布式事務(wù),TM可以采用2PC、3PC、Paxos等協(xié)議實(shí)現(xiàn)分布式事務(wù)。
- RM:Resource Manager 資源管理器
- 能夠提供數(shù)據(jù)服務(wù)的對象都可以是資源管理器,比如:數(shù)據(jù)庫、消息中間件、緩存等。大部分場景下,數(shù)據(jù)庫即為分布式事務(wù)中的資源管理器。
- 資源管理器能夠提供單數(shù)據(jù)庫的事務(wù)能力,它們通過XA接口,將本數(shù)據(jù)庫的提交、回滾等能力提供給事務(wù)管理器調(diào)用,以幫助事務(wù)管理器實(shí)現(xiàn)分布式的事務(wù)管理。
- XA是DTP模型定義的接口,用于向事務(wù)管理器提供該資源管理器(該數(shù)據(jù)庫)的提交、回滾等能力。
- DTP只是一套實(shí)現(xiàn)分布式事務(wù)的規(guī)范,RM具體的實(shí)現(xiàn)是由數(shù)據(jù)庫廠商來完成的。
- 有沒有基于DTP模型的分布式事務(wù)中間件?
- DTP模型有啥優(yōu)缺點(diǎn)?
方案2:基于可靠消息服務(wù)的分布式事務(wù)
這種實(shí)現(xiàn)分布式事務(wù)的方式需要通過消息中間件來實(shí)現(xiàn)。假設(shè)有A和B兩個系統(tǒng),分別可以處理任務(wù)A和任務(wù)B。此時系統(tǒng)A中存在一個業(yè)務(wù)流程,需要將任務(wù)A和任務(wù)B在同一個事務(wù)中處理。下面來介紹基于消息中間件來實(shí)現(xiàn)這種分布式事務(wù)。

- 在系統(tǒng)A處理任務(wù)A前,首先向消息中間件發(fā)送一條消息
- 消息中間件收到后將該條消息持久化,但并不投遞。此時下游系統(tǒng)B仍然不知道該條消息的存在。
- 消息中間件持久化成功后,便向系統(tǒng)A返回一個確認(rèn)應(yīng)答;
- 系統(tǒng)A收到確認(rèn)應(yīng)答后,則可以開始處理任務(wù)A;
- 任務(wù)A處理完成后,向消息中間件發(fā)送Commit請求。該請求發(fā)送完成后,對系統(tǒng)A而言,該事務(wù)的處理過程就結(jié)束了,此時它可以處理別的任務(wù)了。 但commit消息可能會在傳輸途中丟失,從而消息中間件并不會向系統(tǒng)B投遞這條消息,從而系統(tǒng)就會出現(xiàn)不一致性。這個問題由消息中間件的事務(wù)回查機(jī)制完成,下文會介紹。
- 消息中間件收到Commit指令后,便向系統(tǒng)B投遞該消息,從而觸發(fā)任務(wù)B的執(zhí)行;
- 當(dāng)任務(wù)B執(zhí)行完成后,系統(tǒng)B向消息中間件返回一個確認(rèn)應(yīng)答,告訴消息中間件該消息已經(jīng)成功消費(fèi),此時,這個分布式事務(wù)完成。
上述過程可以得出如下幾個結(jié)論:
- 消息中間件扮演者分布式事務(wù)協(xié)調(diào)者的角色。
- 系統(tǒng)A完成任務(wù)A后,到任務(wù)B執(zhí)行完成之間,會存在一定的時間差。在這個時間差內(nèi),整個系統(tǒng)處于數(shù)據(jù)不一致的狀態(tài),但這短暫的不一致性是可以接受的,因?yàn)榻?jīng)過短暫的時間后,系統(tǒng)又可以保持?jǐn)?shù)據(jù)一致性,滿足BASE理論。
上述過程中,如果任務(wù)A處理失敗,那么需要進(jìn)入回滾流程,如下圖所示:

- 若系統(tǒng)A在處理任務(wù)A時失敗,那么就會向消息中間件發(fā)送Rollback請求。和發(fā)送Commit請求一樣,系統(tǒng)A發(fā)完之后便可以認(rèn)為回滾已經(jīng)完成,它便可以去做其他的事情。
- 消息中間件收到回滾請求后,直接將該消息丟棄,而不投遞給系統(tǒng)B,從而不會觸發(fā)系統(tǒng)B的任務(wù)B。
此時系統(tǒng)又處于一致性狀態(tài),因?yàn)槿蝿?wù)A和任務(wù)B都沒有執(zhí)行。
上面所介紹的Commit和Rollback都屬于理想情況,但在實(shí)際系統(tǒng)中,Commit和Rollback指令都有可能在傳輸途中丟失。那么當(dāng)出現(xiàn)這種情況的時候,消息中間件是如何保證數(shù)據(jù)一致性呢?——答案就是超時詢問機(jī)制。

系統(tǒng)A除了實(shí)現(xiàn)正常的業(yè)務(wù)流程外,還需提供一個事務(wù)詢問的接口,供消息中間件調(diào)用。當(dāng)消息中間件收到一條事務(wù)型消息后便開始計(jì)時,如果到了超時時間也沒收到系統(tǒng)A發(fā)來的Commit或Rollback指令的話,就會主動調(diào)用系統(tǒng)A提供的事務(wù)詢問接口詢問該系統(tǒng)目前的狀態(tài)。該接口會返回三種結(jié)果:
- 提交 若獲得的狀態(tài)是“提交”,則將該消息投遞給系統(tǒng)B。
- 回滾 若獲得的狀態(tài)是“回滾”,則直接將條消息丟棄。
- 處理中 若獲得的狀態(tài)是“處理中”,則繼續(xù)等待。
消息中間件的超時詢問機(jī)制能夠防止上游系統(tǒng)因在傳輸過程中丟失Commit/Rollback指令而導(dǎo)致的系統(tǒng)不一致情況,而且能降低上游系統(tǒng)的阻塞時間,上游系統(tǒng)只要發(fā)出Commit/Rollback指令后便可以處理其他任務(wù),無需等待確認(rèn)應(yīng)答。而Commit/Rollback指令丟失的情況通過超時詢問機(jī)制來彌補(bǔ),這樣大大降低上游系統(tǒng)的阻塞時間,提升系統(tǒng)的并發(fā)度。
下面來說一說消息投遞過程的可靠性保證。 當(dāng)上游系統(tǒng)執(zhí)行完任務(wù)并向消息中間件提交了Commit指令后,便可以處理其他任務(wù)了,此時它可以認(rèn)為事務(wù)已經(jīng)完成,接下來消息中間件一定會保證消息被下游系統(tǒng)成功消費(fèi)掉!那么這是怎么做到的呢?這由消息中間件的投遞流程來保證。
消息中間件向下游系統(tǒng)投遞完消息后便進(jìn)入阻塞等待狀態(tài),下游系統(tǒng)便立即進(jìn)行任務(wù)的處理,任務(wù)處理完成后便向消息中間件返回應(yīng)答。消息中間件收到確認(rèn)應(yīng)答后便認(rèn)為該事務(wù)處理完畢!
如果消息在投遞過程中丟失,或消息的確認(rèn)應(yīng)答在返回途中丟失,那么消息中間件在等待確認(rèn)應(yīng)答超時之后就會重新投遞,直到下游消費(fèi)者返回消費(fèi)成功響應(yīng)為止。當(dāng)然,一般消息中間件可以設(shè)置消息重試的次數(shù)和時間間隔,比如:當(dāng)?shù)谝淮瓮哆f失敗后,每隔五分鐘重試一次,一共重試3次。如果重試3次之后仍然投遞失敗,那么這條消息就需要人工干預(yù)。


有的同學(xué)可能要問:消息投遞失敗后為什么不回滾消息,而是不斷嘗試重新投遞?
這就涉及到整套分布式事務(wù)系統(tǒng)的實(shí)現(xiàn)成本問題。 我們知道,當(dāng)系統(tǒng)A將向消息中間件發(fā)送Commit指令后,它便去做別的事情了。如果此時消息投遞失敗,需要回滾的話,就需要讓系統(tǒng)A事先提供回滾接口,這無疑增加了額外的開發(fā)成本,業(yè)務(wù)系統(tǒng)的復(fù)雜度也將提高。對于一個業(yè)務(wù)系統(tǒng)的設(shè)計(jì)目標(biāo)是,在保證性能的前提下,最大限度地降低系統(tǒng)復(fù)雜度,從而能夠降低系統(tǒng)的運(yùn)維成本。
不知大家是否發(fā)現(xiàn),上游系統(tǒng)A向消息中間件提交Commit/Rollback消息采用的是異步方式,也就是當(dāng)上游系統(tǒng)提交完消息后便可以去做別的事情,接下來提交、回滾就完全交給消息中間件來完成,并且完全信任消息中間件,認(rèn)為它一定能正確地完成事務(wù)的提交或回滾。然而,消息中間件向下游系統(tǒng)投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統(tǒng)后,它會阻塞等待,等下游系統(tǒng)成功處理完任務(wù)返回確認(rèn)應(yīng)答后才取消阻塞等待。為什么這兩者在設(shè)計(jì)上是不一致的呢?
首先,上游系統(tǒng)和消息中間件之間采用異步通信是為了提高系統(tǒng)并發(fā)度。業(yè)務(wù)系統(tǒng)直接和用戶打交道,用戶體驗(yàn)尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間。此外,異步通信相對于同步通信而言,沒有了長時間的阻塞等待,因此系統(tǒng)的并發(fā)性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機(jī)制來彌補(bǔ)。
那么,消息中間件和下游系統(tǒng)之間為什么要采用同步通信呢?
異步能提升系統(tǒng)性能,但隨之會增加系統(tǒng)復(fù)雜度;而同步雖然降低系統(tǒng)并發(fā)度,但實(shí)現(xiàn)成本較低。因此,在對并發(fā)度要求不是很高的情況下,或者服務(wù)器資源較為充裕的情況下,我們可以選擇同步來降低系統(tǒng)的復(fù)雜度。 我們知道,消息中間件是一個獨(dú)立于業(yè)務(wù)系統(tǒng)的第三方中間件,它不和任何業(yè)務(wù)系統(tǒng)產(chǎn)生直接的耦合,它也不和用戶產(chǎn)生直接的關(guān)聯(lián),它一般部署在獨(dú)立的服務(wù)器集群上,具有良好的可擴(kuò)展性,所以不必太過于擔(dān)心它的性能,如果處理速度無法滿足我們的要求,可以增加機(jī)器來解決。而且,即使消息中間件處理速度有一定的延遲那也是可以接受的,因?yàn)榍懊嫠榻B的BASE理論就告訴我們了,我們追求的是最終一致性,而非實(shí)時一致性,因此消息中間件產(chǎn)生的時延導(dǎo)致事務(wù)短暫的不一致是可以接受的。
方案3:最大努力通知(定期校對)
最大努力通知也被稱為定期校對,其實(shí)在方案二中已經(jīng)包含,這里再單獨(dú)介紹,主要是為了知識體系的完整性。這種方案也需要消息中間件的參與,其過程如下:

- 上游系統(tǒng)在完成任務(wù)后,向消息中間件同步地發(fā)送一條消息,確保消息中間件成功持久化這條消息,然后上游系統(tǒng)可以去做別的事情了;
- 消息中間件收到消息后負(fù)責(zé)將該消息同步投遞給相應(yīng)的下游系統(tǒng),并觸發(fā)下游系統(tǒng)的任務(wù)執(zhí)行;
- 當(dāng)下游系統(tǒng)處理成功后,向消息中間件反饋確認(rèn)應(yīng)答,消息中間件便可以將該條消息刪除,從而該事務(wù)完成。
上面是一個理想化的過程,但在實(shí)際場景中,往往會出現(xiàn)如下幾種意外情況:
- 消息中間件向下游系統(tǒng)投遞消息失敗
- 上游系統(tǒng)向消息中間件發(fā)送消息失敗
對于第一種情況,消息中間件具有重試機(jī)制,我們可以在消息中間件中設(shè)置消息的重試次數(shù)和重試時間間隔,對于網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致的消息投遞失敗的情況,往往重試幾次后消息便可以成功投遞,如果超過了重試的上限仍然投遞失敗,那么消息中間件不再投遞該消息,而是記錄在失敗消息表中,消息中間件需要提供失敗消息的查詢接口,下游系統(tǒng)會定期查詢失敗消息,并將其消費(fèi),這就是所謂的“定期校對”。
如果重復(fù)投遞和定期校對都不能解決問題,往往是因?yàn)橄掠蜗到y(tǒng)出現(xiàn)了嚴(yán)重的錯誤,此時就需要人工干預(yù)。
對于第二種情況,需要在上游系統(tǒng)中建立消息重發(fā)機(jī)制??梢栽谏嫌蜗到y(tǒng)建立一張本地消息表,并將 任務(wù)處理過程 和 向本地消息表中插入消息 這兩個步驟放在一個本地事務(wù)中完成。如果向本地消息表插入消息失敗,那么就會觸發(fā)回滾,之前的任務(wù)處理結(jié)果就會被取消。如果這量步都執(zhí)行成功,那么該本地事務(wù)就完成了。接下來會有一個專門的消息發(fā)送者不斷地發(fā)送本地消息表中的消息,如果發(fā)送失敗它會返回重試。當(dāng)然,也要給消息發(fā)送者設(shè)置重試的上限,一般而言,達(dá)到重試上限仍然發(fā)送失敗,那就意味著消息中間件出現(xiàn)嚴(yán)重的問題,此時也只有人工干預(yù)才能解決問題。
對于不支持事務(wù)型消息的消息中間件,如果要實(shí)現(xiàn)分布式事務(wù)的話,就可以采用這種方式。它能夠通過重試機(jī)制+定期校對實(shí)現(xiàn)分布式事務(wù),但相比于第二種方案,它達(dá)到數(shù)據(jù)一致性的周期較長,而且還需要在上游系統(tǒng)中實(shí)現(xiàn)消息重試發(fā)布機(jī)制,以確保消息成功發(fā)布給消息中間件,這無疑增加了業(yè)務(wù)系統(tǒng)的開發(fā)成本,使得業(yè)務(wù)系統(tǒng)不夠純粹,并且這些額外的業(yè)務(wù)邏輯無疑會占用業(yè)務(wù)系統(tǒng)的硬件資源,從而影響性能。
因此,盡量選擇支持事務(wù)型消息的消息中間件來實(shí)現(xiàn)分布式事務(wù),如RocketMQ。
方案4:TCC(兩階段型、補(bǔ)償型)
TCC即為Try Confirm Cancel,它屬于補(bǔ)償型分布式事務(wù)。顧名思義,TCC實(shí)現(xiàn)分布式事務(wù)一共有三個步驟:
- Try:嘗試待執(zhí)行的業(yè)務(wù)
- 這個過程并未執(zhí)行業(yè)務(wù),只是完成所有業(yè)務(wù)的一致性檢查,并預(yù)留好執(zhí)行所需的全部資源
- Confirm:執(zhí)行業(yè)務(wù)
- 這個過程真正開始執(zhí)行業(yè)務(wù),由于Try階段已經(jīng)完成了一致性檢查,因此本過程直接執(zhí)行,而不做任何檢查。并且在執(zhí)行的過程中,會使用到Try階段預(yù)留的業(yè)務(wù)資源。
- Cancel:取消執(zhí)行的業(yè)務(wù)
- 若業(yè)務(wù)執(zhí)行失敗,則進(jìn)入Cancel階段,它會釋放所有占用的業(yè)務(wù)資源,并回滾Confirm階段執(zhí)行的操作。
下面以一個轉(zhuǎn)賬的例子來解釋下TCC實(shí)現(xiàn)分布式事務(wù)的過程。
假設(shè)用戶A用他的賬戶余額給用戶B發(fā)一個100元的紅包,并且余額系統(tǒng)和紅包系統(tǒng)是兩個獨(dú)立的系統(tǒng)。
- Try
- 創(chuàng)建一條轉(zhuǎn)賬流水,并將流水的狀態(tài)設(shè)為交易中
- 將用戶A的賬戶中扣除100元(預(yù)留業(yè)務(wù)資源)
- Try成功之后,便進(jìn)入Confirm階段
- Try過程發(fā)生任何異常,均進(jìn)入Cancel階段
- Confirm
- 向B用戶的紅包賬戶中增加100元
- 將流水的狀態(tài)設(shè)為交易已完成
- Confirm過程發(fā)生任何異常,均進(jìn)入Cancel階段
- Confirm過程執(zhí)行成功,則該事務(wù)結(jié)束
- Cancel
- 將用戶A的賬戶增加100元
- 將流水的狀態(tài)設(shè)為交易失敗
在傳統(tǒng)事務(wù)機(jī)制中,業(yè)務(wù)邏輯的執(zhí)行和事務(wù)的處理,是在不同的階段由不同的部件來完成的:業(yè)務(wù)邏輯部分訪問資源實(shí)現(xiàn)數(shù)據(jù)存儲,其處理是由業(yè)務(wù)系統(tǒng)負(fù)責(zé);事務(wù)處理部分通過協(xié)調(diào)資源管理器以實(shí)現(xiàn)事務(wù)管理,其處理由事務(wù)管理器來負(fù)責(zé)。二者沒有太多交互的地方,所以,傳統(tǒng)事務(wù)管理器的事務(wù)處理邏輯,僅需要著眼于事務(wù)完成(commit/rollback)階段,而不必關(guān)注業(yè)務(wù)執(zhí)行階段。
TCC全局事務(wù)必須基于RM本地事務(wù)來實(shí)現(xiàn)全局事務(wù)
TCC服務(wù)是由Try/Confirm/Cancel業(yè)務(wù)構(gòu)成的, 其Try/Confirm/Cancel業(yè)務(wù)在執(zhí)行時,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數(shù)據(jù)。這些存取操作,必須要參與RM本地事務(wù),以使其更改的數(shù)據(jù)要么都commit,要么都rollback。
這一點(diǎn)不難理解,考慮一下如下場景:

假設(shè)圖中的服務(wù)B沒有基于RM本地事務(wù)(以RDBS為例,可通過設(shè)置auto-commit為true來模擬),那么一旦[B:Try]操作中途執(zhí)行失敗,TCC事務(wù)框架后續(xù)決定回滾全局事務(wù)時,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經(jīng)寫到DB、哪些操作還沒有寫到DB:假設(shè)[B:Try]業(yè)務(wù)有5個寫庫操作,[B:Cancel]業(yè)務(wù)則需要逐個判斷這5個操作是否生效,并將生效的操作執(zhí)行反向操作。
不幸的是,由于[B:Cancel]業(yè)務(wù)也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯,則后續(xù)的[B:Cancel]執(zhí)行任務(wù)更加繁重。因?yàn)椋啾鹊谝淮蝃B:Cancel]操作,后續(xù)的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經(jīng)執(zhí)行、哪幾個還沒有執(zhí)行,這就涉及到了冪等性問題。而對冪等性的保障,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因?yàn)闆]有RM本地事務(wù)的支持而存在類似問題。。??上攵?,如果不基于RM本地事務(wù),TCC事務(wù)框架是無法有效的管理TCC全局事務(wù)的。
反之,基于RM本地事務(wù)的TCC事務(wù),這種情況則會很容易處理:[B:Try]操作中途執(zhí)行失敗,TCC事務(wù)框架將其參與RM本地事務(wù)直接rollback即可。后續(xù)TCC事務(wù)框架決定回滾全局事務(wù)時,在知道“[B:Try]操作涉及的RM本地事務(wù)已經(jīng)rollback”的情況下,根本無需執(zhí)行[B:Cancel]操作。
換句話說,基于RM本地事務(wù)實(shí)現(xiàn)TCC事務(wù)框架時,一個TCC型服務(wù)的cancel業(yè)務(wù)要么執(zhí)行,要么不執(zhí)行,不需要考慮部分執(zhí)行的情況。
TCC事務(wù)框架應(yīng)該提供Confirm/Cancel服務(wù)的冪等性保障
一般認(rèn)為,服務(wù)的冪等性,是指針對同一個服務(wù)的多次(n>1)請求和對它的單次(n=1)請求,二者具有相同的副作用。
在TCC事務(wù)模型中,Confirm/Cancel業(yè)務(wù)可能會被重復(fù)調(diào)用,其原因很多。比如,全局事務(wù)在提交/回滾時會調(diào)用各TCC服務(wù)的Confirm/Cancel業(yè)務(wù)邏輯。執(zhí)行這些Confirm/Cancel業(yè)務(wù)時,可能會出現(xiàn)如網(wǎng)絡(luò)中斷的故障而使得全局事務(wù)不能完成。因此,故障恢復(fù)機(jī)制后續(xù)仍然會重新提交/回滾這些未完成的全局事務(wù),這樣就會再次調(diào)用參與該全局事務(wù)的各TCC服務(wù)的Confirm/Cancel業(yè)務(wù)邏輯。
既然Confirm/Cancel業(yè)務(wù)可能會被多次調(diào)用,就需要保障其冪等性。 那么,應(yīng)該由TCC事務(wù)框架來提供冪等性保障?還是應(yīng)該由業(yè)務(wù)系統(tǒng)自行來保障冪等性呢? 個人認(rèn)為,應(yīng)該是由TCC事務(wù)框架來提供冪等性保障。如果僅僅只是極個別服務(wù)存在這個問題的話,那么由業(yè)務(wù)系統(tǒng)來負(fù)責(zé)也是可以的;然而,這是一類公共問題,毫無疑問,所有TCC服務(wù)的Confirm/Cancel業(yè)務(wù)存在冪等性問題。TCC服務(wù)的公共問題應(yīng)該由TCC事務(wù)框架來解決;而且,考慮一下由業(yè)務(wù)系統(tǒng)來負(fù)責(zé)冪等性需要考慮的問題,就會發(fā)現(xiàn),這無疑增大了業(yè)務(wù)系統(tǒng)的復(fù)雜度。