01.29 分佈式:深入理解分佈式系統的2PC和3PC(兩階段和三階段提交)

之前寫過一篇文章專門介紹了一下2PC和3PC(詳見:關於分佈式事務、兩階段提交協議、三階提交協議)。上一篇文章中主要介紹了下這兩種分佈式一致性協議的概念、具體提交流程以及優缺點。本文在上篇文章的基礎上在深入瞭解下這兩種分佈式一致性協議。主要來分析下為什麼2PC存在問題,為什麼3PC解決了部分2PC存在的問題,以及為什麼3PC還存在可能導致數據不一致的情況。

對分佈式系統的概念及2PC和3PC不瞭解的朋友建議先閱讀分佈式系列文章。

協調者

在分佈式系統中,每一個機器節點雖然都能明確的知道自己執行的事務是成功還是失敗,但是卻無法知道其他分佈式節點的事務執行情況。因此,當一個事務要跨越多個分佈式節點的時候(比如,淘寶下單流程,下單系統和庫存系統可能就是分別部署在不同的分佈式節點中),為了保證該事務可以滿足ACID,就要引入一個協調者(Cooradinator)。其他的節點被稱為參與者(Participant)。協調者負責調度參與者的行為,並最終決定這些參與者是否要把事務進行提交。

二階段提交協議(2PC)

二階段提交協議主要分為來個階段:準備階段和提交階段。

在日常生活中其實是有很多事都是這種二階段提交的,比如西方婚禮中就經常出現這種場景:

牧師:"你願意娶這個女人嗎?愛她、忠誠於她,無論她貧困、患病或者殘疾,直至死亡。Doyou(你願意嗎)?"新郎:"Ido(我願意)!"牧師:"你願意嫁給這個男人嗎?愛他、忠誠於他,無論他貧困、患病或者殘疾,直至死亡。Doyou(你願意嗎)?"新娘:"Ido(我願意)!"牧師:現在請你們面向對方,握住對方的雙手,作為妻子和丈夫向對方宣告誓言。新郎:我——某某某,全心全意娶你做我的妻子,無論是順境或逆境,富裕或貧窮,健康或疾病,快樂或憂愁,我都將毫無保留地愛你,我將努力去理解你,完完全全信任你。我們將成為一個整體,互為彼此的一部分,我們將一起面對人生的一切,去分享我們的夢想,作為平等的忠實伴侶,度過今後的一生。新娘:我全心全意嫁給你作為你的妻子,無論是順境或逆境,富裕或貧窮,健康或疾病,快樂或憂愁,我都將毫無保留的愛你,我將努力去理解你,完完全全信任你,我們將成為一個整體,互為彼此的一部分,我們將一起面對人生的一切,去分享我們的夢想,作為平等的忠實伴侶,度過今後的一生。

上面這個比較經典的橋段就是一個典型的二階段提交過程。

首先協調者(牧師)會詢問兩個參與者(二位新人)是否能執行事務提交操作(願意結婚)。如果兩個參與者能夠執行事務的提交,先執行事務操作,然後返回YES,如果沒有成功執行事務操作,就返回NO。

當協調者接收到所有的參與者的反饋之後,開始進入事務提交階段。如果所有參與者都返回YES,那就發送COMMIT請求,如果有一個人返回NO,那就返送roolback請求。

值得注意的是,二階段提交協議的第一階段準備階段不僅僅是回答YES or NO,還是要執行事務操作的,只是執行完事務操作,並沒有進行commit還是roolback。和上面的結婚例子不太一樣。如果非要舉例的話可以理解為男女雙方交換定情信物的過程。信物一旦交給對方了,這個信物就不能挪作他用了。也就是說,一旦事務執行之後,在沒有執行commit或者roolback之前,資源是被鎖定的。這會造成阻塞。

2PC存在的問題

下面我們來分析下2PC存在的問題。

這裡暫且不談2PC存在的同步阻塞、單點問題、腦裂等問題(上篇文章中有具體介紹),我們只討論下數據一致性問題。作為一個分佈式的一致性協議,我們主要關注他可能帶來的一致性問題的。


2PC在執行過程中可能發生協調者或者參與者突然宕機的情況,在不同時期宕機可能有不同的現象。


情況一:協調者掛了,參與者沒掛

這種情況其實比較好解決,只要找一個協調者的替代者。當他成為新的協調者的時候,詢問所有參與者的最後那條事務的執行情況,他就可以知道是應該做什麼樣的操作了。所以,這種情況不會導致數據不一致。


情況二:參與者掛了,協調者沒掛

這種情況其實也比較好解決。如果協調者掛了。那麼之後的事情有兩種情況:

  • 第一個是掛了就掛了,沒有再恢復。那就掛了唄,反正不會導致數據一致性問題。
  • 第二個是掛了之後又恢復了,這時如果他有未執行完的事務操作,直接取消掉,然後詢問協調者目前我應該怎麼做,協調者就會比對自己的事務執行記錄和該參與者的事務執行記錄,告訴他應該怎麼做來保持數據的一致性。

情況三:參與者掛了,協調者也掛了

這種情況比較複雜,我們分情況討論。

  • 協調者和參與者在第一階段掛了。
  • 由於這時還沒有執行commit操作,新選出來的協調者可以詢問各個參與者的情況,再決定是進行commit還是roolback。因為還沒有commit,所以不會導致數據一致性問題。
  • 第二階段協調者和參與者掛了,掛了的這個參與者在掛之前並沒有接收到協調者的指令,或者接收到指令之後還沒來的及做commit或者roolback操作。
  • 這種情況下,當新的協調者被選出來之後,他同樣是詢問所有的參與者的情況。只要有機器執行了abort(roolback)操作或者第一階段返回的信息是No的話,那就直接執行roolback操作。如果沒有人執行abort操作,但是有機器執行了commit操作,那麼就直接執行commit操作。這樣,當掛掉的參與者恢復之後,只要按照協調者的指示進行事務的commit還是roolback操作就可以了。因為掛掉的機器並沒有做commit或者roolback操作,而沒有掛掉的機器們和新的協調者又執行了同樣的操作,那麼這種情況不會導致數據不一致現象。
  • 第二階段協調者和參與者掛了,掛了的這個參與者在掛之前已經執行了操作。但是由於他掛了,沒有人知道他執行了什麼操作。
  • 這種情況下,新的協調者被選出來之後,如果他想負起協調者的責任的話他就只能按照之前那種情況來執行commit或者roolback操作。這樣新的協調者和所有沒掛掉的參與者就保持了數據的一致性,我們假定他們執行了commit。但是,這個時候,那個掛掉的參與者恢復了怎麼辦,因為他之前已經執行完了之前的事務,如果他執行的是commit那還好,和其他的機器保持一致了,萬一他執行的是roolback操作那?這不就導致數據的不一致性了麼?雖然這個時候可以再通過手段讓他和協調者通信,再想辦法把數據搞成一致的,但是,這段時間內他的數據狀態已經是不一致的了!

所以,2PC協議中,如果出現協調者和參與者都掛了的情況,有可能導致數據不一致。

為了解決這個問題,衍生除了3PC。我們接下來看看3PC是如何解決這個問題的。

三階段提交協議(3PC)

3PC最關鍵要解決的就是協調者和參與者同時掛掉的問題,所以3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。在第一階段,只是詢問所有參與者是否可可以執行事務操作,並不在本階段執行事務操作。當協調者收到所有的參與者都返回YES時,在第二階段才執行事務操作,然後在第三階段在執行commit或者rollback。

這裡再舉一個生活中類似三階段提交的例子:

班長要組織全班同學聚餐,由於大家畢業多年,所以要逐個打電話敲定時間,時間初定10.1日。然後開始逐個打電話。班長:小A,我們想定在10.1號聚會,你有時間嘛?有時間你就說YES,沒有你就說NO,然後我還會再去問其他人,具體時間地點我會再通知你,這段時間你可先去幹你自己的事兒,不用一直等著我。(協調者詢問事務是否可以執行,這一步不會鎖定資源)小A:好的,我有時間。(參與者反饋)班長:小B,我們想定在10.1號聚會......不用一直等我。班長收集完大家的時間情況了,一看大家都有時間,那麼就再次通知大家。(協調者接收到所有YES指令
)班長:小A,我們確定了10.1號聚餐,你要把這一天的時間空出來,這一天你不能再安排其他的事兒了。然後我會逐個通知其他同學,通知完之後我會再來和你確認一下,還有啊,如果我沒有特意給你打電話,你就10.1號那天來聚餐就行了。對了,你確定能來是吧?(協調者發送事務執行指令,這一步鎖住資源。如果由於網絡原因參與者在後面沒有收到協調者的命令,他也會執行commit)小A順手在自己的日曆上把10.1號這一天圈上了,然後跟班長說,我可以去。(參與者執行事務操作,反饋狀態)班長:小B,我們覺得了10.1號聚餐......你就10.1號那天來聚餐就行了。班長通知完一圈之後。所有同學都跟他說:"我已經把10.1號這天空出來了"。於是,他在10.1號這一天又挨個打了一遍電話告訴他們:嘿,現在你們可以出門拉。。。。(協調者收到所有參與者的ACK響應,通知所有參與者執行事務的commit)小A,小B:我已經出門拉。(執行commit操作,反饋狀態

3PC為什麼比2PC好?

直接分析協調者和參與者都掛的情況。

  • 第二階段協調者和參與者掛了,掛了的這個參與者在掛之前已經執行了操作。但是由於他掛了,沒有人知道他執行了什麼操作。
  • 這種情況下,當新的協調者被選出來之後,他同樣是詢問所有的參與者的情況來覺得是commit還是roolback。這看上去和二階段提交一樣啊?他是怎麼解決一致性問題的呢?

  • 看上去和二階段提交的那種數據不一致的情況的現象是一樣的,但仔細分析所有參與者的狀態的話就會發現其實並不一樣。我們假設掛掉的那臺參與者執行的操作是commit。那麼其他沒掛的操作者的狀態應該是什麼?他們的狀態要麼是prepare-commit要麼是commit。因為3PC的第三階段一旦有機器執行了commit,那必然第一階段大家都是同意commit。所以,這時,新選舉出來的協調者一旦發現未掛掉的參與者中有人處於commit狀態或者是prepare-commit的話,那就執行commit操作。否則就執行rollback操作。這樣掛掉的參與者恢復之後就能和其他機器保持數據一致性了。(為了簡單的讓大家理解,筆者這裡簡化了新選舉出來的協調者執行操作的具體細節,真實情況比我描述的要複雜)

簡單概括一下就是,如果掛掉的那臺機器已經執行了commit,那麼協調者可以從所有未掛掉的參與者的狀態中分析出來,並執行commit。如果掛掉的那個參與者執行了rollback,那麼協調者和其他的參與者執行的肯定也是rollback操作。

所以,再多引入一個階段之後,3PC解決了2PC中存在的那種由於協調者和參與者同時掛掉有可能導致的數據一致性問題。

3PC存在的問題

在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者rebort請求時,會在等待超時之後,會繼續進行事務的提交。

所以,由於網絡原因,協調者發送的abort響應沒有及時被參與者接收到,那麼參與者在等待超時之後執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。

參考資料2PC和3PC一點理解再談2PC和3PC


分享到:


相關文章: