深入理解「分散式事務」

碼洞發表於2018-12-17
本文作者:gushitong
來源:知乎專欄「長日將盡」

個人簡介:夏天,如果這條街沒有鞋匠;我就打著赤腳,站在太陽下看太陽

深入理解「分散式事務」

如果一個事務呼叫了不同伺服器上的操作,那麼它就成為了一個分散式事務。

考慮下面一種場景:當你發了工資之後,把你的當月工資¥1024從支付寶轉到了餘額寶。

如果在支付寶賬戶扣除¥1024之後,餘額寶系統掛掉了,餘額寶的賬戶並沒有增加¥1024,這時候就出現了資料不一致的情況。

在很多系統中都能找到上述情況的影子:

  • 在下單的時候,需要在訂單表中插入一條資料,然後把庫存減去一

  • 在搜尋的時候如果點選了廣告,需要先記錄該點選事件,然後通知商家系統扣除廣告費

在一個分散式事務結束的時候,事務的原子特性要求所有參與該事務的伺服器必須全部提交或全部放棄該事務。為了實現這一點,其中一個伺服器承擔了協調者(coordinater)的角色,由它來保證所有的伺服器獲得相同的結果。

協調者(coordinater)的工作方式取決於它選用的協議,“兩階段提交”是分散式事務最常用的協議。

1、two-phase commit protocol

深入理解「分散式事務」

兩階段提交協議(two-phase commit protocol)的設計出發點是允許任何一個參與者自行放棄它自己的那部分事務。由於事務原子性的要求,如果部分事務被放棄,那麼整個分散式事務也必須被放棄。

在該協議的第一個階段,每個參與者投票表決該事務是放棄還是提交,一旦參與者要求提交事務,那麼就不允許放棄該事務。因此,在一個參與者要求提交事務之前,它必須保證最終能夠執行分散式事務中自己的那部分,即使該參與者出現故障而被中途替換掉。

一個事務的參與者如果最終能提交事務,那麼可以說參與者處於事務的準備好(prepared)狀態。為了保證能夠提交,每個參與者必須將事務中所有發生改變的物件以及自身的狀態(prepared)儲存到永續性儲存中。

在該協議的第二個階段,事務的每個參與者執行最終統一的決定。如果任何一個參與者投票放棄事務,那麼最終的決定是放棄事務。如果所有的參與者都投票提交事務,那麼最終的決定是提交事務。

問題在於,要保證每個參與者都投票,並且達成一個共同的決定。在無故障時,該協議相當簡單。但是,協議必須在出現各種故障(例如伺服器崩潰,訊息丟失或服務暫時無法通訊)時能夠正常工作。

2、兩階段提交的實現

為了實現兩階段提交協議,分散式事務中的協調者和參與者通常按照下面的介面進行通訊:

  • canCommit(trans) ?

協調者詢問參與者是否可以提交事務,參與者回覆自己的投票結果。

  • doCommit(trans) 

協調者告訴參與者提交它的那部分事務。

  • doAbort(trans) 

協調者告訴參與者放棄它的那部分事務。

  • haveCommitted(trans, participant) 

參與者用該操作向協調者確認它提交了事務。

  • getDecision(trans) ?

當參與者在投Yes票後一段時間內未收到應答時,參與者用該操作向協調者詢問事務的投票表決結果。該操作用於從伺服器崩潰或從訊息延遲中恢復。

階段一(投票階段): 1)協調者向分散式事務的所有參與者傳送canCommit?請求 2)當參與者收到canCommit請求後,它向協調者回復自己的投票(Yes/No)。    在投Yes票之前,它在永續性儲存中儲存所有物件,準備提交。如果投No票,參與者立即放棄。 階段二(提交階段): 1)協調者收集所有的投票(包括它自己的投票)。    a)如果不存在故障並且所有的投票都是Yes時,那麼協調者將決定提交事務並向所有參與者傳送doCommit       請求    b)否則,協調者決定放棄該事務,並向所有投Yes票的參與者傳送doAbort請求 2)投Yes票的等待者等待協調者傳送的doCommit或者doAbort請求。一旦參與者接收到任何一種請求訊息,    它將根據該請求放棄或者提交事務。如果請求是提交事務,那麼他還要向協調者傳送一個haveCommitted    來確認事務已經提交

3、分散式事務的故障模型

在分散式事務中執行的過程中,可能出現磁碟故障,程式崩潰以及訊息的丟失,超時等。

兩階段提交是一種達成共識的協議,在該系統中,如果程式崩潰,那麼是不可能達成共識的。但是,兩階段提交卻是在這些條件下達成了共識,這是由於程式的崩潰被遮蔽,崩潰的程式被一個新的程式取代,新程式的狀態根據永續性儲存中儲存的資訊和其他程式擁有的資訊來設定。

3.1、故障模型

Lampson提出過一個分散式事務的故障模型,包括了硬碟故障、伺服器故障以及通訊故障。該故障模型聲稱:可以保證演算法在出現故障時正確工作,但是對於不可預見的災難性故障則不能正確處理。儘管會出現錯誤,但是可以在發生不正確行為之前發現並處理這些錯誤。Lampson的故障模型包括以下故障:

  • 對永續性儲存的寫操作可能發生故障(或因為寫操作無效或因為寫入錯誤的值)。例如,將資料寫到錯誤的磁碟塊被認為是災難性故障。檔案儲存可能損壞。在永續性儲存中讀資料時可根據校驗和來判斷資料塊是否損壞。

  • 伺服器可能偶爾崩潰。當一個崩潰的伺服器由一個新程式取代後,它的可變記憶體被重置,崩潰之前的資料均丟失。此後新程式執行一個可恢復過程,根據持久儲存中的資訊以及從其他程式獲得的資訊設定物件的值,包括兩階段提交協議有關物件的值。當一個處理器出現故障時,伺服器也會崩潰,這樣它就不會傳送錯誤的資訊或將錯誤的值寫入持久儲存,即它不會產生隨機故障。伺服器崩潰可能出現在任何時候,特別是在恢復時也可能出現。

  • 訊息傳遞可能有任意長的延遲。訊息可能丟失、重複或者損壞。接收方(透過校驗和)能夠檢測到受損訊息。未發現的受損訊息和偽造的訊息可能會導致災難性故障。

利用這個關於永續性儲存、處理器和通訊的故障模型能夠設計出一個可靠系統,該系統的元件可對付任何單一故障,並提供一個簡單的故障模型。特別是,可靠儲存(stable storage)可以在出現一個write操作故障或者程式崩潰的情況下提供原子寫操作。它是透過將每一個資料塊複製到兩個磁碟上實現的。此時一個write操作用於兩個磁碟塊,在一個磁碟出現故障的前提下,另一個好的磁碟也可以提供正確資料。可靠處理器(stable processor)使用可靠儲存,用於在崩潰之後恢復物件。可透過可靠的遠端過程呼叫機制來遮蔽通訊錯誤。

3.2、兩階段提交協議的超時

在兩階段協議的不同階段,協調者或參與者都會遇到這種場景:不能處理它的那部分協議,直到接收到下一個請求或應答為止。

首先考慮這樣的情形:某個投票者投Yes票並等待協調者發回最終決定,即告訴它是提交事務還是放棄事務。這樣參與者的結果是不確定(uncertain)的,它在協調者處得到投票結果之前不能進行進一步處理。參與者不能單方面決定下一步做什麼,同時該事務使用的物件也不能釋放以用於其他事物。參與者向協調者發出getDecision請求來獲取事務的結果,直到收到應答時,才能進入兩階段協議的第二階段。

同理,如果協調者發生故障,那麼參與者將不能獲得協定,直到協調者被替代為止,這可能導致不確定狀態的參與者長時間的延遲。

不依賴協調者獲取最終決定的方法是透過參與者協作來獲得決定。這種策略的優點是可以在協調者出故障時使用。(在本篇文章中我們不討論這種方式)


4、兩階段提交的故障處理

  • 當參與者發生故障的時候:

深入理解「分散式事務」

  • 當協調者發生故障的時候:

深入理解「分散式事務」


5、兩階段提交的效能

假設一切運轉正常,即協調者參與者不出現故障,通訊也正常時,有N個參與者的兩階段提交協議需要N個canCommit訊息和應答,然後再有N個doCommit訊息。這樣訊息開銷和3N成正比,時間開銷是3次訊息往返。由於協議在沒有haveCommitted訊息時仍可以正常運作(它們的作用只是通知伺服器刪除過時的協調者訊息),因此在估計協議開銷上,不將haveCommitted訊息計算在內。

在最壞的情況下,兩階段提交協議在執行過程中可能出現任意多次伺服器和通訊故障。儘管協議不能指定協議完成的時間限制,但它能正確處理連續故障(服務崩潰或者訊息丟失),並保證最終完成。


6、使用訊息佇列來避免分散式事務

5.1、訊息佇列

由於分散式事務存在嚴重的效能問題,在設計高併發服務的時候,往往透過其他途徑來解決資料一致性問題。

舉例來講,你在北京很有名的姚記炒肝點了炒肝並付了錢後,他們並不會直接把你點的炒肝給你,而是給你一張小票,然後讓你拿著小票到出貨區排隊去取。為什麼他們要將付錢和取貨兩個動作分開呢?原因很多,其中一個很重要的原因是為了使他們接待能力增強(併發量更高)。

還是回到我們的問題,只要這張小票在,你最終是能拿到炒肝的。同理轉賬服務也是如此,當支付寶賬戶扣除1萬後,我們只要生成一個憑證(訊息)即可,這個憑證(訊息)上寫著“讓餘額寶賬戶增加 1萬”,只要這個憑證(訊息)能可靠儲存,我們最終是可以拿著這個憑證(訊息)讓餘額寶賬戶增加1萬的,即我們能依靠這個憑證(訊息)完成最終一致性。

這樣我們上述的轉賬就變成了如下過程:

  • 支付寶在扣款事務提交之前,向訊息佇列傳送訊息。此時的訊息佇列只記錄訊息,而並沒有將訊息發往餘額寶。

  • 當支付寶扣款事務提交成功,向訊息佇列傳送確認。在得到確認的指令後,訊息佇列向該訊息發往餘額寶。

  • 當支付寶扣款事務提交失敗,向訊息佇列傳送取消。在得到取消的指令後,訊息佇列取消該訊息,該訊息將不會被髮送。

  • 對於那麼未確認的訊息,需要訊息佇列去支付寶系統查詢這個訊息的狀態,並進行更新。(因為支付寶可能在扣款事務提交成功後掛掉,此時訊息的狀態未被更新為:“確認傳送“。從而導致訊息不能被髮送。

5.2、重複投遞

還有一個嚴重的問題是訊息重複投遞,以我們支付寶轉賬到餘額寶為例,如果相同的訊息被重複投遞兩次,那麼我們餘額寶賬戶將會增加2萬而不是1萬了。

深入理解「分散式事務」

我們將在後續的文章討論訊息佇列的兩個問題:

  • Exactly-once delivery

  • Guaranteed order of messages 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31561269/viewspace-2285478/,如需轉載,請註明出處,否則將追究法律責任。

相關文章