眾所周知,數(shù)據(jù)庫能實現(xiàn)本地事務(wù),也就是在同一個數(shù)據(jù)庫中,你可以允許一組操作要么全都正確執(zhí)行,要么全都不執(zhí)行。這里特別強調(diào)了本地事務(wù),也就是目前的數(shù)據(jù)庫只能支持同一個數(shù)據(jù)庫中的事務(wù)。但現(xiàn)在的系統(tǒng)往往采用微服務(wù)架構(gòu),業(yè)務(wù)系統(tǒng)擁有獨立的數(shù)據(jù)庫,因此就出現(xiàn)了跨多個數(shù)據(jù)庫的事務(wù)需求,這種事務(wù)即為“分布式事務(wù)”。那么在目前數(shù)據(jù)庫不支持跨庫事務(wù)的情況下,我們應(yīng)該如何實現(xiàn)分布式事務(wù)呢?本文首先會為大家梳理分布式事務(wù)的基本概念和理論基礎(chǔ),然后介紹幾種目前常用的分布式事務(wù)解決方案。廢話不多說,那就開始吧~
1. 什么是事務(wù)?
事務(wù)由一組操作構(gòu)成,我們希望這組操作能夠全部正確執(zhí)行,如果這一組操作中的任意一個步驟發(fā)生錯誤,那么就需要回滾之前已經(jīng)完成的操作。也就是同一個事務(wù)中的所有操作,要么全都正確執(zhí)行,要么全都不要執(zhí)行。
2. 事務(wù)的四大特性 ACID
說到事務(wù),就不得不提一下事務(wù)著名的四大特性。
- 原子性
- 原子性要求,事務(wù)是一個不可分割的執(zhí)行單元,事務(wù)中的所有操作要么全都執(zhí)行,要么全都不執(zhí)行。
- 一致性
- 一致性要求,事務(wù)在開始前和結(jié)束后,數(shù)據(jù)庫的完整性約束沒有被破壞。
- 隔離性
- 事務(wù)的執(zhí)行是相互獨立的,它們不會相互干擾,一個事務(wù)不會看到另一個正在運行過程中的事務(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)共同配合實現(xiàn)。
3. 事務(wù)的隔離級別
這里擴展一下,對事務(wù)的隔離性做一個詳細(xì)的解釋。
在事務(wù)的四大特性ACID中,要求的隔離性是一種嚴(yán)格意義上的隔離,也就是多個事務(wù)是串行執(zhí)行的,彼此之間不會受到任何干擾。這確實能夠完全保證數(shù)據(jù)的安全性,但在實際業(yè)務(wù)系統(tǒng)中,這種方式性能不高。因此,數(shù)據(jù)庫定義了四種隔離級別,隔離級別和數(shù)據(jù)庫的性能是呈反比的,隔離級別越低,數(shù)據(jù)庫性能越高,而隔離級別越高,數(shù)據(jù)庫性能越差。
3.1 事務(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對該記錄進行了修改,從而事務(wù)1第二次讀到了不一樣的記錄。
- 幻讀:事務(wù)1在兩次查詢的過程中,事務(wù)2對該表進行了插入、刪除操作,從而事務(wù)1第二次查詢的結(jié)果發(fā)生了變化。
不可重復(fù)讀 與 臟讀 的區(qū)別? 臟讀讀到的是尚未提交的數(shù)據(jù),而不可重復(fù)讀讀到的是已經(jīng)提交的數(shù)據(jù),只不過在兩次讀的過程中數(shù)據(jù)被另一個事務(wù)改過了。
3.2 數(shù)據(jù)庫的四種隔離級別
數(shù)據(jù)庫一共有如下四種隔離級別:
- Read uncommitted 讀未提交
- 在該級別下,一個事務(wù)對一行數(shù)據(jù)修改的過程中,不允許另一個事務(wù)對該行數(shù)據(jù)進行修改,但允許另一個事務(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)用程序采用悲觀鎖或樂觀鎖來控制。
4. 什么是分布式事務(wù)?
到此為止,所介紹的事務(wù)都是基于單數(shù)據(jù)庫的本地事務(wù),目前的數(shù)據(jù)庫僅支持單庫事務(wù),并不支持跨庫事務(wù)。而隨著微服務(wù)架構(gòu)的普及,一個大型業(yè)務(wù)系統(tǒng)往往由若干個子系統(tǒng)構(gòu)成,這些子系統(tǒng)又擁有各自獨立的數(shù)據(jù)庫。往往一個業(yè)務(wù)流程需要由多個子系統(tǒng)共同完成,而且這些操作可能需要在一個事務(wù)中完成。在微服務(wù)系統(tǒng)中,這些業(yè)務(wù)場景是普遍存在的。此時,我們就需要在數(shù)據(jù)庫之上通過某種手段,實現(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)瀏覽商品,他看中了某一項商品,便點擊下單
- 此時訂單系統(tǒng)會生成一條訂單
- 訂單創(chuàng)建成功后,支付系統(tǒng)提供支付功能
- 當(dāng)支付完成后,由積分系統(tǒng)為該用戶增加積分
上述步驟2、3、4需要在一個事務(wù)中完成。對于傳統(tǒng)單體應(yīng)用而言,實現(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àn)分布式事務(wù)的支持。
5. CAP理論
CAP理論說的是:在一個分布式系統(tǒng)中,最多只能滿足C、A、P中的兩個需求。
CAP的含義:
- C:Consistency 一致性
- 同一數(shù)據(jù)的多個副本是否實時相同。
- A:Availability 可用性
- 可用性:一定時間內(nèi) & 系統(tǒng)返回一個明確的結(jié)果 則稱為該系統(tǒng)可用。
- P:Partition tolerance 分區(qū)容錯性
- 將同一服務(wù)分布在多個系統(tǒng)中,從而保證某一個系統(tǒng)宕機,仍然有其他系統(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é)點提供相同的功能,從而整體上提升系統(tǒng)的性能,這就是使用分布式系統(tǒng)的第一個原因。
- 實現(xiàn)分區(qū)容錯性
- 單一節(jié)點 或 多個節(jié)點處于相同的網(wǎng)絡(luò)環(huán)境下,那么會存在一定的風(fēng)險,萬一該機房斷電、該地區(qū)發(fā)生自然災(zāi)害,那么業(yè)務(wù)系統(tǒng)就全面癱瘓了。為了防止這一問題,采用分布式系統(tǒng),將多個子系統(tǒng)分布在不同的地域、不同的機房中,從而保證系統(tǒng)高可用性。
這說明分區(qū)容錯性是分布式系統(tǒng)的根本,如果分區(qū)容錯性不能滿足,那使用分布式系統(tǒng)將失去意義。
此外,可用性對業(yè)務(wù)系統(tǒng)也尤為重要。在大談用戶體驗的今天,如果業(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理論。
6. BASE理論
CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件。而對于業(yè)務(wù)系統(tǒng)而言,我們往往選擇犧牲一致性來換取系統(tǒng)的可用性和分區(qū)容錯性。不過這里要指出的是,所謂的“犧牲一致性”并不是完全放棄數(shù)據(jù)一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。
- BA:Basic Available 基本可用
- “一定時間”可以適當(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),可以不需要實時一致。
- E:Eventual Consisstency:最終一致性
- 同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實時一致,但一定要保證經(jīng)過一定時間后仍然是一致的。
7. 酸堿平衡
ACID能夠保證事務(wù)的強一致性,即數(shù)據(jù)是實時一致的。這在本地事務(wù)中是沒有問題的,在分布式事務(wù)中,強一致性會極大影響分布式系統(tǒng)的性能,因此分布式系統(tǒng)中遵循BASE理論即可。但分布式系統(tǒng)的不同業(yè)務(wù)場景對一致性的要求也不同。如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在注冊成功后發(fā)送短信驗證碼等場景下,并不需要實時一致,因此遵循BASE理論即可。因此要根據(jù)具體業(yè)務(wù)場景,在ACID和BASE之間尋求平衡。
8. 分布式事務(wù)協(xié)議
下面介紹幾種實現(xiàn)分布式事務(wù)的協(xié)議。
8.1 兩階段提交協(xié)議 2PC
分布式系統(tǒng)的一個難點是如何保證架構(gòu)下多個節(jié)點在進行事務(wù)性操作的時候保持一致性。為實現(xiàn)這個目的,二階段提交算法的成立基于以下假設(shè):
- 該分布式系統(tǒng)中,存在一個節(jié)點作為協(xié)調(diào)者(Coordinator),其他節(jié)點作為參與者(Cohorts)。且節(jié)點之間可以進行網(wǎng)絡(luò)通信。
- 所有節(jié)點都采用預(yù)寫式日志,且日志被寫入后即被保持在可靠的存儲設(shè)備上,即使節(jié)點損壞不會導(dǎo)致日志數(shù)據(jù)的消失。
- 所有節(jié)點不會永久性損壞,即使損壞后仍然可以恢復(fù)。
1. 第一階段(投票階段):
- 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點詢問是否可以執(zhí)行提交操作(vote),并開始等待各參與者節(jié)點的響應(yīng)。
- 參與者節(jié)點執(zhí)行詢問發(fā)起為止的所有事務(wù)操作,并將Undo信息和Redo信息寫入日志。(注意:若成功這里其實每個參與者已經(jīng)執(zhí)行了事務(wù)操作)
- 各參與者節(jié)點響應(yīng)協(xié)調(diào)者節(jié)點發(fā)起的詢問。如果參與者節(jié)點的事務(wù)操作實際執(zhí)行成功,則它返回一個"同意"消息;如果參與者節(jié)點的事務(wù)操作實際執(zhí)行失敗,則它返回一個"中止"消息。
2. 第二階段(提交執(zhí)行階段):
當(dāng)協(xié)調(diào)者節(jié)點從所有參與者節(jié)點獲得的相應(yīng)消息都為"同意"時:
- 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點發(fā)出"正式提交(commit)"的請求。
- 參與者節(jié)點正式完成操作,并釋放在整個事務(wù)期間內(nèi)占用的資源。
- 參與者節(jié)點向協(xié)調(diào)者節(jié)點發(fā)送"完成"消息。
- 協(xié)調(diào)者節(jié)點受到所有參與者節(jié)點反饋的"完成"消息后,完成事務(wù)。
如果任一參與者節(jié)點在第一階段返回的響應(yīng)消息為"中止",或者 協(xié)調(diào)者節(jié)點在第一階段的詢問超時之前無法獲取所有參與者節(jié)點的響應(yīng)消息時:
- 協(xié)調(diào)者節(jié)點向所有參與者節(jié)點發(fā)出"回滾操作(rollback)"的請求。
- 參與者節(jié)點利用之前寫入的Undo信息執(zhí)行回滾,并釋放在整個事務(wù)期間內(nèi)占用的資源。
- 參與者節(jié)點向協(xié)調(diào)者節(jié)點發(fā)送"回滾完成"消息。
- 協(xié)調(diào)者節(jié)點受到所有參與者節(jié)點反饋的"回滾完成"消息后,取消事務(wù)。
不管最后結(jié)果如何,第二階段都會結(jié)束當(dāng)前事務(wù)。
二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:
- 執(zhí)行過程中,所有參與節(jié)點都是事務(wù)阻塞型的。當(dāng)參與者占有公共資源時,其他第三方節(jié)點訪問公共資源不得不處于阻塞狀態(tài)。
- 參與者發(fā)生故障。協(xié)調(diào)者需要給每個參與者額外指定超時機制,超時后整個事務(wù)失敗。(沒有多少容錯機制)
- 協(xié)調(diào)者發(fā)生故障。參與者會一直阻塞下去。需要額外的備機進行容錯。(這個可以依賴后面要講的Paxos協(xié)議實現(xiàn)HA)
- 二階段無法解決的問題:協(xié)調(diào)者再發(fā)出commit消息之后宕機,而唯一接收到這條消息的參與者同時也宕機了。那么即使協(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)。
8.2 三階段提交協(xié)議 3PC
與兩階段提交不同的是,三階段提交有兩個改動點。
- 引入超時機制。同時在協(xié)調(diào)者和參與者中都引入超時機制。
- 在第一階段和第二階段中插入一個準(zhǔn)備階段。保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的。
也就是說,除了引入超時機制之外,3PC把2PC的準(zhǔn)備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。
1. CanCommit階段
3PC的CanCommit階段其實和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),并進入預(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請求,并進入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階段
該階段進行真正的事務(wù)提交,也可以分為以下兩種情況。
該階段進行真正的事務(wù)提交,也可以分為以下兩種情況。
3.1 執(zhí)行提交
- 發(fā)送提交請求
- 協(xié)調(diào)接收到參與者發(fā)送的ACK響應(yīng),那么他將從預(yù)提交狀態(tài)進入到提交狀態(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ù)的中斷。
最后
另外,如果覺得本文對您有幫助的話,記得關(guān)注Wooola、轉(zhuǎn)發(fā)哦,我會為大家持續(xù)提供原創(chuàng)干貨。
原文鏈接:https://mp.weixin.qq.com/s/VbFOscil4s4XEbhznisvhQ