關(guān)于微服務(wù)系統(tǒng)中數(shù)據(jù)一致性的總結(jié)
你好,我是看山。
從單體架構(gòu)到分布式架構(gòu),從巨石架構(gòu)到微服務(wù)架構(gòu)。系統(tǒng)之間的交互越來越復(fù)雜,系統(tǒng)間的數(shù)據(jù)交互量級(jí)也是指數(shù)級(jí)增長(zhǎng)。作為一個(gè)系統(tǒng),我們要保證邏輯的自洽和數(shù)據(jù)的自洽。
數(shù)據(jù)自洽有兩方面要求:
- 拋開代碼,數(shù)據(jù)能夠自己驗(yàn)證自己的準(zhǔn)確性,也就是數(shù)據(jù)彼此之間不矛盾
- 所有數(shù)據(jù)準(zhǔn)確且符合期望
為了實(shí)現(xiàn)這兩點(diǎn),需要實(shí)現(xiàn)數(shù)據(jù)的一致性,為了實(shí)現(xiàn)一致性,就需要用到事務(wù)。
需要注意一下,本文所設(shè)計(jì)的數(shù)據(jù)一致性,不是多數(shù)據(jù)副本之間保持?jǐn)?shù)據(jù)一致性,而是系統(tǒng)之間的業(yè)務(wù)數(shù)據(jù)保持一致性。
本地事務(wù)
在早期的系統(tǒng)中,我們可以通過關(guān)系型數(shù)據(jù)庫(kù)的事務(wù)保證數(shù)據(jù)的一致性。這種事務(wù)有四個(gè)基本要素:ACID。
- A(Atomicity,原子性):整個(gè)事務(wù)中的所有操作,要么全部完成,要么全部失敗,不可能停滯在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
- C(Consistency,一致性):一個(gè)事務(wù)可以封裝狀態(tài)改變(除非它是一個(gè)只讀的)。事務(wù)必須始終保持系統(tǒng)處于一致的狀態(tài),不管在任何給定的時(shí)間并發(fā)事務(wù)有多少。
- I(Isolation,隔離性):隔離狀態(tài)執(zhí)行事務(wù),使它們好像是系統(tǒng)在給定時(shí)間內(nèi)執(zhí)行的唯一操作。如果有兩個(gè)事務(wù),運(yùn)行在相同的時(shí)間內(nèi),執(zhí)行相同的功能,事務(wù)的隔離性將確保每一事務(wù)在系統(tǒng)中認(rèn)為只有該事務(wù)在使用系統(tǒng)。這種屬性有時(shí)稱為串行化,為了防止事務(wù)操作間的混淆,必須串行化或序列化請(qǐng)求,使得在同一時(shí)間僅有一個(gè)請(qǐng)求用于同一數(shù)據(jù)。
- D(Durability,持久性):在事務(wù)完成以后,該事務(wù)對(duì)數(shù)據(jù)庫(kù)所作的更改便持久的保存在數(shù)據(jù)庫(kù)之中,并不會(huì)被回滾。
這四個(gè)要素是關(guān)系型數(shù)據(jù)庫(kù)的根本。無論系統(tǒng)多么復(fù)雜,只要使用同一個(gè)關(guān)系型數(shù)據(jù)庫(kù),我們就可以借助事務(wù)保證數(shù)據(jù)一致性?;趯?duì)關(guān)系型數(shù)據(jù)庫(kù)的信任,我們可以認(rèn)為本地事務(wù)是可靠的,開發(fā)過程中不需要額外的工作。從架構(gòu)的角度,關(guān)系型數(shù)據(jù)庫(kù)也是一個(gè)單獨(dú)的系統(tǒng),那關(guān)系型數(shù)據(jù)庫(kù)與應(yīng)用之間也是形成了分布式。所以我們先研究一下這種簡(jiǎn)單的分布式系統(tǒng)如何實(shí)現(xiàn) ACID。
首先,A(原子性)和 D(持久性)是彼此之間密不可分的兩個(gè)屬性:原子性保證了事務(wù)的所有操作,要么全部完成,要么全部失敗,不可能停滯在中間某個(gè)環(huán)節(jié);持久性保證了一旦事務(wù)完成,該事務(wù)對(duì)數(shù)據(jù)庫(kù)所作的更改便持久的保存在數(shù)據(jù)庫(kù)之中,不會(huì)因?yàn)槿魏卧蚨鴮?dǎo)致其修改的內(nèi)容被撤銷或丟失。
眾所周知,數(shù)據(jù)必須寫入到磁盤后才能保證持久化,僅僅保存在內(nèi)存中,一旦出現(xiàn)系統(tǒng)崩潰、主機(jī)斷電等情況,數(shù)據(jù)就會(huì)丟失。所以,關(guān)鍵是“寫入磁盤”要實(shí)現(xiàn)原子性和持久性,然而這個(gè)動(dòng)作存在中間態(tài):正在寫入。所以,現(xiàn)代的關(guān)系型數(shù)據(jù)庫(kù)通常采用追加日志記錄的方式。將修改數(shù)據(jù)所需的全部信息(包括修改什么數(shù)據(jù)、數(shù)據(jù)物理上位于哪個(gè)內(nèi)存頁(yè)和磁盤塊中、從什么值改成什么值,等等),以順序追加的形式記錄到磁盤中。只有在日志記錄全部落盤,數(shù)據(jù)庫(kù)在日志中看到代表事務(wù)成功提交的“提交記錄”后,才會(huì)根據(jù)日志上的信息對(duì)真正的數(shù)據(jù)進(jìn)行修改。修改完成后,再在日志中加入一條“結(jié)束記錄”表示事務(wù)已完成持久化,這種事務(wù)實(shí)現(xiàn)方法被稱為“提交日志”。
本地事務(wù)
我們能夠通過日志保證一個(gè)事務(wù)的原子性和持久性,那如果出現(xiàn)多個(gè)事務(wù)訪問同一個(gè)資源呢?作為程序猿都知道,多個(gè)線程/進(jìn)程訪問同一個(gè)資源,這個(gè)資源就稱為臨界資源,想要解決臨界資源占用沖突的方式很簡(jiǎn)單,就是加鎖。關(guān)系型數(shù)據(jù)庫(kù)為我們準(zhǔn)備了三種鎖:
- 寫鎖(Write Lock):同一個(gè)時(shí)刻,只有有一個(gè)事務(wù)對(duì)數(shù)據(jù)加寫鎖,所以寫鎖也被稱為排它鎖(exclusive Lock)。數(shù)據(jù)被加了寫鎖后,其他事務(wù)不能寫入數(shù)據(jù),也不能對(duì)其添加讀鎖(注意,是不能加讀鎖,但是可以讀取數(shù)據(jù))。
- 讀鎖(Read Lock):同一時(shí)刻,多個(gè)事務(wù)可以對(duì)數(shù)據(jù)添加讀鎖,所以讀鎖也被稱為共享鎖(Shared Lock)。數(shù)據(jù)庫(kù)被添加讀鎖后,數(shù)據(jù)不能被添加寫鎖。
- 范圍鎖(Range Lock):對(duì)一個(gè)范圍的數(shù)據(jù)添加寫鎖,這個(gè)范圍的數(shù)據(jù)不能被寫入。也可以算作寫鎖的批量行為。
根據(jù)這三種鎖的不同組合,我們可以實(shí)現(xiàn)四種不同的事務(wù)隔離級(jí)別:
- 可串行化(Serializable):寫入的時(shí)候加寫鎖,讀取的時(shí)候加讀鎖,范圍讀寫的時(shí)候加范圍鎖。
- 可重復(fù)度(Repeatable Read):寫入的時(shí)候加寫鎖,讀取的時(shí)候加讀鎖,范圍讀寫的時(shí)候不加鎖,這樣會(huì)出現(xiàn)讀取相同范圍數(shù)據(jù)的時(shí)候,返回結(jié)果不同,即幻讀(Phantom Read)。
- 讀已提交(Read Committed):寫入的時(shí)候加寫鎖,讀取的時(shí)候加讀鎖,讀取完成后立馬釋放讀鎖。這樣會(huì)出現(xiàn)同一個(gè)事務(wù)多次讀取相同數(shù)據(jù),返回結(jié)果不同,即不可重復(fù)讀(Non-Repeatable Read)。
- 讀未提交(Read Uncommitted):寫入的時(shí)候加寫鎖,讀取的時(shí)候不加鎖。這樣就會(huì)讀取到另一個(gè)還未提交的事務(wù)寫入的數(shù)據(jù),即臟讀(Dirty Read)。
全局事務(wù)
隨著系統(tǒng)規(guī)模不斷擴(kuò)大,業(yè)務(wù)量不斷增加。單體應(yīng)用不再滿足需求,我們會(huì)拆分系統(tǒng),然后拆分?jǐn)?shù)據(jù)庫(kù)。此時(shí),同一個(gè)請(qǐng)求中,就會(huì)出現(xiàn)同時(shí)訪問多個(gè)數(shù)據(jù)庫(kù)的情況。為了解決這種情況的數(shù)據(jù)一致性問題,X/Open 組織在 1991 年(那個(gè)時(shí)候我還小)提出了一套 X/Open XA 的處理事務(wù)的架構(gòu)。XA 的核心內(nèi)容是定義了全局的事務(wù)管理器(Transaction Manager,用于協(xié)調(diào)全局事務(wù))和局部的資源管理器(Resource Manager,用于驅(qū)動(dòng)本地事務(wù))之間的通信接口,在一個(gè)事務(wù)管理器和多個(gè)資源管理器(Resource Manager)之間形成通信橋梁,通過協(xié)調(diào)多個(gè)數(shù)據(jù)源的一致動(dòng)作,實(shí)現(xiàn)全局事務(wù)的統(tǒng)一提交或者統(tǒng)一回滾。與 XA 架構(gòu)配套的是兩階段提交協(xié)議(2PC,Two Phase Commitment Protocol)。在這個(gè)協(xié)議中,最關(guān)鍵的點(diǎn)就是,多個(gè)數(shù)據(jù)庫(kù)的活動(dòng),均由一個(gè)事務(wù)協(xié)調(diào)器的組件來控制。具體的分為 5 個(gè)步驟:
- 應(yīng)用程序調(diào)用事務(wù)管理器中的提交方法
- 事務(wù)管理器將聯(lián)絡(luò)事務(wù)中涉及的每個(gè)數(shù)據(jù)庫(kù),并通知它們準(zhǔn)備提交事務(wù)(這是第一階段的開始)
- 接收到準(zhǔn)備提交事務(wù)通知后,數(shù)據(jù)庫(kù)必須確保能在被要求提交事務(wù)時(shí)提交事務(wù),或在被要求回滾事務(wù)時(shí)回滾事務(wù)。如果數(shù)據(jù)庫(kù)無法準(zhǔn)備事務(wù),它會(huì)以一個(gè)否定響應(yīng)來回應(yīng)事務(wù)管理器。
- 事務(wù)管理器收集來自各數(shù)據(jù)庫(kù)的所有響應(yīng)。
- 在第二階段,事務(wù)管理器將事務(wù)的結(jié)果通知給每個(gè)數(shù)據(jù)庫(kù)。如果任一數(shù)據(jù)庫(kù)做出否定響應(yīng),則事務(wù)管理器會(huì)將一個(gè)回滾命令發(fā)送給事務(wù)中涉及的所有數(shù)據(jù)庫(kù)。如果數(shù)據(jù)庫(kù)都做出肯定響應(yīng),則事務(wù)管理器會(huì)指示所有的資源管理器提交事務(wù)。一旦通知數(shù)據(jù)庫(kù)提交,此后的事務(wù)就不能失敗了。通過以肯定的方式響應(yīng)第一階段,每個(gè)資源管理器均已確保,如果以后通知它提交事務(wù),則事務(wù)不會(huì)失敗。
2PC
兩階段提交協(xié)議實(shí)現(xiàn)簡(jiǎn)單,但存在幾個(gè)明顯缺陷:
- 單點(diǎn)問題:事務(wù)管理器在兩段提交中具有舉足輕重的作用,事務(wù)管理器等待資源管理器回復(fù)時(shí)可以有超時(shí)機(jī)制,允許資源管理器宕機(jī),但資源管理器等待事務(wù)管理器指令時(shí)無法做超時(shí)處理。一旦宕機(jī)的不是其中某個(gè)資源管理器,而是事務(wù)管理器的話,所有資源管理器都會(huì)受到影響。如果事務(wù)管理器一直沒有恢復(fù),沒有正常發(fā)送 Commit 或者 Rollback 的指令,那所有資源管理器都必須一直等待。
- 性能問題:兩段提交過程中,所有資源管理器相當(dāng)于被綁定成為一個(gè)統(tǒng)一調(diào)度的整體,期間要經(jīng)過兩次遠(yuǎn)程服務(wù)調(diào)用,三次數(shù)據(jù)持久化(準(zhǔn)備階段寫重做日志,事務(wù)管理器做狀態(tài)持久化,提交階段在日志寫入 Commit Record),整個(gè)過程將持續(xù)到資源管理器集群中最慢的那一個(gè)處理操作結(jié)束為止,這決定了兩段式提交的性能通常都較差。
- 一致性風(fēng)險(xiǎn):盡管提交階段時(shí)間很短,但這仍是一段明確存在的危險(xiǎn)期。如果事務(wù)管理器在發(fā)出準(zhǔn)備指令后,根據(jù)收到各個(gè)資源管理器發(fā)回的信息確定事務(wù)狀態(tài)是可以提交的,事務(wù)管理器會(huì)先持久化事務(wù)狀態(tài),并提交自己的事務(wù),如果這時(shí)候網(wǎng)絡(luò)斷開,無法再通過網(wǎng)絡(luò)向所有資源管理器發(fā)出 Commit 指令的話,就會(huì)導(dǎo)致部分?jǐn)?shù)據(jù)(事務(wù)管理器的)已提交,但部分?jǐn)?shù)據(jù)(資源管理器的)既未提交,也沒有辦法回滾,產(chǎn)生了數(shù)據(jù)不一致的問題。
能夠發(fā)現(xiàn)問題,就能夠想到辦法解決。我們高中老師說了,只要意識(shí)不滑坡,辦法總比困難多。所以又發(fā)展出了三階段提交協(xié)議(3PC,Three Phase Commitment Protocol),能夠緩解單點(diǎn)問題和準(zhǔn)備階段的性能問題。這個(gè)協(xié)議把 2PC 中的準(zhǔn)備階段拆分為 CanCommit 和 PreCommit,把提交階段改名為 DoCommit。CanCommit 是詢問階段,讓每個(gè)資源管理器根據(jù)自身情況判斷該事務(wù)是否有可能完成。
3PC 本質(zhì)是通過一次問詢,如果大家都說自己可以,那成事的可能性很大,減少了準(zhǔn)備階段直接鎖定資源的重操作。由于事務(wù)失敗回滾概率變小的原因,在三段式提交中,如果在 PreCommit 階段之后發(fā)生了事務(wù)管理器宕機(jī),即資源管理器沒有能等到 DoCommit 的消息的話,默認(rèn)的操作策略將是提交事務(wù)而不是回滾事務(wù)或者持續(xù)等待,這就相當(dāng)于避免了事務(wù)管理器單點(diǎn)問題的風(fēng)險(xiǎn)。
3PC
分布式事務(wù)
說到分布式事務(wù),不得不提 CAP 理論:任何分布式系統(tǒng)只可同時(shí)滿足一致性(Consistency)、可用性(Availability)、分區(qū)容錯(cuò)性(Partition tolerance)中的兩點(diǎn),沒法三者兼顧。
CAP 理論
- 一致性(Consistency):數(shù)據(jù)在任何時(shí)刻、任何分布式節(jié)點(diǎn)中所看到的都是符合預(yù)期的。
- 可用性(Availability):系統(tǒng)不間斷地提供服務(wù)的能力,可用性是由可靠性(Reliability)和可維護(hù)性(Serviceability)計(jì)算得出的比例值??煽啃酝ㄟ^平均無故障時(shí)間(Mean Time Between Failure,MTBF)來度量;可維護(hù)性通過平均可修復(fù)時(shí)間(Mean Time To Repair,MTTR)來度量??捎眯院饬肯到y(tǒng)可以正常使用的時(shí)間與總時(shí)間之比,公式為:A=MTBF/(MTBF+MTTR)。
- 分區(qū)容錯(cuò)性(Partition Tolerance):分布式環(huán)境中部分節(jié)點(diǎn)因網(wǎng)絡(luò)原因而彼此失聯(lián)后,系統(tǒng)仍能正確地提供服務(wù)的能力。
CAP 理論定義是經(jīng)過幾次修改的,修改后的定義本質(zhì)沒有區(qū)別,只是在邏輯上更加嚴(yán)謹(jǐn)。本文為了好理解,使用了最容易讓大眾接收并理解的定義。
既然 CAP 不能兼顧,那我們來看看缺少其中一環(huán)會(huì)出現(xiàn)什么情況:
- 選擇 CA 放棄 P:即我們認(rèn)為網(wǎng)絡(luò)可靠不會(huì)出現(xiàn)分區(qū)情況,這種可靠是各個(gè)節(jié)點(diǎn)之間不會(huì)出現(xiàn)網(wǎng)絡(luò)延遲、中斷等情況,顯然是不成立的。
- 選擇 CP 放棄 A:這樣做就是拋棄了可用性,為了保證數(shù)據(jù)一致性,一旦出現(xiàn)網(wǎng)絡(luò)異常,節(jié)點(diǎn)之間的信息同步時(shí)間可以無限制地延長(zhǎng)。使用 CP 組合的一般用于對(duì)數(shù)據(jù)質(zhì)量要求很高的場(chǎng)合,也就是為了保證數(shù)據(jù)完全一致,暫時(shí)不提供服務(wù),直到網(wǎng)絡(luò)完全恢復(fù),這可能持續(xù)一個(gè)不確定的時(shí)間,尤其是在系統(tǒng)已經(jīng)表現(xiàn)出高延遲時(shí)或者網(wǎng)絡(luò)故障導(dǎo)致失去連接時(shí)。
- 選擇 AP 放棄 C:意味著一旦發(fā)生網(wǎng)絡(luò)分區(qū),優(yōu)先提供服務(wù)可用,放棄數(shù)據(jù)一致性。這是目前分布式系統(tǒng)的主流選擇,因?yàn)榫W(wǎng)絡(luò)本身就是鏈接不同區(qū)域的服務(wù)器的,網(wǎng)絡(luò)又是不可靠的,所以 P 不能被舍棄。同時(shí),我們實(shí)現(xiàn)分布式系統(tǒng)就是為了提高可用性,這是我們的目的,不能舍棄。
這里需要再說明一下,我們選擇 AP 放棄 C 不是放棄數(shù)據(jù)一致,而是暫時(shí)放棄強(qiáng)一致性(Strong Consistency),而是選擇弱一致性,即最終一致性(Eventual Consistency):系統(tǒng)中的所有數(shù)據(jù)副本經(jīng)過一段時(shí)間后,最終能夠達(dá)到一致的狀態(tài)。這里所說的一段時(shí)間,也要是用戶可接受范圍內(nèi)的一段時(shí)間。
最終一致性也有一個(gè)理論支撐:BASE 理論(不得不說,理論界的縮寫真牛啊,ACID 是酸,CAP 是帽子,BASE 是堿),內(nèi)容主要包括:
- 基本可用(Basically Available):當(dāng)系統(tǒng)在出現(xiàn)不可預(yù)知故障的時(shí)候,允許損失部分可用性。比如,允許響應(yīng)時(shí)間增長(zhǎng),允許部分非關(guān)鍵接口降級(jí)或熔斷等。
- 軟狀態(tài)(Soft State):軟狀態(tài)也稱為弱狀態(tài),和硬狀態(tài)相對(duì)。是指允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認(rèn)為該中間狀態(tài)的存在不會(huì)影響系統(tǒng)的整體可用性,即允許系統(tǒng)在不同節(jié)點(diǎn)的數(shù)據(jù)副本之間進(jìn)行數(shù)據(jù)同步的過程存在延時(shí)。
- 最終一致性(Eventually Consistent):最終一致性強(qiáng)調(diào)的是系統(tǒng)中所有的數(shù)據(jù)副本,在經(jīng)過一段時(shí)間的同步后,最終能夠達(dá)到一個(gè)一致的狀態(tài)。因此,最終一致性的本質(zhì)是需要系統(tǒng)保證最終數(shù)據(jù)能夠達(dá)到一致,而不需要實(shí)時(shí)保證系統(tǒng)數(shù)據(jù)的強(qiáng)一致性。
在工程實(shí)踐中,最終一致性分為 5 種,這 5 種方式會(huì)結(jié)合使用,共同實(shí)現(xiàn)最終一致性:
- 因果一致性(Causal consistency):如果節(jié)點(diǎn) A 在更新完某個(gè)數(shù)據(jù)后通知了節(jié)點(diǎn) B,那么節(jié)點(diǎn) B 之后對(duì)該數(shù)據(jù)的訪問和修改都是基于 A 更新后的值。于此同時(shí),和節(jié)點(diǎn) A 無因果關(guān)系的節(jié)點(diǎn) C 的數(shù)據(jù)訪問則沒有這樣的限制。
- 讀己之所寫(Read your writes):節(jié)點(diǎn) A 更新一個(gè)數(shù)據(jù)后,它自身總是能訪問到自身更新過的最新值,而不會(huì)看到舊值。
- 會(huì)話一致性(Session consistency):會(huì)話一致性將對(duì)系統(tǒng)數(shù)據(jù)的訪問過程框定在了一個(gè)會(huì)話當(dāng)中,系統(tǒng)能保證在同一個(gè)有效的會(huì)話中實(shí)現(xiàn)“讀己之所寫”的一致性,也就是說,執(zhí)行更新操作之后,客戶端能夠在同一個(gè)會(huì)話中始終讀取到該數(shù)據(jù)項(xiàng)的最新值。
- 單調(diào)讀一致性(Monotonic read consistency):如果一個(gè)節(jié)點(diǎn)從系統(tǒng)中讀取出一個(gè)數(shù)據(jù)項(xiàng)的某個(gè)值后,那么系統(tǒng)對(duì)于該節(jié)點(diǎn)后續(xù)的任何數(shù)據(jù)訪問都不應(yīng)該返回更舊的值。
- 單調(diào)寫一致性(Monotonic write consistency):一個(gè)系統(tǒng)要能夠保證來自同一個(gè)節(jié)點(diǎn)的寫操作被順序的執(zhí)行。
一致性關(guān)系模型
有了理論之后,我們來說一下實(shí)現(xiàn)最終一致性的幾種模式。
可靠事件模式
可靠事件模式屬于事件驅(qū)動(dòng)架構(gòu):當(dāng)某個(gè)事件發(fā)生時(shí),例如更新一個(gè)業(yè)務(wù)實(shí)體,服務(wù)會(huì)向消息代理發(fā)布一個(gè)事件。消息代理會(huì)向訂閱事件的服務(wù)推送事件,當(dāng)訂閱這些事件的服務(wù)接收此事件時(shí),就可以完成自己的業(yè)務(wù),也可能會(huì)引發(fā)更多的事件發(fā)布。
我們通過一個(gè)例子來解釋一下這種模式,用戶下單成功后,訂單系統(tǒng)需要通知庫(kù)存系統(tǒng)減庫(kù)存。
可靠事件模式
- 訂單系統(tǒng)根據(jù)用戶操作完成下單操作。此時(shí)會(huì)使用同一個(gè)本地事務(wù)保存訂單信息和寫入事件。
- 另外一個(gè)消息服務(wù)會(huì)輪詢事件表,將狀態(tài)是“進(jìn)行中”的事件以消息形式發(fā)送到消息服務(wù)中。如果發(fā)送失敗,因?yàn)槭禽喸內(nèi)蝿?wù),會(huì)在下一次輪詢的時(shí)候再次發(fā)送。(此處有一些優(yōu)化點(diǎn),本例為了簡(jiǎn)化模型,不展開)
- 消息服務(wù)向訂閱下單消息的庫(kù)存服務(wù)發(fā)送下單成功消息,庫(kù)存服務(wù)開始處理。此時(shí)會(huì)有這么集中情況:
- 庫(kù)存服務(wù)扣減庫(kù)存成功,消息服務(wù)接收到處理成功響應(yīng)。消息服務(wù)將響應(yīng)結(jié)果返回給訂單服務(wù),訂單服務(wù)中事件接收器將事件修改為“已完成”。
- 庫(kù)存服務(wù)扣減庫(kù)存失敗,消息服務(wù)接收到處理失敗響應(yīng)。此時(shí)消息服務(wù)會(huì)再次向庫(kù)存服務(wù)發(fā)送消息,直到得到成功響應(yīng)。如果失敗次數(shù)達(dá)到閾值,可以告警通知人工介入。
- 消息服務(wù)給訂單服務(wù)返回結(jié)果時(shí),發(fā)生失敗,訂單服務(wù)沒有接收到成功響應(yīng)。這個(gè)時(shí)候,事件輪詢邏輯會(huì)再次將事件發(fā)送給消息服務(wù)。這樣,庫(kù)存服務(wù)會(huì)重復(fù)收到扣減庫(kù)存的消息,所以要求庫(kù)存服務(wù)做好冪等。庫(kù)存服務(wù)發(fā)現(xiàn)消息已經(jīng)處理過,直接返回成功。
這種靠著持續(xù)重試來保證可靠性的解決方案,叫做“最大努力交付”(Best-Effort Delivery),也是“可靠”兩個(gè)字的來源。
可靠事件模式還有一種更普通的形式,被稱為“最大努力一次提交”(Best-Effort 1PC),指的就是將最有可能出錯(cuò)或最核心的業(yè)務(wù)以本地事務(wù)的方式完成后,采用不斷重試的方式(不限于消息服務(wù))來促使同一個(gè)分布式事務(wù)中的其他關(guān)聯(lián)業(yè)務(wù)全部完成。找到最可能出錯(cuò)的方式是提前做好出錯(cuò)概率的先驗(yàn)評(píng)估,才能夠知道哪塊最容易出錯(cuò)。找到最核心的業(yè)務(wù)的方式是找到那種只要成功,其他業(yè)務(wù)必須成功的那塊業(yè)務(wù)。
這里我們?cè)傺a(bǔ)充兩個(gè)概念:
- 業(yè)務(wù)異常:業(yè)務(wù)邏輯產(chǎn)生錯(cuò)誤的情況,比如賬戶余額不足、商品庫(kù)存不足等。
- 技術(shù)異常:非業(yè)務(wù)邏輯產(chǎn)生的異常,如網(wǎng)絡(luò)連接異常、網(wǎng)絡(luò)超時(shí)等。
TCC 模式
TCC(Try-Confirm-Cancel)是一種業(yè)務(wù)侵入式較強(qiáng)的事務(wù)方案,要求業(yè)務(wù)處理過程必須拆分為“預(yù)留業(yè)務(wù)資源”和“確認(rèn)/釋放消費(fèi)資源”兩個(gè)子過程,由統(tǒng)一的服務(wù)協(xié)調(diào)調(diào)度不同業(yè)務(wù)系統(tǒng)的子過程。分為以下三個(gè)階段:
- Try:嘗試執(zhí)行階段,完成所有業(yè)務(wù)可執(zhí)行性的檢查(保障一致性),并且預(yù)留好全部需要用到的業(yè)務(wù)資源(保障隔離性)。
- Confirm:確認(rèn)執(zhí)行階段,不進(jìn)行任何業(yè)務(wù)檢查,直接使用 Try 階段準(zhǔn)備的資源來完成業(yè)務(wù)處理。Confirm 階段可能會(huì)重復(fù)執(zhí)行,需要滿足冪等性。
- Cancel:取消執(zhí)行階段,釋放 Try 階段預(yù)留的業(yè)務(wù)資源。Cancel 階段可能會(huì)重復(fù)執(zhí)行,需要滿足冪等性。
TCC 模式
1.訂單系統(tǒng)創(chuàng)建事務(wù),生成事務(wù) ID(用于作為識(shí)別請(qǐng)求冪等的標(biāo)識(shí)),通過活動(dòng)管理器記錄活動(dòng)日志。
2.進(jìn)入 Try 階段
- 調(diào)用賬戶系統(tǒng),檢查賬戶余額是否充足,如果充足,凍結(jié)需要的金額,此時(shí)賬戶余額是臨界資源,需要通過排它鎖或樂觀鎖保證凍結(jié)操作的安全性。
- 調(diào)用庫(kù)存系統(tǒng),檢查商品庫(kù)存是否充足,如果充足,鎖定需要的庫(kù)存,鎖庫(kù)操作也需要加鎖保證安全
3.如果所有業(yè)務(wù)返回成功,記錄活動(dòng)日志為 Confirm,進(jìn)入 Confirm 階段:
- 調(diào)用賬戶系統(tǒng),扣減凍結(jié)的金額
- 調(diào)用庫(kù)存系統(tǒng),扣減鎖定的庫(kù)存
4.第 3 步操作中如果全部完成,事務(wù)宣告結(jié)束。如果第 3 步中任何一方出現(xiàn)異常,都會(huì)根據(jù)活動(dòng)日志中的記錄,重復(fù)執(zhí)行 Confirm 操作,即進(jìn)行最大努力交付。所以各業(yè)務(wù)系統(tǒng)的 Confirm 操作需要實(shí)現(xiàn)冪等性。
5.如果第 2 步有任何一方失敗(包括業(yè)務(wù)異常和技術(shù)異常),將活動(dòng)日志記錄為 Cancel,進(jìn)入 Cancel 階段:
- 調(diào)用賬戶系統(tǒng),釋放凍結(jié)的金額
- 調(diào)用庫(kù)存系統(tǒng),釋放鎖定的庫(kù)存
6.第 5 步操作中如果全部完成,事務(wù)宣告失敗。如果第 5 步中任何一方出現(xiàn)異常(包括業(yè)務(wù)異常和技術(shù)異常),都會(huì)根據(jù)活動(dòng)日志中的記錄,重復(fù)執(zhí)行 Cacel 操作,即最大努力交付。所以各業(yè)務(wù)系統(tǒng)的 Cancel 操作也需要實(shí)現(xiàn)冪等性。
是不是感覺 TCC 與 2PC 的很像,兩者的區(qū)別在于,TCC 位于業(yè)務(wù)代碼層面,屬于白盒,2PC 位于基礎(chǔ)設(shè)施層面,屬于黑盒。所以 TCC 有更高的靈活性,可以根據(jù)需要,調(diào)整資源鎖定的粒度。
TCC 在業(yè)務(wù)執(zhí)行過程中可以預(yù)留資源,解決了可靠事件模式的資源隔離問題。但是,TCC 還有兩個(gè)明顯缺點(diǎn):
- TCC 將基礎(chǔ)設(shè)施層的邏輯上移到業(yè)務(wù)代碼,對(duì)業(yè)務(wù)有很高的侵入性,需要更高的開發(fā)成本,開發(fā)成本提升,相對(duì)應(yīng)的維護(hù)成本、開發(fā)人員的素質(zhì)等,都會(huì)有更高的要求。
- TCC 要求資源可以鎖定、占用或釋放,但是有的資源屬于外部系統(tǒng),沒有辦法實(shí)現(xiàn)鎖定。
鑒于上面的兩個(gè)缺點(diǎn),我們看看 SAGA 是否可以彌補(bǔ)。
SAGA 模式
SAGA 在英文中是“長(zhǎng)篇故事、長(zhǎng)篇記敘、一長(zhǎng)串事件”的意思。SAGA 模式的提出遠(yuǎn)早于分布式事務(wù)概念的提出(再次對(duì)前輩大佬佩服的五體投地),它源于 1987 年普林斯頓大學(xué)的 Hector Garcia-Molina 和 Kenneth Salem 在 ACM 發(fā)表的一篇論文《SAGAS》。文中提出了一種提升“長(zhǎng)時(shí)間事務(wù)”(Long Lived Transaction)運(yùn)作效率的方法,大致思路是把一個(gè)大事務(wù)分解為可以交錯(cuò)運(yùn)行的一系列子事務(wù)集合,后來發(fā)展成將一個(gè)分布式環(huán)境中的大事務(wù)分解為一系列本地事務(wù)的設(shè)計(jì)模式。在有的文章中,將這種模式稱為業(yè)務(wù)補(bǔ)償模式,SAGA 是對(duì)事務(wù)形式的描述,業(yè)務(wù)補(bǔ)償是對(duì)事務(wù)行為的描述,其本質(zhì)是一樣的。
SAGA 模式有兩種實(shí)現(xiàn):
- 正向恢復(fù)(Forward Recovery):順序執(zhí)行各個(gè)子事務(wù),如果遇到某個(gè)子事務(wù)執(zhí)行失敗,將一直重試該操作,知道成功,然后繼續(xù)執(zhí)行下一個(gè)子事務(wù)。比如用戶下單支付成功了,就一定要扣減庫(kù)存。
- 反向恢復(fù)(Backward Recovery):順序執(zhí)行各個(gè)子事務(wù),如果遇到某個(gè)子事務(wù)執(zhí)行失敗,將執(zhí)行該子事務(wù)的補(bǔ)償操作(避免因?yàn)榧夹g(shù)異常造成的失敗,補(bǔ)償操作需要冪等),然后倒序執(zhí)行已經(jīng)成功的子事務(wù)的補(bǔ)償操作。這種一般是可取消的批量操作,比如出行訂票,需要購(gòu)買飛機(jī)票、訂酒店、買門票,如果買門票失敗了,飛機(jī)票和酒店就可以取消了。
SAGA 模式
根據(jù)這兩種實(shí)現(xiàn),SAGA 可以分為兩部分:
- 子事務(wù)(Normal Transactions):大事務(wù)拆分若干個(gè)小事務(wù),將整個(gè)事務(wù) T 分解為 n 個(gè)子事務(wù),命名為 T1、T2、…、Tn。每個(gè)子事務(wù)都應(yīng)該是或者能被視為是原子行為。如果分布式事務(wù)能夠正常提交,其對(duì)數(shù)據(jù)的影響(最終一致性)應(yīng)與連續(xù)按順序成功提交 Ti 等價(jià)。
- 補(bǔ)償事務(wù)(Compensating Transactions):每個(gè)子事務(wù)對(duì)應(yīng)的補(bǔ)償動(dòng)作,命名為 C1、C2、…、Cn。
子事務(wù)與補(bǔ)償動(dòng)作需要滿足一些條件:
- Ti與 Ci必須對(duì)應(yīng)
- 補(bǔ)償動(dòng)作 Ci一定會(huì)執(zhí)行成功,即需要實(shí)現(xiàn)最大努力交付。
- Ti與 Ci需要具備冪等性
文末總結(jié)
本文主要總結(jié)了本地事務(wù)、全局事務(wù)、最終一致性等方式實(shí)現(xiàn)數(shù)據(jù)自洽。重點(diǎn)介紹了實(shí)現(xiàn)最終一致性的集中模式:可靠事件模式、TCC 模式、SAGA 模式等。數(shù)據(jù)的一致性一直是個(gè)難題,隨著微服務(wù)化之后,數(shù)據(jù)一致性更加困難,有困難不怕,只要不放棄,總會(huì)解決的。
本文轉(zhuǎn)載自微信公眾號(hào)「看山的小屋」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系看山的小屋公眾號(hào)。