原文連接:https://zhuanlan.zhihu.com/p/51684618面試
若是一個事務調用了不一樣服務器上的操做,那麼它就成爲了一個分佈式事務。算法
考慮下面一種場景:當你發了工資以後,把你的當月工資¥1024從支付寶轉到了餘額寶。服務器
若是在支付寶帳戶扣除¥1024以後,餘額寶系統掛掉了,餘額寶的帳戶並無增長¥1024,這時候就出現了數據不一致的狀況。多線程
在不少系統中都能找到上述狀況的影子:架構
在一個分佈式事務結束的時候,事務的原子特性要求全部參與該事務的服務器必須所有提交或所有放棄該事務。爲了實現這一點,其中一個服務器承擔了協調者(coordinater)的角色,由它來保證全部的服務器得到相同的結果。併發
協調者(coordinater)的工做方式取決於它選用的協議,「兩階段提交」是分佈式事務最經常使用的協議。分佈式
一、two-phase commit protocol微服務
兩階段提交協議(two-phase commit protocol)的設計出發點是容許任何一個參與者自行放棄它本身的那部分事務。因爲事務原子性的要求,若是部分事務被放棄,那麼整個分佈式事務也必須被放棄。高併發
在該協議的第一個階段,每一個參與者投票表決該事務是放棄仍是提交,一旦參與者要求提交事務,那麼就不容許放棄該事務。所以,在一個參與者要求提交事務以前,它必須保證最終可以執行分佈式事務中本身的那部分,即便該參與者出現故障而被中途替換掉。性能
一個事務的參與者若是最終能提交事務,那麼能夠說參與者處於事務的準備好(prepared)狀態。爲了保證可以提交,每一個參與者必須將事務中全部發生改變的對象以及自身的狀態(prepared)保存到持久性存儲中。
在該協議的第二個階段,事務的每一個參與者執行最終統一的決定。若是任何一個參與者投票放棄事務,那麼最終的決定是放棄事務。若是全部的參與者都投票提交事務,那麼最終的決定是提交事務。
問題在於,要保證每一個參與者都投票,而且達成一個共同的決定。在無端障時,該協議至關簡單。可是,協議必須在出現各類故障(例如服務器崩潰,消息丟失或服務暫時沒法通訊)時可以正常工做。
二、兩階段提交的實現
爲了實現兩階段提交協議,分佈式事務中的協調者和參與者一般按照下面的接口進行通訊:
協調者詢問參與者是否能夠提交事務,參與者回覆本身的投票結果。
協調者告訴參與者提交它的那部分事務。
協調者告訴參與者放棄它的那部分事務。
參與者用該操做向協調者確認它提交了事務。
當參與者在投Yes票後一段時間內未收到應答時,參與者用該操做向協調者詢問事務的投票表決結果。該操做用於從服務器崩潰或從消息延遲中恢復。
階段一(投票階段): 1)協調者向分佈式事務的全部參與者發送canCommit?請求 2)當參與者收到canCommit請求後,它向協調者回複本身的投票(Yes/No)。 在投Yes票以前,它在持久性存儲中保存全部對象,準備提交。若是投No票,參與者當即放棄。 階段二(提交階段): 1)協調者收集全部的投票(包括它本身的投票)。 a)若是不存在故障而且全部的投票都是Yes時,那麼協調者將決定提交事務並向全部參與者發送doCommit 請求 b)不然,協調者決定放棄該事務,並向全部投Yes票的參與者發送doAbort請求 2)投Yes票的等待者等待協調者發送的doCommit或者doAbort請求。一旦參與者接收到任何一種請求消息, 它將根據該請求放棄或者提交事務。若是請求是提交事務,那麼他還要向協調者發送一個haveCommitted 來確認事務已經提交
三、分佈式事務的故障模型
在分佈式事務中執行的過程當中,可能出現磁盤故障,進程崩潰以及消息的丟失,超時等。
兩階段提交是一種達成共識的協議,在該系統中,若是進程崩潰,那麼是不可能達成共識的。可是,兩階段提交倒是在這些條件下達成了共識,這是因爲進程的崩潰被屏蔽,崩潰的進程被一個新的進程取代,新進程的狀態根據持久性存儲中保存的信息和其餘進程擁有的信息來設定。
3.一、故障模型
Lampson提出過一個分佈式事務的故障模型,包括了硬盤故障、服務器故障以及通訊故障。該故障模型聲稱:能夠保證算法在出現故障時正確工做,可是對於不可預見的災難性故障則不能正確處理。儘管會出現錯誤,可是能夠在發生不正確行爲以前發現並處理這些錯誤。Lampson的故障模型包括如下故障:
利用這個關於持久性存儲、處理器和通訊的故障模型可以設計出一個可靠系統,該系統的組件可對付任何單一故障,並提供一個簡單的故障模型。特別是,可靠存儲(stable storage)能夠在出現一個write操做故障或者進程崩潰的狀況下提供原子寫操做。它是經過將每個數據塊複製到兩個磁盤上實現的。此時一個write操做用於兩個磁盤塊,在一個磁盤出現故障的前提下,另外一個好的磁盤也能夠提供正確數據。可靠處理器(stable processor)使用可靠存儲,用於在崩潰以後恢復對象。可經過可靠的遠程過程調用機制來屏蔽通訊錯誤。
3.二、兩階段提交協議的超時
在兩階段協議的不一樣階段,協調者或參與者都會遇到這種場景:不能處理它的那部分協議,直到接收到下一個請求或應答爲止。
首先考慮這樣的情形:某個投票者投Yes票並等待協調者發回最終決定,即告訴它是提交事務仍是放棄事務。這樣參與者的結果是不肯定(uncertain)的,它在協調者處獲得投票結果以前不能進行進一步處理。參與者不能單方面決定下一步作什麼,同時該事務使用的對象也不能釋放以用於其餘事物。參與者向協調者發出getDecision請求來獲取事務的結果,直到收到應答時,才能進入兩階段協議的第二階段。
同理,若是協調者發生故障,那麼參與者將不能得到協定,直到協調者被替代爲止,這可能致使不肯定狀態的參與者長時間的延遲。
不依賴協調者獲取最終決定的方法是經過參與者協做來得到決定。這種策略的優勢是能夠在協調者出故障時使用。(在本篇文章中我討論這種方式)
四、兩階段提交的故障處理
五、兩階段提交的性能
假設一切運轉正常,即協調者參與者不出現故障,通訊也正常時,有N個參與者的兩階段提交協議須要N個canCommit消息和應答,而後再有N個doCommit消息。這樣消息開銷和3N成正比,時間開銷是3次消息往返。因爲協議在沒有haveCommitted消息時仍能夠正常運做(它們的做用只是通知服務器刪除過期的協調者消息),所以在估計協議開銷上,不將haveCommitted消息計算在內。
在最壞的狀況下,兩階段提交協議在執行過程當中可能出現任意屢次服務器和通訊故障。儘管協議不能指定協議完成的時間限制,但它能正確處理連續故障(服務崩潰或者消息丟失),並保證最終完成。加Q羣:479499375可獲取一份Java架構進階技術精品視頻。(高併發+Spring源碼+JVM原理解析+分佈式架構+微服務架構+多線程併發原理+BATJ面試寶典)
六、使用消息隊列來避免分佈式事務
6.一、消息隊列
因爲分佈式事務存在嚴重的性能問題,在設計高併發服務的時候,每每經過其餘途徑來解決數據一致性問題。
舉例來說,你在北京頗有名的姚記炒肝點了炒肝並付了錢後,他們並不會直接把你點的炒肝給你,而是給你一張小票,而後讓你拿着小票到出貨區排隊去取。爲何他們要將付錢和取貨兩個動做分開呢?緣由不少,其中一個很重要的緣由是爲了使他們接待能力加強(併發量更高)。
仍是回到咱們的問題,只要這張小票在,你最終是能拿到炒肝的。同理轉帳服務也是如此,當支付寶帳戶扣除1萬後,咱們只要生成一個憑證(消息)便可,這個憑證(消息)上寫着「讓餘額寶帳戶增長 1萬」,只要這個憑證(消息)能可靠保存,咱們最終是能夠拿着這個憑證(消息)讓餘額寶帳戶增長1萬的,即咱們能依靠這個憑證(消息)完成最終一致性。
這樣咱們上述的轉帳就變成了以下過程:
6.二、重複投遞
還有一個嚴重的問題是消息重複投遞,以咱們支付寶轉帳到餘額寶爲例,若是相同的消息被重複投遞兩次,那麼咱們餘額寶帳戶將會增長2萬而不是1萬了。