讀書筆記

深入理解分布式共識算法部分讀書筆記,書作者 釋慧利

分布式與集群

  • 分布式
    分布式是指將同一個應(yīng)用的不同功能模塊分別部署,它們之間通過約定的通信協(xié)議進(jìn)行交互。

  • 集群
    集群是指將同一個應(yīng)用部署在多臺服務(wù)器上,它們擁有相同的功能,所有的成員都是平等的。

在后端部署的過程中,“分布式+集群”的部署方式也很常見。
舉例:
我們將原本的訂單服務(wù)拆解為庫存服務(wù)和支付服務(wù),同時,為了提升并發(fā)處理能力,為庫存服務(wù)和支付服務(wù)采用集群部署,并為此增加負(fù)載均衡策略。例如當(dāng)創(chuàng)建一個訂單時,訂單服務(wù)根據(jù)負(fù)載均衡策略,會將請求分發(fā)給支付服務(wù)集群和庫存服務(wù)集群中的任意成員來完成。

集群除了提升并發(fā)能力之外,還常用于滿足容錯性。我們期望當(dāng)個別成員發(fā)生故障時,不會影響整體服務(wù)的可用性。

對于數(shù)據(jù)庫的容錯最有效的容錯方式仍舊是集群。有以下的兩種方案:

  1. 主備同步,在整成運行中只有主成員,當(dāng)主成員發(fā)生故障時,備成員晉升成主成員。
  2. 多個成員組成集群,整體向外提供服務(wù),當(dāng)單個成員故障時,不影響整體服務(wù)的可用性。

第一種方案相對簡單些,不過問題仍舊存在:

  1. 在主成員將數(shù)據(jù)同步給備成員之前,主成員宕機,那么這部分?jǐn)?shù)據(jù)就會丟失。
  2. 備成員晉升為主成員期間,服務(wù)是不可用的,還有可能需要人工參與。

第二種方案較為困難,因為每個成員都是對等的服務(wù),所以其中一個成員發(fā)生故障并不會影響其它成員。但此方案要求每個成員之間保持?jǐn)?shù)據(jù)完全對齊,才能提供正確的服務(wù)。這是比較困難的,如何保證呢?復(fù)制狀態(tài)機!

基于狀態(tài)機的分布式系統(tǒng)最關(guān)鍵的是決定輸入的順序。因為相同的輸入順序才能使所有非錯誤的成員達(dá)到相同狀態(tài),這樣才能保證這些成員間的數(shù)據(jù)一致。

為了使一組成員擁有相同順序的輸入,我們需要在狀態(tài)機上再設(shè)計一個保證協(xié)議,即共識算法。

共識算法需要保證:從客戶端看來,系統(tǒng)中的所有成員都選擇了同一個提案。讓使用者看起來無論操作哪個成員,都是在操作整個集群。

共識算法運行在集群之上,單個成員談不上共識。

共識與集群的最大區(qū)別是成員之間數(shù)據(jù)的處理方式不同。集群通常需要引入一個額外的存儲服務(wù)來保證數(shù)據(jù)的一致性; 而共識是為了解決數(shù)據(jù)的一致性而存在的,因此共識系統(tǒng)不需要依賴外部的存儲服務(wù)。例如,ZooKeeper就不需要額外的存儲服務(wù),而是依賴自身的ZAB協(xié)議就能使各個成員之間的數(shù)據(jù)保持一致,但是業(yè)務(wù)邏輯通常需要引入一個公共的數(shù)據(jù)庫服務(wù)(這也合理,業(yè)務(wù)的寫入對于有共識協(xié)議的存儲不一定能滿足,比如大并發(fā)、寫多讀少時的性能問題。一個明顯的例子就是Etcd本身也是提供存儲服務(wù)的,但是對于寫多讀少的性能很差)。

共識與一致性

共識不等于一致性,共識要求的是大部分參與者對于某一個決議達(dá)成一致。而一致性則是要求所有參與成員均擁有某種相同的狀態(tài)(相同的數(shù)據(jù))。區(qū)別在于大多數(shù)和全部。共識算法可以理解為放棄了一部分成員的一致性,而增加了可用性。

拜占庭故障

Lamport證明了,在同步網(wǎng)絡(luò)下,通過驗證消息真?zhèn)尾⑶冶撑颜卟怀^1/3的情況下才有可能達(dá)成共識。其共識的原則也是少數(shù)服從多數(shù)。多數(shù)的決策生效。
所以集群數(shù)量如果是3個,則需要引入1個成員才能達(dá)到不超過1/3這個條件。所以集群的成員需要滿足成員總是為3F+1,即最多容忍F個成員背叛。

從ACID和BASE到CAP

ACID 追求一致性

  • 原子性:在一組操作中,要么全部操作執(zhí)行成功,要么全部操作執(zhí)行失敗,不存在中間數(shù)據(jù),也不存在部分操作執(zhí)行成功,部分失敗的情況。
  • 一致性:每一次事務(wù)的操作不會影響數(shù)據(jù)庫當(dāng)前的完整性,數(shù)據(jù)庫從一個完整的狀態(tài)到另一個完整的狀態(tài)。這里的完整有兩個含義,即,
    a. 規(guī)則約束的完整性,這里的規(guī)則更多的是相對于業(yè)務(wù)而言的,滿足業(yè)務(wù)的某種規(guī)則,比如轉(zhuǎn)賬業(yè)務(wù),要求轉(zhuǎn)賬前和轉(zhuǎn)賬后兩個賬戶的整體余額是不變的。
    b. 事務(wù)執(zhí)行的順序,即,在外部看來的順序性。符合某種發(fā)生的順序。滿足外部認(rèn)為的一致順序。
  • 隔離性:在多個事務(wù)并發(fā)執(zhí)行時,保證事務(wù)與事務(wù)之間不互相干擾的程度。
  • 持久性:在食物執(zhí)行成功之后,所有操作的執(zhí)行結(jié)果都是永久的,哪怕服務(wù)發(fā)生故障。

Base 追求可用性

在某些場景下,無需強的一致性,更要保證系統(tǒng)的可用性,同時每個應(yīng)用可以采用適當(dāng)?shù)姆椒ㄊ瓜到y(tǒng)數(shù)據(jù)達(dá)到最終的一致性。

  • 基本可用(Basically Available):當(dāng)分布式系統(tǒng)出現(xiàn)故障的時候,允許損失部分可用性,但不等于系統(tǒng)不可用。這里的損失部分可用性通常包括兩個方面:響應(yīng)時間的損失和在功能上的降級。
    a. 響應(yīng)時間的損失:當(dāng)部分節(jié)點宕機或者機房出現(xiàn)故障時,在請求增加響應(yīng)時間的基礎(chǔ)上,需要給用戶返回正確的數(shù)據(jù),而不是拒絕服務(wù)。
    b. 在功能上的降級:當(dāng)處于流量高峰時,一部分請求可能會返回降級的數(shù)據(jù),而不會真正請求后端核心系統(tǒng),以保證后端系統(tǒng)的穩(wěn)定性。
  • 軟狀態(tài)(Soft State):指允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認(rèn)為該中間狀態(tài)不會影響系統(tǒng)的整體可用性。即允許節(jié)點間數(shù)據(jù)同步存在延遲。允許出現(xiàn)事務(wù)的中間狀態(tài)。例如,節(jié)點之間的投票協(xié)商和多副本之間的數(shù)據(jù)同步都需要進(jìn)行網(wǎng)絡(luò)交互,交互過程中的狀態(tài)即是軟狀態(tài)的一種體現(xiàn)。
  • 最終一致性(Eventually Consistene):系統(tǒng)中的所有副本在經(jīng)過一個時間期限后最終達(dá)到一致的狀態(tài)。在BASE理論中系統(tǒng)允許出現(xiàn)事務(wù)的中間狀態(tài)(軟狀態(tài)),但是在經(jīng)過一個時間期限后,要求事務(wù)結(jié)束,所有操作要么全部成功,要么全部失敗。

BASE 理論的應(yīng)用

BASE 理論應(yīng)用于多個為服務(wù)之間的調(diào)用。將一個大型系統(tǒng)拆分成多個微服務(wù)是勢在必行的,但因此會降低系統(tǒng)的可用性。比如每個單個的微服務(wù)的可用性是99.9%,如果有多個微服務(wù)來完成一個事務(wù),比如3個,則可用性為 99.9% x 99.9% x 99.9% 約等于 99.7%,相當(dāng)于每30發(fā)生故障的時間增加了 86mins。導(dǎo)致行業(yè)內(nèi)長時間形成困局。

而解決這一問題的方案則是BASE理論。在允許存在軟狀態(tài)的基礎(chǔ)上,我們只需要保證整個事務(wù)的基本可用性和最終一致性即可,而不需要實時保證強一致性。這就需要在設(shè)計微服務(wù)的接口和調(diào)用微服務(wù)接口的客戶端要做一些補償。通常采用異步補償機制。

舉例,如果采用異步補償機制,則需要明確那些操作屬于非關(guān)鍵操作,如果非關(guān)鍵操作失敗,則應(yīng)允許業(yè)務(wù)流程繼續(xù)執(zhí)行,然后再異步補償非關(guān)鍵操作,以此來降低非關(guān)鍵操作失敗對整個事務(wù)可用性的影響。
比如在訂單系統(tǒng)中,如果支付服務(wù)和庫存服務(wù)均正常完成,但積分系統(tǒng)異常,此時通常不會放棄本次操作。因為在訂單系統(tǒng)中我們認(rèn)為,給用戶增加積分操作不是至關(guān)重要的,因此,即使失敗了,我們可以通過定時任務(wù)或者消息隊列的方式,在下單完成之后再給用戶增加積分。
但如果因為支付系統(tǒng)或者庫存系統(tǒng)任意一個發(fā)生以外,我們需要回滾此次事務(wù)。因為這兩個在訂單系統(tǒng)中屬于關(guān)鍵業(yè)務(wù)。

實現(xiàn)異步補償機制時需要注意以下幾點

  1. 每個補償操作都應(yīng)設(shè)置重試機制,且需要實現(xiàn)冪等。(因為有重試,所以需要冪等,來保證接口即使執(zhí)行了多次也不會多加或多減)
  2. 整個事務(wù)應(yīng)由工作流驅(qū)動,記錄每個分支數(shù)據(jù)的處理結(jié)果。(這就有點像狀態(tài)機)
  3. 對于事務(wù)狀態(tài),通常需要提供特殊的接口進(jìn)行查詢。(給上層提供一個視角?)
  4. 對于所有分支事務(wù),需要ui wu提供回滾事務(wù)的接口。(保證可以回滾整個事務(wù))

CAP - 分布式系統(tǒng)的PH試紙

CAP 猜想是Brewer在Towards Robust Distributed Systems演講中提出的猜想,Brewer對ACID和BASE做了進(jìn)一步對比分析,在ACID中的C和BASE中的A的基礎(chǔ)上擴展出了一個新維度,及分區(qū)容錯性(Partitions Tolerance,簡寫P),以此做出CAP猜想。

  • 一致性:所有節(jié)點的數(shù)據(jù)實時保持一致。
  • 可用性:任何情況都能夠處理客戶端的每個請求。
  • 分區(qū)容錯性:發(fā)生分區(qū)時,系統(tǒng)應(yīng)該持續(xù)提供服務(wù)。

CAP猜想的核心思想是:任何分布式系統(tǒng),其一致性、可用性、分區(qū)容錯性最多只能滿足其中兩個。
依次,任何分布式系統(tǒng)都可以歸為3類架構(gòu):CP、AP、CA。它們的特性總結(jié)如下:

  • CP(鎖定事務(wù)資源):所有節(jié)點的數(shù)據(jù)需要實時保持一致,意味著當(dāng)處理寫請求的時候,需要鎖定各個分支事務(wù)的資源。當(dāng)發(fā)生分區(qū)的時候,各節(jié)點之間不能聯(lián)通,請求完成所需的時間依賴于分區(qū)恢復(fù)的時間。在此期間為了確保返回客戶端數(shù)據(jù)的準(zhǔn)確性且不破壞一致性,可能會因為無法響應(yīng)最新數(shù)據(jù)而拒絕響應(yīng),即放棄A。
  • AP(盡最大能力提供服務(wù)):要求每個節(jié)點擁有集群的所有能力或數(shù)據(jù),能自己處理客戶端的每個請求,這樣才能實現(xiàn)盡最大能力容錯的要求。當(dāng)發(fā)生分區(qū)時,可以通過緩存或者本地數(shù)據(jù)來處理客戶端的請求,以達(dá)到可用性,但是各個節(jié)點數(shù)據(jù)將會出現(xiàn)不一致的情況,即放棄C。
  • CA(本地一致性):在不考慮P的情況下,意味著集群正常運行,一致性和可用性是可以同時滿足的(一個正確的系統(tǒng)就應(yīng)該符合這個要求)。但是網(wǎng)絡(luò)分區(qū)不可避免,永遠(yuǎn)可能發(fā)生,因此選擇CA的系統(tǒng)通常會在發(fā)生分區(qū)時,讓各個子分區(qū)滿足CA。

CAP定理描述

2002年,Lynch在發(fā)表的Brewer's Conjecture and the Fasibility of Consistent, Avaliable, Partition-Tolerant WebServices 論文中證明了CAP猜想,從此CAP猜想上升為定理。同時其對CAP進(jìn)行了更具體的第二次描述:

  • 一致性:被形容為原子性和串行化,每個讀/寫操作都像是一個原子操作,并且像是全局排好序一樣,后面的讀操作一定能讀到前面的寫操作。這意味著在分布式系統(tǒng)中執(zhí)行一個操作就像在單節(jié)點上執(zhí)行一樣。
  • 可用性:系統(tǒng)中未發(fā)生故障的節(jié)點接受的每個請求都必須產(chǎn)生響應(yīng),即,每個請求最終都必須終止,但是不要求終止之前所需的時間長短。
  • 分區(qū)容錯性:允許在集群中丟失任意數(shù)量的消息(請求),因為當(dāng)發(fā)生分區(qū)時,A分區(qū)發(fā)送到B分區(qū)的消息可能會全部丟失。

一致性必須保證每個請求都是原子的,即使由于分區(qū)導(dǎo)致任意的消息不能被傳遞。
可用性必須保證每個請求都響應(yīng),即使由于分區(qū)導(dǎo)致任意的消息丟失。
分區(qū)容錯性需要保證,即使分區(qū)中只存在一個節(jié)點,也要返回有效的原子響應(yīng)。

為什么C、A、P三者不可兼得

在分布式系統(tǒng)中,各個組件必然部署在不同的節(jié)點上,因為網(wǎng)絡(luò)本身是不可靠的,所以必然會出現(xiàn)子網(wǎng)絡(luò),也一定存在延遲和數(shù)據(jù)丟失的情況,即網(wǎng)絡(luò)分區(qū)是必然存在的。因此P(分區(qū)容錯性)是分布式系統(tǒng)必須要面對和解決的問題(因為你無法保證系統(tǒng)運行的環(huán)境永遠(yuǎn)不發(fā)生網(wǎng)絡(luò)分區(qū))。

C、A、P三者不可兼得,變成如何在C(一致性)和A(可用性)二者之間進(jìn)行抉擇。
比如,在分布式環(huán)境中,為了確保系統(tǒng)的可用性,通常將數(shù)據(jù)復(fù)制到多個備份節(jié)點上,而復(fù)制的過程需要通過網(wǎng)絡(luò)交互。當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)時候,將面臨如下兩個選擇:

  • 如果堅持保持節(jié)點之間的數(shù)據(jù)一致(選擇C),則需要等到網(wǎng)絡(luò)分區(qū)恢復(fù)后,將數(shù)據(jù)復(fù)制完成才可以對外部提供服務(wù)。在這期間發(fā)生網(wǎng)絡(luò)分區(qū)將不能對外提供服務(wù),因為它不能保證此時的數(shù)據(jù)的一致性。
  • 如果選擇可用性(選擇A),則當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)時,依然需要對外部提供服務(wù)。但是由于網(wǎng)絡(luò)分區(qū)的原因,同步不了最新的數(shù)據(jù),因此返回的數(shù)據(jù)可能不是最新的(與其它節(jié)點的數(shù)據(jù)不一致)數(shù)據(jù)。

由此可見,當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)時,我們只能在C和A中選擇其一。

CAP的應(yīng)用

CAP的指導(dǎo)作用是說,在架構(gòu)設(shè)計中,不要浪費經(jīng)歷去設(shè)計一個滿足一致性、可用性和分區(qū)容錯性三者的完美系統(tǒng),需要根據(jù)自己的業(yè)務(wù)場景進(jìn)行取舍。值得注意的是CAP中的一致性和可用性表現(xiàn)為強一致性和完全(100%)可用性。

在實際生產(chǎn)中,更加需要的是二者之間的調(diào)節(jié)劑,即對一致性和可用性的強度進(jìn)行調(diào)節(jié),使得系統(tǒng)在用戶允許和業(yè)務(wù)要求的情況下,讓系統(tǒng)的一致性和可用性達(dá)到最佳狀態(tài),而不是非黑即白。在實際生產(chǎn)中,也不需要100%的強一致性和100%完全的可用性。

例如,在一個跨區(qū)域電商平臺中,任何數(shù)據(jù)的修改都需要跨區(qū)域地通知其它節(jié)點完成數(shù)據(jù)同步。

在商家對商品詳情進(jìn)行修改的場景中,并不要求在所有的節(jié)點上該商品的信息都保持實時同步,而只要在經(jīng)過一個期間后所有區(qū)域的用戶都能看到修改后的內(nèi)容,而這個期間是用戶允許的情況即可。即,在本次的修改請求中,并不依賴所有節(jié)點成功響應(yīng),可用性便會有相應(yīng)的提升。

在用戶購買商品的場景中,對庫存的修改相對來說一致性要求更高一些,即,期望的是當(dāng)最后一個商品被購買后,所有區(qū)域的用戶都能看到該商品已經(jīng)售罄的狀態(tài)。為了實現(xiàn)庫存的實時同步,在用戶購買商品的請求中,需要依賴所有節(jié)點成功響應(yīng),則可用性便會有所降低。

在實際開發(fā)中,追求的應(yīng)該是用戶感知的可用性需要在一致性和可用性之間權(quán)衡。

常見分布式共識算法原理與實戰(zhàn)

如果要設(shè)計一個強一致性的CP架構(gòu)系統(tǒng),該如何實現(xiàn)?針對這一需求,逐漸衍生出了 兩階段提交和三階段提交協(xié)議。

2PC、3PC - 分布式事務(wù)的解決方案

2PC

兩階段提交是一個基于協(xié)調(diào)者的強一致性原子提交協(xié)議。在分布式事務(wù)中,由于各個分支事務(wù)只能知道自己執(zhí)行的結(jié)果是成功還是失敗,并不清楚其它分支事務(wù)的執(zhí)行結(jié)果,因此需要設(shè)計一個協(xié)調(diào)者的身份,各個分支事務(wù)向協(xié)調(diào)者上報執(zhí)行狀態(tài),再由協(xié)調(diào)者根據(jù)各個分支事務(wù)的執(zhí)行結(jié)果決定全局事務(wù)的提交或回滾。

  • 協(xié)調(diào)者:可以由事務(wù)的發(fā)起者充當(dāng),也可以由第三方組件充當(dāng),如Seata的TC角色。協(xié)調(diào)者維護全局事務(wù)和分支事務(wù)的狀態(tài),驅(qū)動全局事務(wù)和分支事務(wù)提交或回滾。
  • 參與者:通常由各個資源管理者充當(dāng),負(fù)責(zé)執(zhí)行各個分支事務(wù)提交或回滾,并向協(xié)調(diào)者匯報執(zhí)行狀態(tài)。

兩階段提交協(xié)議,由準(zhǔn)備階段和提交/回滾階段組成。第一個階段用于各個分支事務(wù)資源鎖定(文中這里類似于OCC的處理方式,其實第一階段也算是給協(xié)調(diào)者承諾的階段,承諾其已經(jīng)準(zhǔn)備好提交),第二階段用于全局事務(wù)的提交或回滾。

  • 階段一:準(zhǔn)備階段,又稱為投票階段(Vote Request),由協(xié)調(diào)者向參與者發(fā)送請求,以詢問當(dāng)前事務(wù)能否處理成功。詳細(xì)的流程如下:
    1. 開啟全局事務(wù)。當(dāng)協(xié)調(diào)者收到客戶端的請求后,它分別將各個分支事務(wù)需要處理的內(nèi)容,通過Prepare請求發(fā)送給所有參與者,然后詢問各個參與者能否正常處理自己的分支事務(wù),并等待各個參與者進(jìn)行響應(yīng)。
    2. 處理分支事務(wù),當(dāng)參與者收到Prepare請求后,便鎖定事務(wù)資源,然后嘗試執(zhí)行各自的分支事務(wù),記錄 Undo和Redo信息,但并不提交分支事務(wù)。
    3. 匯報分支事務(wù)狀態(tài),參與者根據(jù)第2步執(zhí)行的結(jié)果,向協(xié)調(diào)者匯報各自的分支事務(wù)狀態(tài)。例如,Yes表示自己的分支事務(wù)可以正常提交,No表示自己的分支事務(wù)不能提交。
  • 階段二:提交/回滾階段,協(xié)調(diào)者在超過約定的時間內(nèi)沒有收到全部參與者的響應(yīng)時,或者在收到所有的參與者的響應(yīng)中存在部分分支事務(wù)的返回為No,便會發(fā)起全局事務(wù)回滾。如果收到的所有分支事務(wù)的狀態(tài)都為Yes,發(fā)起全局事務(wù)提交,同時向客戶端返回全局事務(wù)結(jié)果,結(jié)束本次全局事務(wù)提交。詳細(xì)的流程如下:
    1. 驅(qū)動全局事務(wù)提交/回滾。如果協(xié)調(diào)者在超過約定時間內(nèi)收到第一階段所有參與者的響應(yīng),且所有分支事務(wù)的狀態(tài)為Yes,則向所有參與者發(fā)起Commit請求。否則向所有參與者發(fā)起Rollback請求。
    2. 提交/回滾分支事務(wù)。參與者根據(jù)第一階段記錄的Redo和Undo信息對各自的分支事務(wù)進(jìn)行提交或回滾,并釋放第一階段鎖定的事務(wù)資源。
    3. 匯報分支事務(wù)狀態(tài)。參與者在處理提交/回滾分支事務(wù)后,向協(xié)調(diào)者反饋自己負(fù)責(zé)的分支事務(wù)狀態(tài)。
    4. 關(guān)閉全局事務(wù)。當(dāng)協(xié)調(diào)者收到所有參與者的反饋后,向客戶端返回結(jié)果并關(guān)閉本次事務(wù)。

故障恢復(fù)

協(xié)調(diào)者發(fā)生故障

協(xié)調(diào)者需要先確定當(dāng)前最后一個事務(wù)實際執(zhí)行的狀態(tài),可以直接從磁盤上讀取到。然后繼續(xù)處理。
協(xié)調(diào)者需要嚴(yán)格遵守全部Commit/全部Abort的原則即可。這里不再詳細(xì)描述。

部分參與者發(fā)生故障

參與者重啟了之后通過讀磁盤知道自己上次掛的時候事務(wù)執(zhí)行到哪里。如果已經(jīng)Commit/Abort了,就可以不用處理了。如果只是Prepared了,則需要詢問協(xié)調(diào)者。如果壓根就沒有未完成的事務(wù)記錄,那么就當(dāng)啥都沒發(fā)生過。

協(xié)調(diào)者和部分參與者均發(fā)生故障

需要先恢復(fù)協(xié)調(diào)者,讓協(xié)調(diào)者繼續(xù)推進(jìn)事務(wù)。

二階段提交優(yōu)缺點

  • 優(yōu)點:容易理解、原理簡單。
  • 缺點:同步阻塞、數(shù)據(jù)不一致、單點問題和腦裂
  1. 同步阻塞:二階段提交協(xié)議的阻塞主要體現(xiàn)在參與者需要協(xié)調(diào)者的指令才能執(zhí)行第二階段的操作。當(dāng)協(xié)調(diào)者發(fā)生故障時,參與者在第一階段鎖定的資源將一直無法釋放。
  2. 數(shù)據(jù)不一致:在第二階段,如果因為網(wǎng)絡(luò)異常而導(dǎo)致一部分參與者收到Commit請求,而另一部分參與者沒有收到Commit請求,那么結(jié)果是一部分參與者提交了事務(wù),而另一部分參與者無法提交。(這里不一定會有數(shù)據(jù)不一致性,因為在一階段已經(jīng)鎖定了資源,則后續(xù)的訪問無法獲取到鎖就不能訪問,這是其一。再者,后續(xù)的訪問一般會攜帶一個較大的版本號來訪問,同樣的,有MVCC機制保證不會返回一個舊的數(shù)據(jù),而是會阻塞,因為有事務(wù)還在進(jìn)行中,直到該參與者將事務(wù)提交)。(實際上在分布式場景下的原子性是一種延遲原子性,即在一段時間后最終保證所有的事務(wù)均是提交或者回滾即保證了原子性。只要不存在某些節(jié)點提交,某些節(jié)點回滾的情況即可)
  3. 單點問題和腦裂:
    • 單點問題:二階段提交協(xié)議過于依賴協(xié)調(diào)者,當(dāng)協(xié)調(diào)者發(fā)生故障時,整個集群將不可用。(這不至于吧,協(xié)調(diào)者可以是事務(wù)的發(fā)起者)
    • 腦裂:當(dāng)集群中出現(xiàn)多個協(xié)調(diào)者時,將不能保證二階段提交的正確性。

空回滾和防懸掛

空回滾意味著,在第一階段,當(dāng)發(fā)生網(wǎng)絡(luò)丟包時,協(xié)調(diào)者發(fā)送Prepare請求沒有送達(dá)參與者。根據(jù)協(xié)調(diào)者的超時規(guī)則,協(xié)調(diào)者在等待參與者超時后將發(fā)送Rollback給所有參與者。如果此時網(wǎng)絡(luò)恢復(fù),參與者將會收到Rollback請求,而在此之前參與者并沒有處理過第一階段的任何工作。這導(dǎo)致無東西回滾。直接忽略就行。一個簡單的避免方式就是,每個事務(wù)均生成一個唯一的XID,協(xié)調(diào)者將XID攜帶在消息中,發(fā)送給參與者,參與者需要持久化記錄該XID,并將自己事務(wù)的狀態(tài)也記錄下來。就能判斷是否處理過某個XID的事務(wù)。

另一種情況,網(wǎng)絡(luò)延遲導(dǎo)致 Rollback 和 Prepare 消息都被協(xié)調(diào)者發(fā)送出去了。這樣就存在 Rollback 和 Prepare 先后到達(dá)某個協(xié)調(diào)者上。有可能存在兩個消息被不同的線程并行處理,如果Rollback先執(zhí)行了,Prepare后執(zhí)行了,會導(dǎo)致Prepare執(zhí)行之后事務(wù)會一直殘留,因為不會再有Rollback來回滾它了。
這里需要將其串行化執(zhí)行,比如,同一個XID的事務(wù)加鎖強制其先后處理。上面的場景,Rollback先執(zhí)行的時候,Prepare是不能執(zhí)行的,等到Rollback執(zhí)行完成,并記錄日志了,就可以直接忽略該XID的Prepare請求。

3PC

三階段提交協(xié)議的核心是將二階段提交協(xié)議中的第一階段一分為二,形成由詢問階段、預(yù)提交階段和提交階段組成的事務(wù)提交協(xié)議。
事務(wù)提交協(xié)議由此變?yōu)椋涸儐?-> 鎖定事務(wù)資源 -> 提交事務(wù)。將鎖定事務(wù)資源滯后可以減少事務(wù)資源鎖定的時間長短(范圍)。比如,如果集群中存在個別不具備處理事務(wù)能力的參與者,那么可以提前中斷事務(wù),而不是像二階段提交協(xié)議一樣,從一開始就鎖定所有參與者的事務(wù)資源。
另外,為了解決同步阻塞問題,三階段提交協(xié)議增加了超時機制,解決了當(dāng)協(xié)調(diào)者宕機時,參與者無法釋放事務(wù)資源的問題。這里增加的超時機制主要體現(xiàn)在參與者等待協(xié)調(diào)者的請求超時后,將會執(zhí)行默認(rèn)的提交/回滾指令。(這里相當(dāng)于協(xié)調(diào)者自協(xié)商來推進(jìn)事務(wù))

  • 階段一:CanCommit 詢問階段
    協(xié)調(diào)者在詢問階段將各個分支事務(wù)內(nèi)容通過CanCommit請求分別發(fā)送給所有參與者,參與者收到CanCommit請求后,根據(jù)自身邏輯判斷能否執(zhí)行本次事務(wù),然后將結(jié)果匯報給協(xié)調(diào)者。具體的執(zhí)行邏輯如下:

    1. 開啟全局事務(wù)。協(xié)調(diào)者收到客戶端開啟事務(wù)的請求后,會向所有參與者發(fā)送一個包含事務(wù)內(nèi)容的CanCommit請求,詢問是否可以執(zhí)行本次事務(wù)。
    2. 健康自檢。參與者收到CanCommit請求后,需要完成一下兩項工作,但并不需要鎖定事務(wù)資源。
      a. 檢查自身健康。例如,檢查自身與協(xié)調(diào)者之間的連接,以及自身處理執(zhí)行事務(wù)的能力。
      b. 判斷能否執(zhí)行本次事務(wù),判斷是否與其它事務(wù)沖突。(看起來主要是為了檢測是否有與其它事務(wù)存在沖突)
    3. 匯報分支事務(wù)狀態(tài)。參與者根據(jù)第2步執(zhí)行結(jié)果,向協(xié)調(diào)者匯報各自的分支事務(wù)狀態(tài)。Yes表示自己的分支事務(wù)可以正常提交,No表示自己的分支事務(wù)不能提交。
  • 階段二:PreCommit 預(yù)提交階段
    在詢問階段,如果協(xié)調(diào)者收到了參與者匯報的No響應(yīng)或者等待參與者匯報超時,則協(xié)調(diào)者會中斷全局事務(wù),向所有參與者發(fā)送Abort請求,同樣,參與者在這一階段等待協(xié)調(diào)者的請求超時后也會自行中斷分支事務(wù)。由于在詢問階段,參與者沒有鎖定任何事務(wù)資源,因此對預(yù)提交階段的中斷事務(wù)操作,參與者只需要更新分支事務(wù)的狀態(tài)即可。
    如果協(xié)調(diào)者收到所有參與者的Yes響應(yīng),則會發(fā)起PreCommit請求,進(jìn)入預(yù)提交階段。具體的執(zhí)行邏輯如下:

    1. 驅(qū)動預(yù)提交。協(xié)調(diào)者從詢問狀態(tài)變?yōu)轭A(yù)提交狀態(tài),并向所有參與者發(fā)送PreCommit請求,同時等待參與者的響應(yīng)。
    2. 處理分支事務(wù)。參與者收到PreCommit請求后,更新自己的狀態(tài)為預(yù)提交狀態(tài),然后鎖定事務(wù)資源,嘗試執(zhí)行各自的分支事務(wù),記錄Undo和Redo信息,但并不提交分支事務(wù)。
    3. 匯報分支事務(wù)狀態(tài)。參與者處理完分支事務(wù)后,需要向協(xié)調(diào)者反饋自己負(fù)責(zé)的分支事務(wù)狀態(tài)。
  • 階段三:DoCommit 提交階段
    同樣,根據(jù)預(yù)提交階段的結(jié)果,提交階段也存在兩種情況。如果協(xié)調(diào)者收到預(yù)提交階段的反饋存在No的響應(yīng),或者等待參與者的反饋超時,則會中斷全局事務(wù)。中斷全局事務(wù)的過程如下:

    1. 發(fā)送中斷請求。由協(xié)調(diào)者向所有參與者發(fā)送Abort請求,通知所有參與者中斷分支事務(wù)。
    2. 分支事務(wù)回滾。參與者根據(jù)二階段記錄的Undo信息來執(zhí)行回滾操作,并釋放占用的事務(wù)資源。
    3. 反饋回滾結(jié)果。參與者向協(xié)調(diào)者反饋回滾結(jié)果。
    4. 關(guān)閉全局事務(wù)。協(xié)調(diào)者關(guān)閉全局事務(wù),并向客戶端返回失敗的消息。
      如果在預(yù)提交階段所有參與者反饋的狀態(tài)為Yes,則執(zhí)行提交事務(wù)請求。經(jīng)過前兩個階段的緩沖,參與者會認(rèn)為全局事務(wù)是值得提交的。在提交階段,參與者等待協(xié)調(diào)者的請求超時后會自行提交分支事務(wù)。
      提交全局事務(wù)是由協(xié)調(diào)者發(fā)送Commit請求給所有參與者,參與者收到Commit請求后,提交分支事務(wù)并釋放事務(wù)資源。

    提交全局事務(wù)的過程如下:

    1. 發(fā)送提交請求。協(xié)調(diào)者從預(yù)提交狀態(tài)變?yōu)樘峤粻顟B(tài),然后向所有的參與者節(jié)點發(fā)送Commit請求。
    2. 分支事務(wù)提交。參與者收到Commit請求后,更新自己的狀態(tài)為“提交狀態(tài)”,并提交自己的分支事務(wù),同時釋放占用的事務(wù)資源。
    3. 反饋提交結(jié)果,參與者向協(xié)調(diào)者反饋提交結(jié)果。
    4. 關(guān)閉全局事務(wù)。協(xié)調(diào)者關(guān)閉全局事務(wù),并向客戶端返回成功消息。

故障恢復(fù)

由于三階段提交協(xié)議的超時和自動提交/中斷機制,大部分情況下都能保證協(xié)議的正確性。同樣以協(xié)調(diào)者發(fā)生故障和部分參與者發(fā)生故障的情況來描述。對于協(xié)調(diào)者和參與者同時發(fā)生故障的情況,則優(yōu)先恢復(fù)協(xié)調(diào)者。
相比于二階段提交協(xié)議,三階段提交協(xié)議在故障恢復(fù)時更加友好,多數(shù)情況下參與者可以根據(jù)自身的數(shù)據(jù)來執(zhí)行對應(yīng)的指令,而不用向協(xié)調(diào)者詢問。

協(xié)調(diào)者故障

引入超時機制將會影響協(xié)議的正確性。根據(jù)協(xié)調(diào)者發(fā)生故障的時刻,協(xié)議將存在以下幾種情況:

  1. 如果協(xié)調(diào)者發(fā)生故障之前,全局事務(wù)正處于第二階段,那么所有參與者在等待協(xié)調(diào)者請求超時后都會默認(rèn)執(zhí)行Abort指令。
  2. 如果在協(xié)調(diào)者發(fā)生故障之前,全局事務(wù)正處于第二階段,則存在兩種情況:
    • 如果所有參與者都完成了第二階段的工作,那么根據(jù)超時機制,所有參與者最終都會提交分支事務(wù)。
    • 如果一部分參與者完成了第二階段的工作,另一部分參與者未完成第二階段的工作,那么根據(jù)超時機制,一部分參與者將提交分支事務(wù),另一部分參與者將中斷分支事務(wù),造成數(shù)據(jù)不一致。(造成了一部分分支事務(wù)先行提交了,另一部分則執(zhí)行了Abort,這是很嚴(yán)重的問題,直接違背了原子性,需要避免?。?/strong>
  3. 如果在協(xié)調(diào)者發(fā)生故障之前,全局事務(wù)正處于第三階段,那么所有參與者在等待協(xié)調(diào)者的請求超時后都會自行提交分支事務(wù)。

在上述場景中,只有一種情況會導(dǎo)致算法不安全,即一部分參與者完成了第二階段工作 ,另一部分參與者未完成第二階段工作。

要解決這個問題,需要引入心跳機制,讓參與者能夠感知到協(xié)調(diào)者的運行狀態(tài)。當(dāng)參與者檢測到協(xié)調(diào)者發(fā)生異常,且自己處于第二階段時,此時應(yīng)臨時停用超時機制,等待新的協(xié)調(diào)者的Commit/Abort請求。這個約束需要滿足心跳超時要遠(yuǎn)小于等待協(xié)調(diào)者的超時。這樣才能在等待協(xié)調(diào)者響應(yīng)超時之前,覺察到協(xié)調(diào)者出現(xiàn)了異常,從而關(guān)閉等待協(xié)調(diào)者響應(yīng)的定時器。(注:這里還是存在隱患,因為涉及到定時器的問題,避免不了的會有線程調(diào)度的干預(yù),仍舊會導(dǎo)致等待協(xié)調(diào)者響應(yīng)定時器超時先被處理。此時應(yīng)該主動探測協(xié)調(diào)者的狀態(tài),再或者通過詢問其它參與者狀態(tài),是否已經(jīng)進(jìn)入了第二階段。經(jīng)過幾輪的詢問,每個參與者都應(yīng)該能夠清楚所有其它參與者的事務(wù)狀態(tài),是否都已經(jīng)進(jìn)入了第二階段。如果都已經(jīng)進(jìn)入了第二階段,則繼續(xù)推進(jìn)執(zhí)行完成,如果存在有的參與者并沒有進(jìn)入第二階段,則直接全部回滾掉。此時的決策可以讓所有參與者中“最小”的那個來決策。
這里的 最小 表示一種邏輯上的排序,能夠當(dāng)一個臨時的事務(wù)推進(jìn)的主導(dǎo)者。這個思想和Percolator的Prewrite中First Key所在的Region作為新的協(xié)調(diào)者是一個道理。

新上任的協(xié)調(diào)者需要先詢問所有參與者最后一條全局事務(wù)的處理情況。在上面的例子中,新上任的協(xié)調(diào)者將會收到一部分參與者執(zhí)行了Abort操作,一部分參與者仍處于第二階段完成的狀態(tài),因此新上任的協(xié)調(diào)者會發(fā)送Abort請求給所有參與者,這樣協(xié)議就能正?;謴?fù)了。

另外,在其他場景中,參與者雖然能自己保證協(xié)議正確性,但新上任的協(xié)調(diào)者仍需要詢問所有參與者執(zhí)行的情況, 以維護全局事務(wù)的狀態(tài)。

部分參與者故障

參與者發(fā)生故障的情況比較簡單,恢復(fù)過來的參與者只需要向協(xié)調(diào)者獲取全局事務(wù)的狀態(tài),然會執(zhí)行對應(yīng)的指令即可??赡馨l(fā)生的情景如下:

  1. 如果在參與者發(fā)生故障之前,全局事務(wù)正處在第一階段,那么無論該參與者是否已經(jīng)完成第一階段工作,協(xié)調(diào)者將會在第一階段等待超時或者第二階段等待超時后,中斷全局事務(wù),發(fā)生故障的參與者恢復(fù)后,可自行中斷分支事務(wù)。
  2. 如果在參與者發(fā)生故障前,全局事務(wù)正處于第二階段,同樣存在兩種情況:
    • 參與者未完成第二階段的工作,協(xié)調(diào)者將會在第二階段等待超時之后,中斷全局事務(wù),發(fā)生故障的參與者恢復(fù)后,自行中斷事務(wù)即可,因為第二階段自己并沒有執(zhí)行,則全局事務(wù)一定會中斷的,所以參與者可以安心自行中斷事務(wù)。
    • 參與者完成了第二階段事務(wù),其有可能向協(xié)調(diào)者反饋了Yes,也可能沒有反饋,根據(jù)約定,全局事務(wù)有可能會提交,也有可能會中斷。參與者恢復(fù)過來之后應(yīng)該向協(xié)調(diào)者詢問其情況,然后執(zhí)行對應(yīng)的指令。(此時參與者必須詢問協(xié)調(diào)者,因為自己不知協(xié)調(diào)者做了什么決策。)
  3. 如果在參與者發(fā)生故障之前,全局事務(wù)正處于第三階段,那么無論該參與者是否已經(jīng)完成第三階段的工作,在參與者故障恢復(fù)之后,都應(yīng)該向協(xié)調(diào)者詢問全局事務(wù)的狀態(tài),而不能自行提交分支事務(wù),因為其它參與者在第二階段可能會執(zhí)行失敗,導(dǎo)致全局事務(wù)中斷。

三階段提交協(xié)議的優(yōu)缺點

三階段提交協(xié)議就是為了解決二階段提交協(xié)議同步阻塞的問題而誕生的。其有如下幾個優(yōu)點:

  1. 通過增加超時機制和自動提交/中斷功能,減少了參與者的阻塞范圍。在二階段提交協(xié)議中,參與者必須要等到協(xié)調(diào)者的請求后才能執(zhí)行第二階段,而在三階段提交協(xié)議中,參與者在等待協(xié)調(diào)者的請求超時后,可以根據(jù)自己的處境來執(zhí)行響應(yīng)的操作,離開阻塞狀態(tài)。
  2. 增加的CanCommit階段降低了事務(wù)資源鎖定的范圍(這里的范圍應(yīng)該是持有鎖的時間長短),不會像二階段提交協(xié)議一樣從一開始就鎖定所有的事務(wù)資源,三階段提交協(xié)議在排斥個別不具備處理事務(wù)能力的參與者之后,再進(jìn)入第二階段的鎖定事務(wù)資源。(但是這不能保證第二階段執(zhí)行的時候,鎖定資源被阻塞的情況,只是提前先預(yù)判了。不過它能把不具有事務(wù)處理能力的的參與者剔除掉,雖然不知道這個是什么意思。。。)

三階段提交協(xié)議雖然解決了以上問題,但同時也引入了新的麻煩:

  1. 多增加一個階段等于增加了復(fù)雜度,同時到增加的RPC交互也會降低整個協(xié)議的協(xié)商效率。
  2. 在某些情況下,必然會造成數(shù)據(jù)的不正確。在三階段提交中,由于丟包或者協(xié)調(diào)者發(fā)成異常,導(dǎo)致一部分參與者收到了PreCommit請求,另一部分參與者沒有收到PreCommit請求。因為超時機制,沒收到PreCommit請求的參與者會Abort操作,而收到PreCommit請求額度參與者在等待第三階段Commit請求超時后,會自動提交分支事務(wù),從而造成了整個協(xié)議的不正確。(所以,這里需要在超時Commit之前,相互詢問其他參與者的狀態(tài),這就需要將事務(wù)的所有參與者信息均要同步給每個參與者?;蛘呙總€參與者找指定的一個參與者獲取。)

Seata

Seata是一款解決為服務(wù)架構(gòu)的分布式事務(wù)的框架。它提供了多種事務(wù)模式,如XA模式、TCC(Try-Confirm-Cancel,類似于三階段)模式、Saga模式以及AT模式。

Seata中主要存在3中角色:

  • 事務(wù)管理者(TM):負(fù)責(zé)驅(qū)動事務(wù)協(xié)調(diào)者(TC)執(zhí)行全局事務(wù)的開啟、提交和回滾操作,其通常由發(fā)起全局事務(wù)的業(yè)務(wù)服務(wù)充當(dāng)。
  • 資源管理者(RM):按照事務(wù)協(xié)調(diào)者(TC)的指令,執(zhí)行分支事務(wù)的提交或者回滾操作,并向事務(wù)協(xié)調(diào)者匯報分支事務(wù)的狀態(tài)。其一般由全局事務(wù)中涉及的所有下游服務(wù)充當(dāng)。
  • 事務(wù)協(xié)調(diào)者(TC):維護全局事務(wù)的狀態(tài),執(zhí)行全局事務(wù)的提交和回滾操作,驅(qū)動資源管理者(RM)執(zhí)行分支事務(wù)的提交和回滾操作,有Seata-Server充當(dāng)事務(wù)的協(xié)調(diào)者。

AT 模式

AT 模式是基于兩階段提交協(xié)議演化而來的。但AT模式縮小了分支事務(wù)資源的鎖定范圍(鎖定時間)。

AT模式與兩階段提交的區(qū)別
最大的區(qū)別在于AT模式縮小了分支事務(wù)資源鎖定的范圍。由于AT模式第一階段就提交了分支事務(wù),因此在第二階段也需要有相應(yīng)的變化,具體總結(jié)如下:

  1. 第一階段提交分支事務(wù),釋放本地鎖和事務(wù)資源,以提高系統(tǒng)的工作效率。
  2. 增加了Undo日志,用于第二階段的回滾。
  3. 第二階段提交異步化,進(jìn)一步提高了正常工作的協(xié)商效率。
  4. 第二階段回滾操作需要根據(jù)Undo日志,對第一階段已提交的事務(wù)進(jìn)行反向補償。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容