什麼是 CAP
關於 CAP 理論的背景介紹已經很多,這裡不過多介紹,我們談談如何理解它的問題。
用通俗易懂的話解釋三個名詞:
一致性
如果剛剛向一個節點寫入,那麼之後,從另外一個節點讀取的必須是剛剛寫入的資料,不能是更老的資料。
可用性
如果請求一個節點,這個節點必須能夠給予回覆,如果節點掛掉了,那就談不上可用性了。
分割槽容忍性
是否容忍網路分割槽,即可以允許節點和其它節點無法通訊。
CAP 的意思就是說我們最多隻能保證其中兩個條件同時成立。
下面我們來看看為什麼。
如圖所示,假如我們滿足了分割槽容忍性,即虛線處表示兩個節點發生了分割槽。
假如要滿足一致性,那麼,我們只能讓請求另一個節點的操作暫時 hang 住,返回 client 失敗或者超時的結果,這種情況多發生在銀行櫃檯等對資料一致性要求很高的情境下,因為比起保證使用者資金數目的正確性比暫時讓使用者無法操作要更重要一些。
假如要滿足可用性,因為網路已經隔離,也就沒辦法達到一致性,這種情況多發生在網際網路行業中,比如新聞等對資料一致性要求不高但對可用性要求高的情況下,畢竟,使用者壓根看不了新聞比看不到及時新聞要重要的多。
大家可以自己自由組合,最終會證明,三種條件不可能同時滿足,其實大部分情況下,我們都是在一致性和可用性之間取捨而已。
Consistency = Consensus?
Consistency 幾乎被業界用爛,以至於當我們在討論一致性的時候,其實我們都無法確定對方所說的一致性是不是和自己的那個一致。
Consistency:一致性,Consensus:協同,這兩個概念極容易混淆。
我們常說的一致性(Consistency)在分散式系統中指的是對於同一個資料的多個副本,其對外表現的資料一致性,如線性一致性、因果一致性、最終一致性等,都是用來描述副本問題中的一致性的。而共識(Consensus)則不同,簡單來說,共識問題是要經過某種演算法使多個節點達成相同狀態的一個過程。在我看來,一致性強調結果,共識強調過程。
共識?狀態機?
共識有個更高逼格的稱呼:
基於狀態機複製的共識演算法
那麼,狀態機是什麼?
狀態機是有限狀態自動機的簡稱,是現實事物執行規則抽象而成的一個數學模型。
看下圖,門,有兩種狀態,開著的和關著的。因此,在我看來狀態是一種靜態的場景,而轉換賦予了其動態的變化。
以此類比一下,如果一個節點當前的資料是 X,現在有了 add+1 的操作日誌來了,那麼現在的狀態就是 X+1,好了,狀態(X)有了,變化(操作日誌)有了,這就是狀態機。
分散式共識,簡單來說,就是在一個或多個節點提議了一個狀態應當是什麼後,系統中所有節點對這個狀態達成一致意見的整個過程。
共識是過程,一致是結果。
共識模型
主從同步:
我們都知道 MySQL 等業界常見資料庫的主從同步(Master-Slave Replication),主從同步分三個階段:
Master 接受寫請求
Master 複製日誌至 Slave
Master 等待,直到所有從庫返回。
可見,主從同步模型存在致命問題:只要一個節點失敗,則 Master 就會阻塞,導致整個叢集不可用,保證了一致性,可用性缺大大降低了。
多數派:
每次寫入大於 N/2 個節點,每次讀也保證從 N/2 個節點中讀。多數派的模型看似完美解決了多節點的一致性問題,不就是效能差點嘛,可是在併發的情況下就不一定了,如下圖:
在併發環境下,因為每個節點的操作日誌寫入順序無法保證一致,也就無法保證最終的一致性。如圖,都是向三個節點
inc5、set0 兩個操作,但因為順序不一樣,最終狀態兩個是 0,一個是 5。因此我們需要引入一種機制,給每個操作日誌編上號,這個號從小到大生成,這樣,每個節點就不會弄錯。(是否想到了網路中的分包與重組?)那麼,現在關鍵問題又來了,怎麼協同這個編號?貌似這是雞生蛋、蛋生雞的問題。
Paxos
Paxos 模型試圖探討分散式共識問題的一個更一般的形式。
Lesile Lamport,Latex 的發明者,提出了 Paxos 演算法。他虛擬了一個叫做 Paxos 的希臘城邦,這個島按照議會民主制的政治模式制定法律,但是沒有人願意將自己的全部時間和精力放在這件事上。所以無論是議員、議長或者傳遞紙條的服務員都不能承諾別人需要時一定會出現,也無法承諾批准決議後者傳遞訊息的時間。由於 Paxos 讓人太難以理解,Lamport 覺得同行不能理解他的幽默感,於是後來又重新發表了樸實的演算法描述版本《Paxos Made Simple》。
該共識演算法就整體來說,存在兩個階段,如圖,第一個階段是提議,第二個階段是決定。
分散式系統要做到 fault tolorence,就需要共識模型,而節點達成共識,不僅需要節點之間的演算法,還會取決於 client 的行為。比如即使副本系統使用 multi-paxos 在所有副本伺服器上同步了日誌序號,但如果 Client 被允許從非 Leader 節點寫入資料,則整個副本系統仍然不是強一致的。
下面,重頭戲來了,詳細介紹 Paxos。
角色介紹:
Client:系統外部角色,請求發起者。如民眾。
Proposer: 接受 Client 請求,向叢集提出提議(propose)。並在衝突發生時,起到衝突調節的作用。如議員,替民眾提出議案。
Accpetor: 提議投票和接收者,只有在形成法定人數(Quorum,即 Majority 多數派)時,提議才會最終被接受。如國會。
Learner: 提議接受者,backup,備份,對叢集的一致性沒影響。如記錄員。
步驟、階段:
1.Phase1a: Prepare
proposer 提出一個議案,編號為 N,N 一定大於這個 proposer 之前提出的提案編號,請求 acceptor 的 quorum(大多數) 接受。
2.Phase1b: Promise
如果 N 大於此 acceptor 之前接受的任何提案編號則接受,否則拒絕。
3.Phase2a: Accept
如果達到了多數派,proposer 會發出 accept 請求,此請求包含上一步的提案編號和提案內容。
4.Phase2b: Accepted
如果此 acceptor 在此期間沒有收到任何大於 N 的提案,則接受此提案內容,否則忽略。
還記得上文中我們提到過,同步編號是非常重要的問題,綠色框出來的實際上就是同步編號的過程。透過這個編號,就知道每條操作日誌的先後順序。簡單說來,第一階段,獲取編號,第二階段,寫入日誌。可以看出來,Paxos 透過兩輪互動,犧牲時間和效能來達到彌補一致性的問題。
現在我們考慮部分節點 down 掉的情景。
由於是多數派 accptor 達成了一致,第一階段仍然成功獲得了編號,所有最終還是成功的。
考慮 proposer down 掉的情景。
沒關係,雖然第一個 proposer 失敗,但下一個 proposer 用更大的提案編號,所以下一次 proposer 最終還是成功了,仍然保證了可用性和一致性。
潛在問題:活鎖
Paxos 存在活鎖問題。如圖,當 第一個 proposer 在第一階段發出請求,還沒來得及後續的第二階段請求,緊接著第二個 proposer 在第一階段也發出請求,如果這樣無窮無盡,acceptor 始終停留在決定順序號的過程上,那大家誰也成功不了,遇到這樣的問題,其實很好解決,如果發現順序號被新的 proposer 更新了,則引入一個隨機的等待的超時時間,這樣就避免了多個 proposer 發生衝突的問題。
Multi Paxos
由於 Paxos 存在的問題:難實現、效率低(2 輪 rpc)、活鎖。
因此又引入了 Multi Paxos,Multi Paxos 引入 Leader,也就是唯一的 proposer,所有的請求都需經過此 Leader。
因為只有一個節點維護提案編號,這樣,就省略了第一輪討論提議編號的過程。
然後進一步簡化角色。
Servers 中第左邊的就是 Proposer,另外兩個和自身充當 acceptor,這樣就更像我們真實的系統了。Raft 和 ZAB 協議其實基本上和這個一致,兩者的差別很小,就是心跳的方向不一樣。
Raft 和 ZAB
Raft 和 ZAB 協議將 Multi Paxos 劃分了三個子問題:
Leader Election
Log Replication
Safety
在 leader 選舉的過程中,也重定義角色:
Leader
Follower
Candidate
這個動畫網站生動展示了 leader 選舉和日誌複製的過程。在這裡就不多講了。
另外,raft 官方網站可以自己操作模擬選舉的過程。
總結
今天,我們從 CAP 談到 Raft 和 ZAB,中間穿插了各種名詞,模型無論怎麼變化,我們始終只有一個目的,那就是在一個
fault torlerance 的分散式架構下,如何儘量保證其整個系統的可用性和一致性。最理想的模型當然是 Paxos,然而理論到落地還是有差距的,所以誕生了 Raft 和 ZAB,雖然只有一個 leader,但我們允許 leader 掛掉可以重新選舉 leader,這樣,中心式和分散式達到了一個妥協。
參考資料:
http://blog.kongfy.com/2016/08/被誤用的一致性/
http://blog.kongfy.com/2016/05/分散式共識 consensus:viewstamped、raft 及 paxos/