【轉】Paxos算法3-實現探討

——轉自:{老碼農的專欄算法

  前兩篇Paxos算法的討論,讓咱們對paxos算法的理論造成過程有了大概的瞭解,但距離其成爲一個可執行的算法程序還有很長的路要走,緣由是不少的細節和錯誤未被考慮。Google Chubby的做者說,paxos算法實現起來遠沒有看起來簡單,緣由是paxos的容錯僅限於server crash這一種狀況,但在實際工程實現時要考慮磁盤損壞、文件損壞、Leader身份丟失等諸多的錯誤。編程

 

1. Paxos各角色的職能promise

  在paxos算法中存在Client、Proposer、Proposer Leaer、Acceptor、Learn五種角色,可精簡爲三種主要角色:proposer、acceptor、learn。角色只是邏輯上存在的,在實際實現中,節點能夠身兼多職。網絡

  在咱們的討論中,咱們先假定沒有Proposer Leader這一角色,在不考慮活鎖的狀況下,若是算法過程正確,那有Leader角色的算法過程確定也正確。分佈式

  除了五種角色,還有三個重要的概念:instance、proposal、value,分別表明:每次paxos選舉過程、提案、提案的valueoop

固然,還有4個關鍵過程:學習

  • (Phase1):prepare
  • (Phase1):prepare ack
  • (Phase2):accept
  • (Phase2):accept ack

  對acceptor來講,還蘊含是着promise、accept、reject三個動做。google

  先上一幅圖,更直觀地對幾種角色的職能加以瞭解(各角色的具體職能參考Lamport的論文就足夠了):spa

Paxos各角色職責

上圖不是很是嚴格,僅爲表現各角色之間的關係。.net

 

2. Proposer

  在Proposer、Acceptor、Learn中均涉及到proposal的編號,該編號應該有proposer做出改變,對其餘的角色是隻讀的,這就保證了只有一個數據源。當多個proposer同時提交proposal時,必須保證各proposer的編號惟1、且可比較,具體作法以前已經提到。這裏還要強調一點的是,僅每一個proposer按本身的規則提升編號是不夠的,還必須瞭解「外面」的最大編號是多少,例如,P一、P二、P3(請參考:Paxos算法2#再論編號問題:編號惟一性

  • P3的當前編號爲初始編號2
  • 當P3提交proposal時發現已經有更大的編號16(16是P2提出的,按規則:5*3+1)
  • P3發起新編號時必須保證new no >16,按照前面的規則必須選擇:5*3+2 = 17,而不能僅按本身的規則選擇:1*3+2=5

  這要求acceptor要在reject消息中給出當前的最大編號,proposer可能出現宕機,重啓後繼續服務,reject消息會幫助它迅速找到下一個正確編號。可是當多個acceptor回覆各自不一的reject消息時,事情就變得複雜起來。

當proposer發送proposal給一個acceptor時,會有三種結果:

  • timeout:超時,未接收到aceptor的response
  • reject:編號不夠大,拒絕。並附有當前最大編號
  • promise:接受,並確保不會批准小於此編號的proposal。並附有當前最大編號及對應的value

  在判斷是否能夠進行Phase2時的一個充分條例就是:必須有acceptor的多數派promise了當前的proposal

  下面分別從Phase1和Phase2討論proposer的行爲:

Phase1-prepare:發送prepare到acceptor


Proposer在本地選擇proposal編號,發送給acceptor,會收到幾種狀況的response:

(a). 沒有收到多數派的迴應

  消息丟失、Server宕機致使沒有多數派響應,在可靠消息傳輸(TCP)下,應該報告宕機致使剩餘的Server沒法繼續提供服務,在實際中一個多數派同時宕機的可能性很是小。

(b). 收到多數派的reject

  Acceptor可能會發生任意的錯誤,好比消息丟失、宕機重啓等,會致使每一個acceptor看到的最大編號不一致,於是在reject消息中response給proposer的最大編號也不一致,這種狀況proposer應該取其最大做爲比較對象,從新計算編號後繼續Phase1的prepare階段。

(c). 收到多數派的promise

根據包含的value不一樣,這些promise又分三種狀況:

  • 多數派的value是相同的,說明以前已經達成了最終決議
  • value互不相同,以前並無達成最終決議
  • 返回的value所有爲null

  所有爲null的狀況比較好處理,只要proposer自由決定value便可;多數派達成一致的狀況也好處理,選擇已經達成決議的value提交便可,value互不相同的狀況有兩種處理方式:

  • 方案1:自由肯定一個value。緣由:反正以前沒有達成決議,本次提交哪一個value應該是沒有限制的。
  • 方案2:選擇promise編號最大的value。緣由:本次選舉(instance)已經有提案了,雖未獲經過,但之後的提案應該繼續以前的,Lamport在paxos simple的論文中選擇這種方式。

  其實問題的本質是:在一個instance內,一個acceptor是否能accept多個value?約束P2只是要求,若是某個value v已被選出,那以後選出的還應該是v;反過來講,若是value v尚未被多數派accept,也沒有限制acceptor只accept一個value。

  感受兩種處理方式均可以,只要選擇一個value,能在paxos以後的過程當中達成一致便可。其實否則,有可能value v已經成爲了最終決議,但acceptor不知道,若是這時不選擇value v而選其餘value,會致使在一次instance內達成兩個決議。

  會不會存在這樣一種狀況:A、B、C、D爲多數派的promise,A、B、C的proposal編號,value爲(1,1),D的爲(2,2)?就是說,編號互不一致,但小編號的已經達成了最終決議,而大編號的沒有?

設:小編號的proposal爲P1,value爲v1;大編號的proposal爲P2,value爲v2

  • 若是P1選出最終決議,那麼確定是完成了phase一、phase2。存在一個acceptor的多數派C1,P1爲其最大編號,而且每一個acceptor都accept了v1;
  • 當P2執行phase1時,存在多數派C2迴應了promise,則C1與C2存在一個公共成員,其最大編號爲P1,而且accept了v1
  • 根據規則,P2只能選擇v1繼續phase2,也就是說v1=v2,不管phase2是否能成功,毫不會在acceptor中遺留下相似(2,2)這樣的value

  也就是說,只要按照【方案2】選擇value就能保證結果的正確性。之因此能有這樣的結果,關鍵仍是那個神祕的多數派,這個多數派起了兩個相當重要的做用:

  • 在phase1拒絕小編號的proposal
  • 在phase2強迫proposal選擇指定的value

  而多數派能起做用的緣由就是,任何兩個多數派至少有一個公共成員,而這個公共成員對後續proposal的行爲起着決定性的影響,若是這個多數派拒絕了後續的proposal,這些proposal就會由於沒法造成新的多數派而進行不下去。這也是paxos算法的精髓所在吧。

Phase2-accept:發送accept給acceptor


  若是一切正常,proposer會選擇一個value發送給acceptor,這個過程比較簡單

accept也會收到2種迴應:


(a). acceptor多數派accept了value

  一旦多數派accept了value,那最終決議就已達成,剩下的工做就是交由learn學習並關閉本次選舉(instance)。

(b). acceptor多數派reject了value或超時

  說明acceptor不可用或提交的編號不夠大,繼續Phase1的處理。

proposer的處理大概如此,但實際編程時還有幾個問題要考慮:

  • 來自acceptor的重複消息
  • 原本超時的消息又忽然到了
  • 消息持久化

  其餘2個問題比較簡單,持久化的問題有必要討論下。

  持久化的目的是在proposer server宕機「甦醒」時,能夠繼續參與paxos過程。

  從前面分析可看出,proposer工做的正確性是靠編號的正確性來保證的,編號的正確性是由proposer對編號的初始化寫及acceptor的reject一塊兒保證的,因此只要acceptor能正常工做,proposer就無須持久化當前編號。

 

3. acceptor

  acceptor的行爲相對簡單,就是根據提案的編號決定是否接受proposal,判斷編號依賴promise和accept兩種消息,所以acceptor必須對接收到的消息作持久化處理。根據以前的討論也知道,acceptor的持久化也會影響着proposer的正確性。

  在acceptor對proposal進行決策的時候,還有個重要的概念沒有被詳細討論,即instance。任何對proposal的判斷都是基於某個instance,即某次paxos過程,當本次instance宣佈結束(選出了最終決議)時,paxos過程就轉移到下一個instance。這樣會衍生出幾個問題:

  1. instance什麼時候被關閉?被誰關閉?
  2. acceptor的行爲是否依賴instance的關閉與否?
  3. acceptor的多數派會不會在同一個instance內對兩個不一樣的value同時達成一致?

  根據1中對各角色職能的討論,決議是否被選出是由learn來決定的,當learn得知某個value v已經被多數派accept時,就認爲決議被選出,並宣佈關閉當前的instance。與2中提到的同樣,由於網絡緣由acceptor可能不會得知instance已被關閉,而會繼續對proposer回答關於該instance的問題。也就是說,不管如何acceptor都沒法準確得知instance是否關閉,acceptor程序的正確性也就不能依賴instance是否關閉。但acceptor在已經知道instance已被關閉的狀況下,在拒絕proposer時能提供更多的信息,好比,可使proposer選擇一個更高的instance從新提交請求。

  固然,只要proposer根據2中提到的方式進行提案,就不會發生同一instance中產生兩個決議的狀況。

 

4. learn

  learn的主要職責是學習決議,但決議必須一個一個按順序學,不能跳號,好比learn已經知道了100,102號決議,必須同時知道101時才能一塊兒學習。只是簡單的等待101號決議的到來顯然不是一個好辦法,learn還要去主動向acceptor詢問101號決議的狀況,acceptor會對消息作持久化,作到這一點顯然不難。

  learn還要週期性地check所接收到的value,一旦意識到決議已經達成,必須關閉對應的instance,並通知acceptor、proposer等(根據須要能夠通知任意多的對象)。

  learn還存在一個問題是,是選擇一個server作learn仍是選多個,假若有N個acceptor,M個learn,則會產生N*M的通訊量,若是M很大則通訊量會很是大,若是M=1,通訊量小但會造成單點。折中方案是選擇規模相對較小的M,使這些learn通知其餘learn。

paxos中的learn相對比較抽象,好理解但不可思議能作什麼,緣由在於對paxos的應用場景不清晰。通常說來有兩種使用paxos的場景:

  • paxos做爲單獨的服務,好比google的chubby,hadoop的zookeeper
  • paxos做爲應用的一部分,好比Keyspace、BerkeleyDB

  若是把paxos做爲單獨的服務,那learn的做用就是達成決議後通知客戶端;若是是應用的一部分,learn則會直接執行業務邏輯,好比開始數據複製。

持久化:

  learn所依賴的全部信息就是value和instance,這些信息都已在acceptor中進行了持久化,因此learn不須要再對消息進行持久化,當learn新加入或重啓時要作的就是能經過acceptor把這些信息取回來。

錯誤處理:

  learn可能會重啓或新加入後會對「以前發生的事情」不清楚,解決辦法是:使learn繼續監聽消息,直至某個instance對應的value達成一致時,learn再向acceptor請求以前全部的instance。

 

  至此,咱們進一步討論了paxos個角色的職責和可能的實現分析,離咱們把paxos算法變爲一個可執行程序的目標又進了一步,使咱們對paxos的實現方式大體內心有底,但還有諸多的問題須要進一步討論,好比錯誤處理。雖然文中也提到了一些錯誤及處理方式,但尚未系統地考慮到全部的錯誤。

 

接下來的討論將重點圍繞着分佈式環境下的錯誤處理。

相關文章
相關標籤/搜索