分布式技術(shù)原理與實(shí)戰(zhàn)45講--第06講:分布式事務(wù)有哪些解決方案?
本課時我們來討論下分布式事務(wù)的相關(guān)知識點(diǎn)。
分布式事務(wù)是分布式系統(tǒng)中非常重要的一部分,最典型的例子是銀行轉(zhuǎn)賬和扣款,A 和 B 的賬戶信息在不同的服務(wù)器上,A 給 B 轉(zhuǎn)賬 100 元,要完成這個操作,需要兩個步驟,從 A 的賬戶上扣款,以及在 B 的賬戶上增加金額,兩個步驟必須全部執(zhí)行成功;否則如果有一個失敗,那么另一個操作也不能執(zhí)行。
那么像這種轉(zhuǎn)賬扣款的例子,在業(yè)務(wù)中如何保證一致性,有哪些解決方案呢?
分布式事務(wù)是什么
顧名思義,分布式事務(wù)關(guān)注的是分布式場景下如何處理事務(wù),是指事務(wù)的參與者、支持事務(wù)操作的服務(wù)器、存儲等資源分別位于分布式系統(tǒng)的不同節(jié)點(diǎn)之上。
簡單來說,分布式事務(wù)就是一個業(yè)務(wù)操作,是由多個細(xì)分操作完成的,而這些細(xì)分操作又分布在不同的服務(wù)器上;事務(wù),就是這些操作要么全部成功執(zhí)行,要么全部不執(zhí)行。
數(shù)據(jù)庫事務(wù)
數(shù)據(jù)庫事務(wù)的特性包括原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durabilily),簡稱 ACID。
在數(shù)據(jù)庫執(zhí)行中,多個并發(fā)執(zhí)行的事務(wù)如果涉及到同一份數(shù)據(jù)的讀寫就容易出現(xiàn)數(shù)據(jù)不一致的情況,不一致的異?,F(xiàn)象有以下幾種。
臟讀,是指一個事務(wù)中訪問到了另外一個事務(wù)未提交的數(shù)據(jù)。例如事務(wù) T1 中修改的數(shù)據(jù)項(xiàng)在尚未提交的情況下被其他事務(wù)(T2)讀取到,如果 T1 進(jìn)行回滾操作,則 T2 剛剛讀取到的數(shù)據(jù)實(shí)際并不存在。
不可重復(fù)讀,是指一個事務(wù)讀取同一條記錄 2 次,得到的結(jié)果不一致。例如事務(wù) T1 第一次讀取數(shù)據(jù),接下來 T2 對其中的數(shù)據(jù)進(jìn)行了更新或者刪除,并且 Commit 成功。這時候 T1 再次讀取這些數(shù)據(jù),那么會得到 T2 修改后的數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)已經(jīng)變更,這樣 T1 在一個事務(wù)中的兩次讀取,返回的結(jié)果集會不一致。
幻讀,是指一個事務(wù)讀取 2 次,得到的記錄條數(shù)不一致。例如事務(wù) T1 查詢獲得一個結(jié)果集,T2 插入新的數(shù)據(jù),T2 Commit 成功后,T1 再次執(zhí)行同樣的查詢,此時得到的結(jié)果集記錄數(shù)不同。
臟讀、不可重復(fù)讀和幻讀有以下的包含關(guān)系,如果發(fā)生了臟讀,那么幻讀和不可重復(fù)讀都有可能出現(xiàn)。

不同隔離級別
SQL 標(biāo)準(zhǔn)根據(jù)三種不一致的異?,F(xiàn)象,將隔離性定義為四個 隔離級別(Isolation Level),隔離級別和數(shù)據(jù)庫的性能呈反比,隔離級別越低,數(shù)據(jù)庫性能越高;而隔離級別越高,數(shù)據(jù)庫性能越差,具體如下:

(1)Read uncommitted 讀未提交
在該級別下,一個事務(wù)對數(shù)據(jù)修改的過程中,不允許另一個事務(wù)對該行數(shù)據(jù)進(jìn)行修改,但允許另一個事務(wù)對該行數(shù)據(jù)進(jìn)行讀,不會出現(xiàn)更新丟失,但會出現(xiàn)臟讀、不可重復(fù)讀的情況。
(2)Read committed 讀已提交
在該級別下,未提交的寫事務(wù)不允許其他事務(wù)訪問該行,不會出現(xiàn)臟讀,但是讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)訪問該行數(shù)據(jù),因此會出現(xiàn)不可重復(fù)讀的情況。
(3)Repeatable read 可重復(fù)讀
在該級別下,在同一個事務(wù)內(nèi)的查詢都是和事務(wù)開始時刻一致的,保證對同一字段的多次讀取結(jié)果都相同,除非數(shù)據(jù)是被本身事務(wù)自己所修改,不會出現(xiàn)同一事務(wù)讀到兩次不同數(shù)據(jù)的情況。因?yàn)闆]有約束其他事務(wù)的新增Insert操作,所以 SQL 標(biāo)準(zhǔn)中可重復(fù)讀級別會出現(xiàn)幻讀。
值得一提的是,可重復(fù)讀是 MySQL InnoDB 引擎的默認(rèn)隔離級別,但是在 MySQL 額外添加了間隙鎖(Gap Lock),可以防止幻讀。
(4)Serializable 序列化
該級別要求所有事務(wù)都必須串行執(zhí)行,可以避免各種并發(fā)引起的問題,效率也最低。
對不同隔離級別的解釋,其實(shí)是為了保持?jǐn)?shù)據(jù)庫事務(wù)中的隔離性(Isolation),目標(biāo)是使并發(fā)事務(wù)的執(zhí)行效果與串行一致,隔離級別的提升帶來的是并發(fā)能力的下降,兩者是負(fù)相關(guān)的關(guān)系。
分布式事務(wù)產(chǎn)生的原因
分布式事務(wù)是伴隨著系統(tǒng)拆分出現(xiàn)的,前面我們說過,分布式系統(tǒng)解決了海量數(shù)據(jù)服務(wù)對擴(kuò)展性的要求,但是增加了架構(gòu)上的復(fù)雜性,在這一點(diǎn)上,分布式事務(wù)就是典型的體現(xiàn)。
在實(shí)際開發(fā)中,分布式事務(wù)產(chǎn)生的原因主要來源于存儲和服務(wù)的拆分。
存儲層拆分
存儲層拆分,最典型的就是數(shù)據(jù)庫分庫分表,一般來說,當(dāng)單表容量達(dá)到千萬級,就要考慮數(shù)據(jù)庫拆分,從單一數(shù)據(jù)庫變成多個分庫和多個分表。在業(yè)務(wù)中如果需要進(jìn)行跨庫或者跨表更新,同時要保證數(shù)據(jù)的一致性,就產(chǎn)生了分布式事務(wù)問題。在后面的課程中,也會專門來講解數(shù)據(jù)庫拆分相關(guān)的內(nèi)容。

服務(wù)層拆分
服務(wù)層拆分也就是業(yè)務(wù)的服務(wù)化,系統(tǒng)架構(gòu)的演進(jìn)是從集中式到分布式,業(yè)務(wù)功能之間越來越解耦合。
比如電商網(wǎng)站系統(tǒng),業(yè)務(wù)初期可能是一個單體工程支撐整套服務(wù),但隨著系統(tǒng)規(guī)模進(jìn)一步變大,參考康威定律,大多數(shù)公司都會將核心業(yè)務(wù)抽取出來,以作為獨(dú)立的服務(wù)。商品、訂單、庫存、賬號信息都提供了各自領(lǐng)域的服務(wù),業(yè)務(wù)邏輯的執(zhí)行散落在不同的服務(wù)器上。
用戶如果在某網(wǎng)站上進(jìn)行一個下單操作,那么會同時依賴訂單服務(wù)、庫存服務(wù)、支付扣款服務(wù),這幾個操作如果有一個失敗,那下單操作也就完不成,這就需要分布式事務(wù)來保證了。

分布式事務(wù)解決方案
分布式事務(wù)的解決方案,典型的有兩階段和三階段提交協(xié)議、 TCC 分段提交,和基于消息隊(duì)列的最終一致性設(shè)計(jì)。
2PC 兩階段提交
兩階段提交(2PC,Two-phase Commit Protocol)是非常經(jīng)典的強(qiáng)一致性、中心化的原子提交協(xié)議,在各種事務(wù)和一致性的解決方案中,都能看到兩階段提交的應(yīng)用。
3PC 三階段提交
三階段提交協(xié)議(3PC,Three-phase_commit_protocol)是在 2PC 之上擴(kuò)展的提交協(xié)議,主要是為了解決兩階段提交協(xié)議的阻塞問題,從原來的兩個階段擴(kuò)展為三個階段,增加了超時機(jī)制。
TCC 分段提交
TCC 是一個分布式事務(wù)的處理模型,將事務(wù)過程拆分為 Try、Confirm、Cancel 三個步驟,在保證強(qiáng)一致性的同時,最大限度提高系統(tǒng)的可伸縮性與可用性。
兩階段、三階段以及 TCC 協(xié)議在后面的課程中我會詳細(xì)介紹,接下來介紹幾種系統(tǒng)設(shè)計(jì)中常用的一致性解決方案。
基于消息補(bǔ)償?shù)淖罱K一致性
異步化在分布式系統(tǒng)設(shè)計(jì)中隨處可見,基于消息隊(duì)列的最終一致性就是一種異步事務(wù)機(jī)制,在業(yè)務(wù)中廣泛應(yīng)用。
在具體實(shí)現(xiàn)上,基于消息補(bǔ)償?shù)囊恢滦灾饕斜镜叵⒈砗偷谌娇煽肯㈥?duì)列等。
下面介紹一下本地消息表,本地消息表的方案最初是由 ebay 的工程師提出,核心思想是將分布式事務(wù)拆分成本地事務(wù)進(jìn)行處理,通過消息日志的方式來異步執(zhí)行。
本地消息表是一種業(yè)務(wù)耦合的設(shè)計(jì),消息生產(chǎn)方需要額外建一個事務(wù)消息表,并記錄消息發(fā)送狀態(tài),消息消費(fèi)方需要處理這個消息,并完成自己的業(yè)務(wù)邏輯,另外會有一個異步機(jī)制來定期掃描未完成的消息,確保最終一致性。
下面我們用下單減庫存業(yè)務(wù)來簡單模擬本地消息表的實(shí)現(xiàn)過程:

(1)系統(tǒng)收到下單請求,將訂單業(yè)務(wù)數(shù)據(jù)存入到訂單庫中,并且同時存儲該訂單對應(yīng)的消息數(shù)據(jù),比如購買商品的 ID 和數(shù)量,消息數(shù)據(jù)與訂單庫為同一庫,更新訂單和存儲消息為一個本地事務(wù),要么都成功,要么都失敗。
(2)庫存服務(wù)通過消息中間件收到庫存更新消息,調(diào)用庫存服務(wù)進(jìn)行業(yè)務(wù)操作,同時返回業(yè)務(wù)處理結(jié)果。
(3)消息生產(chǎn)方,也就是訂單服務(wù)收到處理結(jié)果后,將本地消息表的數(shù)據(jù)刪除或者設(shè)置為已完成。
(4)設(shè)置異步任務(wù),定時去掃描本地消息表,發(fā)現(xiàn)有未完成的任務(wù)則重試,保證最終一致性。
以上就是基于本地消息表一致性的主流程,在具體實(shí)踐中,還有許多分支情況,比如消息發(fā)送失敗、下游業(yè)務(wù)方處理失敗等,感興趣的同學(xué)可以思考下。
不要求最終一致性的柔性事務(wù)
除了上述幾種,還有一種不保證最終一致性的柔性事務(wù),也稱為 盡最大努力通知,這種方式適合可以接受部分不一致的業(yè)務(wù)場景。
分布式事務(wù)有哪些開源組件
分布式事務(wù)開源組件應(yīng)用比較廣泛的是螞蟻金服開源的 Seata,也就是 Fescar,前身是阿里中間件團(tuán)隊(duì)發(fā)布的 TXC(Taobao Transaction Constructor)和升級后的 GTS(Global Transaction Service)。
Seata 的設(shè)計(jì)思想是把一個分布式事務(wù)拆分成一個包含了若干分支事務(wù)(Branch Transaction)的全局事務(wù)(Global Transaction)。分支事務(wù)本身就是一個滿足 ACID 的 本地事務(wù),全局事務(wù)的職責(zé)是協(xié)調(diào)其下管轄的分支事務(wù)達(dá)成一致,要么一起成功提交,要么一起失敗回滾。

在 Seata 中,全局事務(wù)對分支事務(wù)的協(xié)調(diào)基于兩階段提交協(xié)議,類似數(shù)據(jù)庫中的 XA 規(guī)范,XA 規(guī)范定義了三個組件來協(xié)調(diào)分布式事務(wù),分別是 AP 應(yīng)用程序、TM 事務(wù)管理器、RM 資源管理器、CRM 通信資源管理器。關(guān)于 XA 規(guī)范的詳細(xì)內(nèi)容,將會在后面的課時中介紹。
總結(jié)
掌握分布式事務(wù)是學(xué)習(xí)分布式系統(tǒng)的必經(jīng)之路,今天介紹了分布式事務(wù)的概念,回顧了數(shù)據(jù)庫事務(wù)和不同隔離級別,以及分布式事務(wù)產(chǎn)生的原因,最后介紹了分布式事務(wù)的幾種解決方案。
對本節(jié)課程中沒有擴(kuò)展的知識點(diǎn),比如 MySQL 的間隙鎖,Seata 組件的具體應(yīng)用等,感興趣的同學(xué)可以找相關(guān)資料去學(xué)習(xí)。