分佈式一致性算法Paxos介紹

Github 系列文章地址git

Introduction

Google Chubby的做者Mike Burrows說過這個世界上只有一種一致性算法,那就是Paxos,其它的算法都是殘次品。github

PAXOS能夠用來解決分佈式環境下,選舉(或設置)某一個值的問題(好比更新數據庫中某個user的age是多少)。分佈式系統中有多個節點就會存在節點間通訊的問題,存在着兩種節點通信模型:共享內存(Shared memory)、消息傳遞(Messages passing),Paxos是基於消息傳遞的通信模型的。它的假設前提是,在分佈式系統中進程之間的通訊會出現丟失、延遲、重複等現象,但不會出現傳錯的現象。Paxos算法就是爲了保證在這樣的系統中進程間基於消息傳遞就某個值達成一致。算法

注意,在Paxos算法中是不考慮拜占庭將軍問題的數據庫

Scenario:場景介紹

好比下面的狀況:有三個服務器進程A,B,C運行在三臺主機上提供數據庫服務,當一個client鏈接到任何一臺服務器上的時候,都能經過該服務器進行read和update的操做。首先先看一個A、B、C不那麼均等的方法:promise

A、B、C選舉主機名最大的服務器爲master提供服務,全部的read、update操做都發生在它上面,其他的兩臺服務器只是slave(後者,在這裏相似proxy),當client是鏈接在master的服務器的時候,其直接和master交互,就相似於單機的場景。當client時鏈接在slave的時候,全部的請求被轉移到了master上進行操做,而後再結果返回給client。若是master掛了,那麼剩餘兩臺主機在檢測到master掛的狀況後,再根據主機名最大的方法選舉master。服務器

上面的算法有一個問題,它讓A、B、C不那麼均等了,A、B、C存在角色之分,且在某個時候master掛機後,須要馬上選舉出新的master提供服務。同時,這個算法還要求各個服務器之間保持心跳。而PAXOS算法則不一樣,PAXOS提供了一種將A、B、C等價的方式提供上面的操做,並保證數據的正確性。在某臺主機宕機後,只要總數一半以上的服務器還存活,則整個集羣依然能對外提供服務,甚至不須要心跳。網絡

Reference

算法原理

Terminology:名詞解釋

爲描述Paxos算法,Lamport虛擬了一個叫作Paxos的希臘城邦,這個島按照議會民主制的政治模式制訂法律,可是沒有人願意將本身的所有時間和精力放在這種事情上。因此不管是議員,議長或者傳遞紙條的服務員都不能承諾別人須要時必定會出現,也沒法承諾批准決議或者傳遞消息的時間。可是這裏假設沒有拜占庭將軍問題(Byzantine failure,即雖然有可能一個消息被傳遞了兩次,可是絕對不會出現錯誤的消息);只要等待足夠的時間,消息就會被傳到。另外,Paxos島上的議員是不會反對其餘議員提出的決議的。

首先將議員的角色分爲proposers,Acceptors,和learners(容許身兼數職)。proposers提出提案,提案信息包括提案編號和提議的value;Acceptor收到提案後能夠接受(accept)提案,若提案得到多數Acceptors的接受,則稱該提案被批准(chosen);learners只能「學習」被批准的提案。劃分角色後,就能夠更精確的定義問題:

  1. 決議(value)只有在被proposers提出後才能被批准(未經批准的決議稱爲「提案(proposal)」);

  2. 在一次Paxos算法的執行實例中,只批准(chosen)一個value;

  3. learners只能得到被批准(chosen)的value。

Process:處理流程

批准value的過程當中,首先proposers將value發送給Acceptors,以後Acceptors對value進行接受(accept)。爲了知足只批准一個value的約束,要求經「多數派(majority)」接受的value成爲正式的決議(稱爲「批准」決議)。這是由於不管是按照人數仍是按照權重劃分,兩組「多數派」至少有一個公共的Acceptor,若是每一個Acceptor只能接受一個value,約束2就能保證。整個過程(一個實例或稱一個事務或一個Round)分爲兩個階段:

  • phase1(準備階段)

    • Proposer向超過半數(n/2+1)Acceptor發起prepare消息(發送編號)

    • 若是prepare符合協議規則Acceptor回覆promise消息,不然拒絕

  • phase2(決議階段或投票階段)

    • 若是超過半數Acceptor回覆promise,Proposer向Acceptor發送accept消息(此時包含真實的值)

    • Acceptor檢查accept消息是否符合規則,消息符合則批准accept請求

根據上述過程當一個proposer發現存在編號更大的提案時將終止提案。這意味着提出一個編號更大的提案會終止以前的提案過程。若是兩個proposer在這種狀況下都轉而提出一個編號更大的提案,就可能陷入活鎖,違背了Progress的要求。這種狀況下的解決方案是選舉出一個leader,僅容許leader提出提案。可是因爲消息傳遞的不肯定性,可能有多個proposer自認爲本身已經成爲leader。Lamport在The Part-Time Parliament一文中描述並解決了這個問題。

約束條件

P1: Acceptor必須接受他接收到的第一個提案,

注意P1是不完備的。若是剛好一半Acceptor接受的提案具備value A,另外一半接受的提案具備value B,那麼就沒法造成多數派,沒法批准任何一個value。

P2:當且僅當Acceptor沒有迴應過編號大於n的prepare請求時,Acceptor接受(accept)編號爲n的提案。

若是一個沒有chosen過任何proposer提案的Acceptor在prepare過程當中回答了一個proposer針對提案n的問題,可是在開始對n進行投票前,又接受(accept)了編號小於n的另外一個提案(例如n-1),若是n-1和n具備不一樣的value,這個投票就會違背P2c。所以在prepare過程當中,Acceptor進行的回答同時也應包含承諾:不會再接受(accept)編號小於n的提案。

P3:只有Acceptor沒有接受過提案Proposer才能採用本身的Value,否者Proposer的Value提案爲Acceptor中編號最大的Proposer Value;
P4: 一個提案被選中須要過半數的Acceptor接受。

假設A爲整個Acceptor集合,B爲一個超過A一半的Acceptor集合,B爲A的子集,C也是一個超過A一半的Acceptor集合,C也是A的子集,有此可知任意兩個過半集合中一定有一個共同的成員Acceptor;此說明了一個Acceptor能夠接受不止一個提案,此時須要一個編號來標識每個提案,提案的格式爲:[編號,Value],編號爲不可重複全序的,由於存在着一個一個Paxos過程只能批准一個value這時又推出了一個約束P3;

P5:當編號K0、Value爲V0的提案(即[K0,V0])被過半的Acceptor接受後,從此(同一個Paxos或稱一個Round中)全部比K0更高編號且被Acceptor接受的提案,其Value值也必須爲V0。

該約束還能夠表述爲:

  • 一旦一個具備value v的提案被批准(chosen),那麼以後任何Acceptor再次接受(accept)的提案必須具備value v。

  • 一旦一個具備value v的提案被批准(chosen),那麼之後任何proposer提出的提案必須具備value v。

  • 若是一個編號爲n的提案具備value v,那麼存在一個多數派,要麼他們中全部人都沒有接受(accept)編號小於n

的任何提案,要麼他們已經接受(accept)的全部編號小於n的提案中編號最大的那個提案具備value v。

由於每一個Proposer均可提出多個議案,每一個議案最初都有一個不一樣的Value因此要知足P3就又要推出一個新的約束P4;

決議的發佈

一個顯而易見的方法是當acceptors批准一個value時,將這個消息發送給全部learner。可是這個方法會致使消息量過大。

因爲假設沒有Byzantine failures,learners能夠經過別的learners獲取已經經過的決議。所以acceptors只需將批准的消息發送給指定的某一個learner,其餘learners向它詢問已經經過的決議。這個方法下降了消息量,可是指定learner失效將引發系統失效。

所以acceptors須要將accept消息發送給learners的一個子集,而後由這些learners去通知全部learners。

可是因爲消息傳遞的不肯定性,可能會沒有任何learner得到了決議批准的消息。當learners須要瞭解決議經過狀況時,可讓一個proposer從新進行一次提案。注意一個learner可能兼任proposer。

議員稅率問題實例

有A1, A2, A3, A4, A5 5位議員,就稅率問題進行決議。議員A1決定將稅率定爲10%,所以它向全部人發出一個草案。這個草案的內容是:

現有的稅率是什麼?若是沒有決定,則建議將其定爲10%.時間:本屆議會第3年3月15日;提案者:A1

在最簡單的狀況下,沒有人與其競爭;信息能及時順利地傳達到其它議員處。

因而, A2-A5迴應:

我已收到你的提案,等待最終批准

而A1在收到2份回覆後就發佈最終決議:

稅率已定爲10%,新的提案不得再討論本問題。

這實際上退化爲二階段提交協議。

如今咱們假設在A1提出提案的同時, A5決定將稅率定爲20%:

現有的稅率是什麼?若是沒有決定,則建議將其定爲20%.時間:本屆議會第3年3月15日;提案者:A5

草案要經過侍從送到其它議員的案頭. A1的草案將由4位侍從送到A2-A5那裏。如今,負責A2和A3的侍從將草案順利送達,負責A4和A5的侍從則不上班. A5的草案則順利的送至A3和A4手中。

如今, A1, A2, A3收到了A1的提案; A3, A4, A5收到了A5的提案。按照協議, A1, A2, A4, A5將接受他們收到的提案,侍從將拿着

我已收到你的提案,等待最終批准

的回覆回到提案者那裏。

而A3的行爲將決定批准哪個。

狀況一:僅有單議員提出議案

假設A1的提案先送到A3處,而A5的侍從決定放假一段時間。因而A3接受並派出了侍從. A1等到了兩位侍從,加上它本身已經構成一個多數派,因而稅率10%將成爲決議. A1派出侍從將決議送到全部議員處:

稅率已定爲10%,新的提案不得再討論本問題。

A3在好久之後收到了來自A5的提案。因爲稅率問題已經討論完畢,他決定再也不理會。可是他要抱怨一句:

稅率已在以前的投票中定爲10%,你不要再來煩我!

這個回覆對A5可能有幫助,由於A5可能由於某種緣由好久沒法與與外界聯繫了。固然更可能對A5沒有任何做用,由於A5可能已經從A1處得到了剛纔的決議。

狀況二:多議員提出議案,可是第一份議案在第二份議案廣播前造成決議

依然假設A1的提案先送到A3處,可是此次A5的侍從不是放假了,只是中途耽擱了一會。此次, A3依然會將"接受"回覆給A1.可是在決議成型以前它又收到了A5的提案。這時協議有兩種處理方式:

1.若是A5的提案更早,按照傳統應該由較早的提案者主持投票。如今看來兩份提案的時間同樣(本屆議會第3年3月15日)。可是A5是個惹不起的大人物。因而A3回覆:

我已收到您的提案,等待最終批准,可是您以前有人提出將稅率定爲10%,請明察。

因而, A1和A5都收到了足夠的回覆。這時關於稅率問題就有兩個提案在同時進行。可是A5知道以前有人提出稅率爲10%.因而A1和A5都會向全體議員廣播:

稅率已定爲10%,新的提案不得再討論本問題。

一致性獲得了保證。

2. A5是個無足輕重的小人物。這時A3再也不理會他, A1不久後就會廣播稅率定爲10%.

狀況三:多議員同時提出議案

在這個狀況中,咱們將看見,根據提案的時間及提案者的權勢決定是否應答是有意義的。在這裏,時間和提案者的權勢就構成了給提案編號的依據。這樣的編號符合"任何兩個提案之間構成偏序"的要求。

A1和A5一樣提出上述提案,這時A1能夠正常聯繫A2和A3; A5也能夠正常聯繫這兩我的。此次A2先收到A1的提案; A3則先收到A5的提案. A5更有權勢。

在這種狀況下,已經回答A1的A2發現有比A1更有權勢的A5提出了稅率20%的新提案,因而回復A5說:

我已收到您的提案,等待最終批准。

而回復了A5的A3發現新的提案者A1是個小人物,不予理會。

A1沒有達到多數,A5達到了,因而A5將主持投票,決議的內容是A5提出的稅率20%.

若是A3決定平等地對待每一位議員,對A1作出"你以前有人提出將稅率定爲20%"的回覆,則將形成混亂。這種狀況下A1和A5都將試圖主持投票,可是此次兩份提案的內容不一樣。

這種狀況下, A3若對A1進行回覆,只能說:

有更大的人物關注此事,請等待他作出決定。

另外,在這種狀況下, A4與外界失去了聯繫。等到他恢復聯繫,並須要得知稅率狀況時,他(在最簡單的協議中)將提出一個提案:

現有的稅率是什麼?若是沒有決定,則建議將其定爲15%.時間:本屆議會第3年4月1日;提案者:A4

這時,(在最簡單的協議中)其餘議員將會回覆:

稅率已在以前的投票中定爲20%,你不要再來煩我!

算法實踐

基於邏輯時鐘的Paxos協議

爲了讓這套系統能正確運行,咱們須要一個精確的時鐘。因爲操做系統的物理時鐘常常是有誤差的,因此咱們決定採用一個邏輯時鐘。時鐘的目的是給系統中 發生的每個事件編排一個序號。假設咱們有一臺單獨的機器提供了一個全局的計數器服務。它只支持一個方法:incrementAndGet()。這個方法 的做用是將計數器的值加一,而且返回增長後的值。咱們將這個計數器稱爲globalClock。globalClock的初始值爲0。而後,系統中的每一個其它機器,都有一個本身的localClock,它的初始值來自globalClock。

int globalClock::incrementAndGet(){
   return (MAX_SERVER_ID+1)*++localCounter+serverID;
}

假設機器和機器之間經過request-response這樣的模式通信。每條網絡消息的類型要麼是reqeust,要麼是response。response又分爲兩種:OK和Rejected。不管什麼類型的網絡消息,都必須帶有一個時間戳,時間戳取自這臺機器的localClock。咱們規定消息的接收方必須執行如下行爲:

if(message.timestamp&localClock && message.type==request) reject(message);
else {
  localClock=message.timestamp; 
  process(message);
}

簡而言之就是:咱們不處理過期的請求。而且利用網絡間傳輸的消息做爲時鐘同步機制。一旦收到過期的請求,就返回Rejected類型的答覆。另外,咱們要求時鐘不能倒流。咱們必須容忍機器忽然crash。在機器從新起來以後,要麼localClock從globalClock取一個新值,要麼localClock每次更新的時候都必須寫入到本地硬盤上,重啓以後從那再讀進來。咱們偏向於第二種方案,由於我後面將會講怎麼去掉 globalClock這個服務器。

Paxos協議分爲兩個階段。

  1. 設置時鐘:proposer令localClock=globalClock.incrementAndGet()。

  2. prepare:proposer向全部Acceptor發送一個prepare消息。接收方應返回它最近一次accept的value,以及 accept的時間,若在它尚未accept過value,那麼就返回空。proposer只有在收到過半數的response以後,纔可進入下一個階段。一旦收到reject消息,那麼就重頭來。

  3. 構造Proposal:proposer從prepare階段收到的全部values中選取時間戳最新的一個。若是沒有,那麼它本身提議一個value。

  4. 發送Proposal:proposer把value發送給其它全部機器,消息的時間戳取自localClock。接收方只要檢查消息時間戳合法,那麼就接受此value,把這個value和時間戳寫入到硬盤上,而後答覆OK,不然拒絕接受。proposer若收到任何的reject答覆,則回到 step1。不然,在收到過半數的OK後,此Proposal被經過。

Leader選舉實例

這裏具體例子來講明Paxos的整個具體流程: 假若有Server一、Server二、Server3這樣三臺服務器,咱們要從中選出leader,這時候Paxos派上用場了。整個選舉的結構圖以下:

enter image description here

Phase1(準備階段)

  1. 每一個Server都向Proposer發消息稱本身要成爲leader,Server1往Proposer1發、Server2往Proposer2發、Server3往Proposer3發;

  2. 如今每一個Proposer都接收到了Server1發來的消息但時間不同,Proposer2先接收到了,而後是Proposer1,接着纔是Proposer3;

  3. Proposer2首先接收到消息因此他從系統中取得一個編號1,Proposer2向Acceptor2和Acceptor3發送一條,編號爲1的消

息;接着Proposer1也接收到了Server1發來的消息,取得一個編號2,Proposer1向Acceptor1和Acceptor2發送一條,編號爲2的消息; 最後Proposer3也接收到了Server3發來的消息,取得一個編號3,Proposer3向Acceptor2和Acceptor3發送一條,編

號爲3的消息;

  1. 這時Proposer1發送的消息先到達Acceptor1和Acceptor2,這兩個都沒有接收過請求因此接受了請求返回[2,null]給Proposer1,並承諾不接受編號小於2的請求;

  2. 此時Proposer2發送的消息到達Acceptor2和Acceptor3,Acceprot3沒有接收過請求返回[1,null]給Proposer2,並承諾不接受編號小於1的請求,但這時Acceptor2已經接受過Proposer1的請求並承諾不接受編號小於的2的請求了,因此Acceptor2拒絕Proposer2的請求;

  3. 最後Proposer3發送的消息到達Acceptor2和Acceptor3,Acceptor2接受過提議,但此時編號爲3大於Acceptor2的承諾2與Accetpor3的承諾1,因此接受提議返回[3,null];

  4. Proposer2沒收到過半的回覆因此從新取得編號4,併發送給Acceptor2和Acceptor3,而後Acceptor2和Acceptor3

Phase2(決議階段)

  1. Proposer3收到過半(三個Server中兩個)的返回,而且返回的Value爲null,因此Proposer3提交了[3,server3]的議案;

  2. Proposer1收到過半返回,返回的Value爲null,因此Proposer1提交了[2,server1]的議案;

  3. Proposer2收到過半返回,返回的Value爲null,因此Proposer2提交了[4,server2]的議案;

  4. Acceptor一、Acceptor2接收到Proposer1的提案[2,server1]請求,Acceptor2承諾編號大於4因此拒絕了經過,Acceptor1經過了請求;

  5. Proposer2的提案[4,server2]發送到了Acceptor二、Acceptor3,提案編號爲4因此Acceptor二、Acceptor3都經過了提案請求;

  6. Acceptor二、Acceptor3接收到Proposer3的提案[3,server3]請求,Acceptor二、Acceptor3承諾編號大於4因此拒絕了提案;

  7. 此時過半的Acceptor都接受了Proposer2的提案[4,server2],Larner感知到了提案的經過,Larner學習提案,server2成爲Leader;

一個Paxos過程只會產生一個議案因此至此這個流程結束,選舉結果server2爲Leader;

相關文章
相關標籤/搜索