在分佈式系統領域,有一個理論,對於分佈式系統的設計影響很是大,那就是 CAP 理論,即對於一個分佈式系統而言,它是沒法同時知足 Consistency(強一致性)、Availability(可用性) 和 Partition tolerance(分區容忍性) 這三個條件的,最多隻能知足其中兩個。但在實際中,因爲網絡環境是不可信的,因此分區容忍性幾乎是必不可選的,設計者基本就是在一致性和可用性之間作選擇,固然大部分狀況下,你們都會選擇犧牲一部分的一致性來保證可用性(可用性較差的系統很是影響用戶體驗的,可是對另外一些場景,好比支付場景,強一致性是必需要知足)。可是分佈式系統又沒法完全放棄一致性(Consistency),若是真的放棄一致性,那麼就說明這個系統中的數據根本不可信,數據也就沒有意義,那麼這個系統也就沒有任何價值可言。html
CAP 理論
CAP 理論三個特性的詳細含義以下:算法
- 一致性(Consistency):每次讀取要麼是最新的數據,要麼是一個錯誤;
- 可用性(Availability):client 在任什麼時候刻的讀寫操做都能在限定的延遲內完成的,即每次請求都能得到一個響應(非錯誤),但不保證是最新的數據;
- 分區容忍性(Partition tolerance):在大規模分佈式系統中,網絡分區現象,即分區間的機器沒法進行網絡通訊的狀況是必然會發生的,系統應該能保證在這種狀況下能夠正常工做。
分區容忍性
不少人可能對分區容忍性不太理解,知乎有一個回答對這個解釋的比較清楚(CAP理論中的P究竟是個什麼意思?),這裏引用一下:shell
- 一個分佈式系統裏面,節點組成的網絡原本應該是連通的。然而可能由於一些故障,使得有些節點之間不連通了,整個網絡就分紅了幾塊區域。數據就散佈在了這些不連通的區域中。這就叫分區。
- 當你一個數據項只在一個節點中保存,那麼分區出現後,和這個節點不連通的部分就訪問不到這個數據了。這時分區就是沒法容忍的。
- 提升分區容忍性的辦法就是一個數據項複製到多個節點上,那麼出現分區以後,這一數據項就可能分佈到各個區裏,容忍性就提升了。
- 然而,要把數據複製到多個節點,就會帶來一致性的問題,就是多個節點上面的數據多是不一致的。
- 要保證一致,每次寫操做就都要等待所有節點寫成功,而這等待又會帶來可用性的問題。
- 總的來講就是,數據存在的節點越多,分區容忍性越高,但要複製更新的數據就越多,一致性就越難保證。爲了保證一致性,更新全部節點數據所須要的時間就越長,可用性就會下降。
CAP 如何選擇
CAP 理論一個經典原理以下所示:segmentfault
CAP 定理代表,在存在網絡分區的狀況下,一致性和可用性必須二選一。而在沒有發生網絡故障時,即分佈式系統正常運行時,一致性和可用性是能夠同時被知足的。可是,對於大多數互聯網應用來講,由於規模比較大,部署節點分散,網絡故障是常態,可用性是必需要保證的,因此只有捨棄一致性來保證服務的 AP。可是對於一些金融相關行業,它有不少場景須要確保一致性,這種狀況一般會權衡 CA 和 CP 模型,CA 模型網絡故障時徹底不可用,CP 模型具有部分可用性。網絡
在一個分佈式系統中,對於這三個特性,咱們只能三選二,沒法同時知足這三個特性,三選二的組合以及這樣系統的特色總結以下(來自左耳朵耗子推薦:分佈式系統架構經典資料):架構
- CA (Consistency + Availability):關注一致性和可用性,它須要很是嚴格的全體一致的協議,好比「兩階段提交」(2PC)。CA 系統不能容忍網絡錯誤或節點錯誤,一旦出現這樣的問題,整個系統就會拒絕寫請求,由於它並不知道對面的那個結點是否掛掉了,仍是隻是網絡問題。惟一安全的作法就是把本身變成只讀的。
- CP (consistency + partition tolerance):關注一致性和分區容忍性。它關注的是系統裏大多數人的一致性協議,好比:Paxos 算法 (Quorum 類的算法)。這樣的系統只須要保證大多數結點數據一致,而少數的結點會在沒有同步到最新版本的數據時變成不可用的狀態。這樣可以提供一部分的可用性。
- AP (availability + partition tolerance):這樣的系統關心可用性和分區容忍性。所以,這樣的系統不能達成一致性,須要給出數據衝突,給出數據衝突就須要維護數據版本。Dynamo 就是這樣的系統。
對於分佈式系統分區容忍性是自然具有的要求,不然一旦出現網絡分區,系統就拒絕全部寫入只容許可讀,這對大部分的場景是不可接收的,所以,在設計分佈式系統時,更多的狀況下是選舉 CP 仍是 AP,要麼選擇強一致性弱可用性,要麼選擇高可用性容忍弱一致性。分佈式
一致性模型
關於分佈式系統的一致性模型有如下幾種:post
強一致性
當更新操做完成以後,任何多個後續進程或者線程的訪問都會返回最新的更新過的值,直到這個數據被其餘數據更新爲止。性能
可是這種實現對性能影響較大,由於這意味着,只要上次的操做沒有處理完,就不能讓用戶讀取數據。
弱一致性
系統並不保證進程或者線程的訪問都會返回最新更新過的值。系統在數據寫入成功以後,不承諾當即能夠讀到最新寫入的值,也不會具體的承諾多久以後能夠讀到。但會盡量保證在某個時間級別(好比秒級別)以後,可讓數據達到一致性狀態。
最終一致性
最終一致性也是弱一致性的一種,它沒法保證數據更新後,全部後續的訪問都能看到最新數值,而是須要一個時間,在這個時間以後能夠保證這一點,而在這個時間內,數據也許是不一致的,這個系統沒法保證強一致性的時間片斷被稱爲「不一致窗口」。不一致窗口的時間長短取決於不少因素,好比備份數據的個數、網絡傳輸延遲速度、系統負載等。
最終一致性在實際應用中又有多種變種:
類型 | 說明 |
---|---|
因果一致性 | 若是 A 進程在更新以後向 B 進程通知更新的完成,那麼 B 的訪問操做將會返回更新的值。而沒有因果關係的 C 進程將會遵循最終一致性的規則(C 在不一致窗口內仍是看到是舊值)。 |
讀你所寫一致性 | 因果一致性的特定形式。一個進程進行數據更新後,會給本身發送一條通知,該進程後續的操做都會以最新值做爲基礎,而其餘的進程仍是隻能在不一致窗口以後才能看到最新值。 |
會話一致性 | 讀你所寫一致性的特定形式。進程在訪問存儲系統同一個會話內,系統保證該進程能夠讀取到最新之,但若是會話終止,從新鏈接後,若是此時還在不一致窗口內,仍是可嫩讀取到舊值。 |
單調讀一致性 | 若是一個進程已經讀取到一個特定值,那麼該進程不會讀取到該值之前的任何值。 |
單調寫一致性 | 系統保證對同一個進程的寫操做串行化。 |
它們的關係又以下圖所示(圖來自 《大數據日知錄:架構與算法》):
分佈式一致性協議
爲了解決分佈式系統的一致性問題,在長期的研究探索過程當中,業內涌現出了一大批經典的一致性協議和算法,其中比較著名的有二階段提交協議(2PC),三階段提交協議(3PC)和 Paxos 算法(本文暫時先不介紹)。
Google 2009年 在Transaction Across DataCenter 的分享中,對一致性協議在業內的實踐作了一簡單的總結,以下圖所示,這是 CAP 理論在工業界應用的實踐經驗。
其中,第一行表頭表明了分佈式系統中通用的一致性方案,包括冷備、Master/Slave、Master/Master、兩階段提交以及基於 Paxos 算法的解決方案,第一列表頭表明了分佈式系統你們所關心的各項指標,包括一致性、事務支持程度、數據延遲、系統吞吐量、數據丟失可能性、故障自動恢復方式。
兩階段提交協議(2PC)
二階段提交協議(Two-phase Commit,即2PC)是經常使用的分佈式事務解決方案,它能夠保證在分佈式事務中,要麼全部參與進程都提交事務,要麼都取消事務,即實現 ACID 的原子性(A)。在數據一致性中,它的含義是:要麼全部副本(備份數據)同時修改某個數值,要麼都不更改,以此來保證數據的強一致性。
2PC 要解決的問題能夠簡單總結爲:在分佈式系統中,每一個節點雖然能夠知道本身的操做是成功仍是失敗,倒是沒法知道其餘節點的操做狀態。當一個事務須要跨越多個節點時,爲了保持事務的 ACID 特性,須要引入一個做爲協調者的組件來統一掌控全部節點(參與者)的操做結果並最終指示這些節點是否要把操做結果進行真正的提交(好比將更新後的數據寫入磁盤等等)。所以,二階段提交的算法思路能夠歸納爲: 參與者將操做結果通知協調者,再由協調者根據全部參與者的反饋情報決定各參與者是否要提交操做仍是停止操做。
2PC 過程
關於兩階段提交的過程以下圖所示:
顧名思義,2PC 分爲兩個過程:
- 表決階段:此時 Coordinator (協調者)向全部的參與者發送一個 vote request,參與者在收到這請求後,若是準備好了就會向 Coordinator 發送一個
VOTE_COMMIT
消息做爲迴應,告知 Coordinator 本身已經作好了準備,不然會返回一個VOTE_ABORT
消息; - 提交階段:Coordinator 收到全部參與者的表決信息,若是全部參與者一致認爲能夠提交事務,那麼 Coordinator 就會發送
GLOBAL_COMMIT
消息,不然發送GLOBAL_ABORT
消息;對於參與者而言,若是收到GLOBAL_COMMIT
消息,就會提交本地事務,不然就會取消本地事務。
2PC 一致性問題
這裏先討論一下,2PC 是否能夠在任何狀況下均可以解決一致性問題,在實際的網絡生產中,各類狀況都有可能發生,這裏,咱們先從理論上分析各類意外狀況。
2PC 在執行過程當中可能發生 Coordinator 或者參與者忽然宕機的狀況,在不一樣時期宕機可能有不一樣的現象。
狀況 | 分析及解決方案 |
---|---|
Coordinator 掛了,參與者沒掛 | 這種狀況其實比較好解決,只要找一個 Coordinator 的替代者。當他成爲新的 Coordinator 的時候,詢問全部參與者的最後那條事務的執行狀況,他就能夠知道是應該作什麼樣的操做了。因此,這種狀況不會致使數據不一致。 |
參與者掛了(沒法恢復),Coordinator 沒掛 | 若是掛了以後沒有恢復,那麼是不會致使數據一致性問題。 |
參與者掛了(後來恢復),Coordinator 沒掛 | 恢復後參與者若是發現有未執行完的事務操做,直接取消,而後再詢問 Coordinator 目前我應該怎麼作,協調者就會比對本身的事務執行記錄和該參與者的事務執行記錄,告訴他應該怎麼作來保持數據的一致性。 |
還有一種狀況是:參與者掛了,Coordinator 也掛了,須要再細分爲幾種類型來討論:
狀況 | 分析及解決方案 |
---|---|
Coordinator 和參與者在第一階段掛了 | 因爲這時尚未執行 commit 操做,新選出來的 Coordinator 能夠詢問各個參與者的狀況,再決定是進行 commit 仍是 roolback。由於尚未 commit,因此不會致使數據一致性問題。 |
Coordinator 和參與者在第二階段掛了,可是掛的這個參與者在掛以前尚未作相關操做 | 這種狀況下,當新的 Coordinator 被選出來以後,他一樣是詢問全部參與者的狀況。只要有機器執行了 abort(roolback)操做或者第一階段返回的信息是 No 的話,那就直接執行 roolback 操做。若是沒有人執行 abort 操做,可是有機器執行了 commit 操做,那麼就直接執行 commit 操做。這樣,當掛掉的參與者恢復以後,只要按照 Coordinator 的指示進行事務的 commit 仍是 roolback 操做就能夠了。由於掛掉的機器並無作 commit 或者 roolback 操做,而沒有掛掉的機器們和新的 Coordinator 又執行了一樣的操做,那麼這種狀況不會致使數據不一致現象。 |
Coordinator 和參與者在第二階段掛了,掛的這個參與者在掛以前已經執行了操做。可是因爲他掛了,沒有人知道他執行了什麼操做。 | 這種狀況下,新的 Coordinator 被選出來以後,若是他想負起 Coordinator 的責任的話他就只能按照以前那種狀況來執行 commit 或者 roolback 操做。這樣新的 Coordinator 和全部沒掛掉的參與者就保持了數據的一致性,咱們假定他們執行了 commit。可是,這個時候,那個掛掉的參與者恢復了怎麼辦,由於他已經執行完了以前的事務,若是他執行的是 commit 那還好,和其餘的機器保持一致了,萬一他執行的是 roolback 操做呢?這不就致使數據的不一致性了麼?雖然這個時候能夠再經過手段讓他和 Coordinator 通訊,再想辦法把數據搞成一致的,可是,這段時間內他的數據狀態已是不一致的了! |
因此,2PC協議中,若是出現協調者和參與者都掛了的狀況,有可能致使數據不一致。爲了解決這個問題,衍生出了3PC。
2PC 優缺點
簡單總結一下 2PC 的優缺點:
- 優勢:原理簡潔清晰、實現方便;
- 缺點:同步阻塞、單點問題、某些狀況可能致使數據不一致。
關於這幾個缺點,在實際應用中,都是對2PC 作了相應的改造:
- 同步阻塞:2PC 有幾個過程(好比 Coordinator 等待全部參與者表決的過程當中)都是同步阻塞的,在實際的應用中,這可能會致使長阻塞問題,這個問題是經過超時判斷機制來解決的,但並不能徹底解決同步阻塞問題;
- Coordinator 單點問題:實際生產應用中,Coordinator 都會有相應的備選節點;
- 數據不一致:這個在前面已經講述過了,若是在第二階段,Coordinator 和參與者都出現掛掉的狀況下,是有可能致使數據不一致的。
三階段提交協議(3PC)
三階段提交協議(Three-Phase Commit, 3PC)最關鍵要解決的就是 Coordinator 和參與者同時掛掉致使數據不一致的問題,因此 3PC 把在 2PC 中又添加一個階段,這樣三階段提交就有:CanCommit、PreCommit 和 DoCommit 三個階段。
3PC 過程
三階段提交協議的過程以下圖(圖來自 維基百科:三階段提交)所示:
3PC 的詳細過程以下(這個過程步驟內容來自 2PC到3PC到Paxos到Raft到ISR):
階段一 CanCommit
- 事務詢問:Coordinator 向各參與者發送 CanCommit 的請求,詢問是否能夠執行事務提交操做,並開始等待各參與者的響應;
- 參與者向 Coordinator 反饋詢問的響應:參與者收到 CanCommit 請求後,正常狀況下,若是自身認爲能夠順利執行事務,那麼會反饋 Yes 響應,並進入預備狀態,不然反饋 No。
階段二 PreCommit
執行事務預提交:若是 Coordinator 接收到各參與者反饋都是Yes,那麼執行事務預提交:
- 發送預提交請求:Coordinator 向各參與者發送 preCommit 請求,並進入 prepared 階段;
- 事務預提交:參與者接收到 preCommit 請求後,會執行事務操做,並將 Undo 和 Redo 信息記錄到事務日記中;
- 各參與者向 Coordinator 反饋事務執行的響應:若是各參與者都成功執行了事務操做,那麼反饋給協調者 ACK 響應,同時等待最終指令,提交 commit 或者終止 abort,結束流程;
中斷事務:若是任何一個參與者向 Coordinator 反饋了 No 響應,或者在等待超時後,Coordinator 沒法接收到全部參與者的反饋,那麼就會中斷事務。
- 發送中斷請求:Coordinator 向全部參與者發送 abort 請求;
- 中斷事務:不管是收到來自 Coordinator 的 abort 請求,仍是等待超時,參與者都中斷事務。
階段三 doCommit
執行提交
- 發送提交請求:假設 Coordinator 正常工做,接收到了全部參與者的 ack 響應,那麼它將從預提交階段進入提交狀態,並向全部參與者發送 doCommit 請求;
- 事務提交:參與者收到 doCommit 請求後,正式提交事務,並在完成事務提交後釋放佔用的資源;
- 反饋事務提交結果:參與者完成事務提交後,向 Coordinator 發送 ACK 信息;
- 完成事務:Coordinator 接收到全部參與者 ack 信息,完成事務。
中斷事務:假設 Coordinator 正常工做,而且有任一參與者反饋 No,或者在等待超時後沒法接收全部參與者的反饋,都會中斷事務
- 發送中斷請求:Coordinator 向全部參與者節點發送 abort 請求;
- 事務回滾:參與者接收到 abort 請求後,利用 undo 日誌執行事務回滾,並在完成事務回滾後釋放佔用的資源;
- 反饋事務回滾結果:參與者在完成事務回滾以後,向 Coordinator 發送 ack 信息;
- 中斷事務:Coordinator 接收到全部參與者反饋的 ack 信息後,中斷事務。
3PC 分析
3PC 雖然解決了 Coordinator 與參與者都異常狀況下致使數據不一致的問題,3PC 依然帶來其餘問題:好比,網絡分區問題,在 preCommit 消息發送後忽然兩個機房斷開,這時候 Coordinator 所在機房會 abort, 另外剩餘參與者的機房則會 commit。
並且因爲3PC 的設計過於複雜,在解決2PC 問題的同時也引入了新的問題,因此在實際上應用不是很普遍。
參考: