1. 程式人生 > >P2P結構與Quorum機制------《Designing Data-Intensive Applications》讀書筆記8

P2P結構與Quorum機制------《Designing Data-Intensive Applications》讀書筆記8

服務器 遠的 數據系統 接收 圖片 次數 小結 概念 覆蓋

前文涉及到了很多與Leader相關的算法,大家有木有想過,王侯將相,寧有種乎,既然Leader這麽麻煩,幹脆還是采用P2P模型吧,來個大家平等的架構。本篇需要和大家探討的就是多副本下實現民主政治的Quorum機制。至於它是怎麽樣解決我們在前文提及的各種問題的,接著這篇文章我們繼續聊聊~~

1. No-Leader機制

有些數據存儲系統放棄了Leader的機制,允許任何副本直接接受用戶的寫操作。(如Amazon的Dynamo,FaceBook的Cassandra,雖然最終FaceBook放棄了Cassandra轉而支持Hbase,但是Uber的強勢介入讓Cassandra後來在開源社區大放異彩。

) 每個接受到客戶端寫請求的節點會轉換為一個協調器節點,而協調器節點不強制執行特定的寫入順序。正是這種設計上的差異對數據庫的使用方式與數據模型產生了深遠的影響。

多副本的讀寫

No-Leader機制是怎麽樣消除Leader這個角色的存在的呢?答案也很簡單:多副本讀寫。接下來我們來看一個栗子:

假設我們在數據系統之中采用了三副本的結構,如下圖所示:User 1234 並行地將所有的副本發送給三個存儲節點,並且兩個節點可以接受副本的寫入,但是其中一個節點不在線,所以副本寫入失敗。所以在三個副本中有兩個副本確認寫入成功了:在User 1234收到兩個OK響應之後,User就認為寫入操作是成功的,忽略了一個副本寫入失敗。(當然,不是簡單的就不管這個寫入失敗了,後續會有修復機制來補齊這個副本的數據

技術分享圖片

現在假設User 2345開始讀取新寫入的數據。由於一個節點寫入失敗了,所以User 234 可能會得到過期的值作為響應。為了解決這個問題,當User 從數據系統之中讀取數據時,它不只是將請求發送到一個副本,而是將讀取請求並行地發送到多個副本節點。User可以從不同節點獲得不同的響應,即來自其他節點的最新值和另一個節點的過期值。這裏通過了版本號用於確定哪個值是更新的值。

副本修復

No-Leader機制導致了數據系統之中可能存在大量過期的值,所以一個節點怎麽來修復自身的副本來獲取最新值的過程我們就稱之為副本修復,No-Leader機制也是通過這樣的方式來達到最終一致性的。通常會有這樣幾種方式:

  • 讀修復
    當用戶並行讀取多個節點時,它可以獲取到其他過期的值的響應。所以用戶會發現其中有些節點擁有過期的值,這時用戶可以主動將新值寫入該節點。這種方法稱之為讀修復

  • 反熵過程(其實是一個物理學概念)
    每個數據存儲節點都會有一個後臺進程,不斷的比對自己的副本與其他節點副本的差異,發現自己擁有過期的值之後,會主動修復自己過期的副本。與基於寫入順序日誌不同,這種反熵過程不以任何特定的順序復制寫操作,並且在復制數據之前可能會有顯著的延遲。

2. Quorum機制

上文之中提及的例子在三個副本中的兩個之上寫入成功,我們認為寫操作成功了。但是如果三個副本只有的一個副本寫入成功了?這時的寫操作是否是成功的呢?

答案是否定的?這裏其實就是簡單的鴿巢原理,這裏我不做數學證明了,大家有興趣的可以自行證明一下。
假設有n個副本,每次寫操作必須由w個節點確認為成功,每個讀操作讀取r個節點。(在上文的例子中,n=3,w=2,r=2)。只要w + r > n,如果讀和寫操作的總次數大於n,那麽讀和寫操作必然至少有一個副本是相同的,也就是讀操作必然可以讀到最新寫操作的數據。這被我們稱之為:Quorum機制,每次讀寫都需要達到法定人數。

通常 n、w和r通常是可配置的,根據您的需要來修改這些數字。一個常見的選擇是使n為奇數(通常為3或5),並設置w=r=(n + 1)/ 2 。如下圖所示,如果w < n,如果有n - w個節點不可用,我們仍然可以處理寫操作。同樣的如果r<n,如果有n - r個節點不可用,我們仍然可以處理讀操作。而如果小於所需的w或r節點可用,則寫或讀操作就會返回錯誤。

  • n=3,w=2,r=2,我們可以容忍一個不可用的節點。
  • n=5,w=3,r=3,我們可以容忍兩個不可用的節點。

技術分享圖片

高可用與Hinted handoff

Quorum機制實現了最終一致性的模型,但是在可用性上還是有一些極端情況,沒法很好處理。如:出現網絡抖動時,但是可能系統仍然有許多正常工作的節點。但是副本應該被寫入的n個節點發生網絡問題,導致了會少於w或r個成功的讀寫操作,由於不能達到法定的人數,讀寫操作都會失敗。所以這時候數據庫系統的設計者面臨權衡取舍,能不能通過一些機制,實現更好的可用性呢?

所以這種情況下,我們就可以利用Hinted handoff了(原諒我翻譯不好)。這種方式是怎麽樣實現的呢?寫和讀操作仍然需要w和r成功的響應,但是可以不強制一定要寫如指定的n個節點 (這個涉及到一致性哈希,數據分布的知識,暫時要是理解不了,我後續會有專門的專題來寫這個內容,可以先放一放。) 打個比方說,如果你把自己鎖在門外,你可能會敲鄰居的門,問你是否可以暫時呆在他們的沙發上,一旦你找到鑰匙了,你就自己回家了。所以其他節點可以暫存本應該放在另一個節點上的副本,一旦網絡中斷被修復,其他節點就會把副本轉交給主人節點。

所以這種模式既保證了不違反Quorum機制,也大大提高了系統的可用性,被No-leader數據系統廣泛采用。

3 寫入沖突與Quorum機制

同樣的Quorum機制的設計本身就可以允許並發讀寫操作,並容忍網絡中斷與高峰延遲。但是這也必然會帶來一致性問題,我們來看下面這個例子:

如圖所示,有兩個Client A與B,同時寫入關鍵字X在一個三副本的數據存儲系統之中。Node 1接收來自A的寫入,但由於網絡中斷而從未接收來自B的寫入。Node 2首先接收來自A的寫入,然後接收B寫入。而Node 3則是首先接收來自B的寫入,然後接收A的寫入。Node 2認為X的最終值是B,而其他Node認為最終值是A.
技術分享圖片

在這樣的場景下如何仲裁寫入結果成為了一個大問題,思路和我們之前提到的類型:

  • Last Write Win
    我們可以為每個寫操作附加一個時間戳,選擇最大的時間戳作為最新的值,並丟棄任何具有早期時間戳的寫操作的值。這種沖突解決算法,稱為Last Write Win。這種情況要求每個寫操作具有冪等性,否則會出現寫丟失的情況,如何能保證不出現依賴的寫丟失呢?

  • 合並“happens-before”關系
    每當有兩個操作A和B時,有三種可能:A發生在B之前,B發生在A之前,A或B是並發的。我們需要的是一個算法,告訴我們兩個操作是否並發。如果一個操作在另一個操作之前發生,那麽後面的操作應該覆蓋前面的操作,但是如果操作是並行的,那麽我們需要解決一個沖突。怎麽樣去捕獲並合並“happen-before”的關系呢?可以在服務器節點維護一個版本號,每次寫操作時遞增版本號,並將新版本號存儲在寫入的值中。
    • 客戶端
      當客戶端讀取一個鍵時,服務節點會返回所有未被覆蓋的值,以及最新版本號。當客戶端需要寫一個鍵時,它必須包含從先前讀取中的版本號,並且它必須合並它在前面讀取中接收到的所有值。
    • 服務器
      當服務器接收到具有特定版本號的寫入時,它可以覆蓋該版本號或以下的所有值,因為它知道已經合並到新值,但必須保留所有值具有更高版本號。
  • 版本向量

合並“happen-before"使用一個單一的版本號來捕捉操作之間的依賴關系,但這不足以解決當有多個副本並行寫入的情況。相反,我們需要使用每個副本的版本號以及每個鍵。每個副本在處理寫時遞增自己的版本號,並跟蹤從其他副本中看到的版本號。此信息指示要覆蓋哪些值以及作為兄弟版本保存著哪些值。而所有副本的版本號的集合稱為版本向量。版本向量從數據節點發送給客戶端,所以版本向量讓我們可以區分覆蓋寫與發並行寫操作。

4. 小結

好了,到此為止我們終於總結完整了分布式系統之中的副本機制。從Leader-Follower 機制到多Leader機制,最後到No-Leader的機制,並且詳細總結了各個機制的實現細節與優缺點,希望大家閱讀完之後也能有所收獲。

P2P結構與Quorum機制------《Designing Data-Intensive Applications》讀書筆記8