Paxos的應用場景

以前介紹了viewservice,可是不能解決單點的問題。換句話說,若是viewserver crash, 那麼整個系統就癱瘓了。git

爲了解決這個問題,一個能夠想到的方法就是將單個的viewserver變爲多個,而多個server保持同步。而多個server之間的同步問題,涉及到分佈式系統中的一致性算法。本文介紹Paxos的概念及實現。github

關於Paxos算法,網上相關的文章不少,不過仍是建議看lamport的原文。原文經過不斷增強約束,一步步的獲得最後的結論,即:算法

For any v and n, if a proposal with value v and number n is issued,then there is a set S consisting of a majority of acceptors such that either (a) no acceptor in S has accepted any proposal numbered less than n, or (b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S .數據庫

咱們以一個分佈式的KV數據庫爲例,分析Paxos的應用場景。首先,假設數據庫對外提供3種操做架構

  • Put負載均衡

  • PutAppendless

  • Get分佈式

Figure-1

在這樣的一個架構下,經過多臺server組成集羣來避免單點問題。可是,如圖所示的3臺server必須保持同步。也就是說,若是Client向集羣發送請求 Put("a", 1)併成功,那麼整個集羣中任意一臺server必須都含有數據("a", 1).函數

這裏的Put("a", 1)就是lamport論文中proposer提出的value,這個稍後解釋。對於數據庫集羣而言,全部的操做都是串行的,就像如今的數據庫都提供日誌功能同樣,每一個操做都會記錄在日誌當中,並而是有前後順序的。若是咱們的集羣前後收到3個操做請求,分別爲spa

  • Put("a", 1)

  • Put("b", 2)

  • Put("c", 3)

那麼在對於數據庫集羣而言,應該有這麼一個表,相似爲

+---+-------------+
|1  |Put("a", 1)  |
+---+-------------+
|2  |Put("b", 2)  |
+---+-------------+
|3  |Put("c", 3)  |
+---+-------------+

咱們暫時稱它爲狀態表

這裏的標號1,2,3就對應論文中的Sequence。

下面咱們就假設數據庫集羣是空的,即沒有任何數據。客戶端發送了上面3個請求,固然,這中間可能存在多個客戶端同時發送請求的狀況。咱們看看整個流程是怎麼樣的。

首先,假設Client1向集羣發送了Put("a", 1), 這個請求雖然是發給集羣的,但實際上最後確定會落實到某一個具體的server,這個過程可能經過負載均衡等方法獲得,並非咱們關心的。咱們就假設這個請求發到了server1。這時server1是空的,因此他查詢本身的狀態表,發現Seq1沒有對應的操做,按理說他能夠將今天的第一個操做肯定爲Put("a", 1),但因爲它是集羣中的一份子,必需要協商一下。這個協商的過程,就是Paxos。他須要協商的問題是:

第1個操做是否能夠爲Put("a", 1)

這裏的協商過程咱們能夠理解爲一個函數, 函數的參數爲操做的序號和操做,返回值爲一個操做。

Op doPaxos(int seq, Op v){...}

這裏因爲只有一個Client提出的操做的請求,不存在競爭,因此協商很順利,當server1調用這個協商函數的時候,返回的值就是它傳入的值,此時咱們就能夠認爲集羣中的3臺server達成了一個共識,即集羣的第一個操做爲Put("a", 1)。因而server1將本身的狀態表改

+---+-------------+
|1  |Put("a", 1)  |
+---+-------------+

須要注意的是,在協商的過程當中,server2與server3也將本身的狀態表改成上面的樣子。這個狀態表同步的過程就是論文中的Learning階段。
可見,Paxos做爲一致性算法,在這裏例子當中保證的一致性其實就是狀態表的一致性。

有沒有協商不順利的狀況呢? 固然, 有!

這裏咱們假設有出現了兩個客戶端,Client2和Client3。接着用上面的例子,Client2向集羣發送了請求Put("b", 2),假設這個請求最終到了server1上。同時, Client3向集羣發送了請求Put("c", 3), 假設這個求情發到了server2上。

Figure-2

此時server1和server2的狀態表是同樣的,這一點是由Paxos保證的。他們的不一樣之處在於:

  • server1的數據和server2不一樣,即server1裏面包含一條數據,就是剛剛Client1請求的Put("a", 1), 可是server2是空的。

  • server1的sequence爲2, 可是server2的sequence仍然爲1。緣由是server1已經作了一次Put操做,可是server2什麼都沒有作。

因爲server1和server2的不一樣,所以他們處理各自請求的方式也不一樣。
先來看server2。server2從1開始遍歷本身的狀態表,發現1號操做非空,說明這是他遺漏的操做,因而server2也執行了操做Put("a", 1), 此時server2與server1的狀態徹底同樣了。在此以後,server2會向上面server1同樣,調用doPaxos函數進行協商,即詢問集羣中的全部server我們的第2個操做可否爲Put("c", 3)

doPaxos(2, v);

其中v爲 Put("c", 3)

但就在此時,server1也調用了doPaxos進行協商,詢問集羣中的全部server第2個操做可否爲Put("b", 2)

這裏集羣中兩個server提出了不一樣的議題(lamport的論文中也使用了這種說法),Paxos保證了最終會選出一個你們都贊成的議題。咱們這裏假設server1的議題最終被經過了。因而,集羣中全部server的狀態表都更新爲

+---+-------------+
|1  |Put("a", 1)  |
+---+-------------+
|2  |Put("b", 2)  |
+---+-------------+

而且server1向本身的database中插入("b", 2),並將本身的sequence更新爲3.

而此時server2發現本身的狀態表中sequence2有了對應的操做,但很惋惜不是本身提出的那個操做。這條信心代表集羣其實已經作了2個操做了,本身想要提出的這個操做Put("c", 3)已經不能做爲集羣的第2個操做了。因而server2對本身的database進行Put("b", 2)操做, 而後將本身的sequence更新爲3。再次調用doPaxos進行協商,試圖將本身的操做做爲集羣的第3個操做,因而doPaxos的參數爲

doPaxos(3, {Put("c", 3)})

此時因爲只有一個提議,因此協商很順利。此時集羣中全部server的狀態表都爲

+---+-------------+
|1  |Put("a", 1)  |
+---+-------------+
|2  |Put("b", 2)  |
+---+-------------+
|3  |Put("c", 3)  |
+---+-------------+

其中,server1包含2條數據,server2包含3條數據,server3爲空。

咱們假設此時Client1向集羣發送了Get("a")請求,並最終發到了server3。因爲server3的sequence爲, 因此他會從1開始,將本身狀態表中的1,2,3號操做就執行一遍,直到到了sequence4時,發現狀態表爲空,因而進行查詢操做,將結果返回Client。
因爲Get是隻讀操做,所以不必協商,也不必將其寫入狀態表。

示例代碼:https://github.com/qc1iu/haze-kv/tree/master/src/kvpaxos
原文來自 qc1iu的簡書

相關文章
相關標籤/搜索