說multi-paxos以前先簡要說一下paxos算法
paxos是在多個成員之間對某個值(提議)達成一致的一致性協議。這個值能夠是任何東西。好比多個成員之間進行選主,那麼這個值就是主的身份。在把multi-paxos協議應用在日誌同步中時,這個值就是一條日誌。網上講paxos的文章已經不少了,這裏簡要說明一下。網絡
paxos分爲prepare和accept兩個階段。協議中有兩個主要的角色,proposer和acceptor。app
value被majority accept以前,每一個acceptor能夠accept多個不一樣的值。可是,一旦一個value被majority accept(即value達成一致),那麼這個value就不會變了。由於prepare階段會將該value給找出來,隨後accept階段會使用這個value,後續的全部的提案都會選擇這個value。優化
須要注意的是,每一個階段都是收到majority的響應後即開始處理。而且因爲機器會宕機,acceptor須要對acceptedProposalID, acceptedValue和minProposal進行持久化。spa
從流程中能夠看出prepare有兩個做用:日誌
能夠看出,一次paxos達成一致至少須要兩次網絡交互。blog
paxos是對一個值達成一致,multi-paxos是運行多個paxos instance來對多個值達成一致,每一個paxos instance對一個值達成一致。在一個支持多寫而且強一致性的系統中,每一個節點均可以接收客戶端的寫請求,生成redo日誌而後開始一個paxos instance的prepare和accept過程。這裏的問題是每條redo日誌到底對應哪一個paxos instance。索引
在日誌同步應用中,用log id來區分不一樣的paxos instance。每條日誌都由一個id惟一標示,這個log id標識一個paxos instance,這個paxos instance達成一致,即對應的日誌內容達成一致,即majority的成員accept了這個日誌內容。在一個由N個機器(每一個機器既承擔proposer也承擔acceptor角色)組成的集羣(一般叫作paxos group)中,每一個proposer均可以產生redo日誌而且進行paxos instance,那麼每條redo日誌到底使用哪一個log id? 顯然,每一個proposer都會選擇本身知道的尚未達成一致的最小的log id來做爲此次日誌的log id,而後運行paxos協議,顯然,多個proposer可能會選擇同一個log id(最典型的場景就是空集羣啓動的狀況下),最終,只有一個proposer可以成功,那麼其餘的proposer就須要選擇更大的未達成一致的log id來運行paxos。顯然,這種衝突是很是嚴重的,會有不少的proposer成功不了進而選擇更大的log id來運行paxos。ip
在真實的系統中,好比chubby, spanner,都會在paxos group中選擇一個成員做爲leader,只有leader可以propose日誌,這樣,prepare階段就不會存在衝突,至關於對整個log文件作了一次prepare,後面這些日誌均可以選用同一個proposal id。這樣的話,每條日誌只須要一次網絡交互就能達成一致。回顧一下文章開頭提到paxos中須要每一個成員須要記錄3個值,minProposal,acceptedProposal,acceptedValue,其中後面兩個值能夠直接記錄在log中,而第一個值minProposal能夠單獨存在一個文件中。因爲這裏後面的日誌均可以選用同一個proposal id,顯然,在大部分時間內,minProposal都不須要改變。這正是multi-paxos的精髓同步
對於paxos來講,主的身份無所謂,主不須要像raft那樣擁有最全的已經commit的日誌。因此選主算法無所謂,好比你們都給機器ip最大的機器投票,或者給日誌最多的投票,或者乾脆直接運行一次paxos,值的內容就是主的身份。顯然,因爲對新主的身份無限制,那麼,新主頗有可能沒有某些已經達成一致的日誌,這個時候,就須要將這些已達成一致的日誌拉過來,另外,新主也有可能沒有某些還未達成一致的日誌。以下圖所示:
圖中,恢復以前,log id等於3的日誌C已經在多數派上達成了一致,可是在新主上沒有。好比log id等於4的日誌D在多數派上沒有達成一致,在新主上也沒有。
新主向全部成員發送查詢最大log id的請求,收到majority的響應後,選擇最大的log id做爲日誌恢復的結束點。圖中,若是收到的majority不包括2號成員,那麼log id=6爲恢復結束點。若是收到的majority包括2號成員,那麼log id=7爲恢復結束點。這裏取majority的意義在於恢復結束點包含任何的majority達成一致的日誌。拿到log id後,從頭開始掃描日誌,對於每條日誌都運行paxos協議確認一次:若是日誌以前已經達成一致了,好比日誌A,B,C,E,F,那麼再次運行paxos的prepare階段會把日誌內容找出來做爲accept階段的值,不影響結果。若是日誌以前並無達成一致,好比日誌D,那麼當返回的majority中包含3號成員時,D會被選出來看成accept階段的值,當返回的majority中不包含3號成員時,那麼D實際上不會被選出,這時主能夠選擇一個dummy日誌做爲accept階段的值。
能夠看出,若是日誌很是多,每次重啓後都要對每條日誌作一次paxos,那麼恢復時間可想而知。在上面的例子中,A,B,E已經達成一致,作了無用功。paxos協議中,只有主即proposer知道哪些日誌達成了一致,acceptor不知道,那麼很容易想到的一個優化就是proposer將已經達成一致的日誌id告訴其餘acceptor,acceptor寫一條確認日誌到日誌文件中。後續重啓的時候,掃描本地日誌只要發現對應的確認日誌就知道這條日誌已經達成多數派,不須要從新使用paxos進行確認了。這種作法有一個問題,考慮以下場景:
舊主成功的給本身和2號成員發送了確認日誌,可是沒有給3號成員發送成功舊掛了,而後2號成員被選爲新主,那麼新主不會對log id=3的日誌從新運行paxos,由於本機已經存在確認日誌。這樣的話,3號成員就回放log id=3的日誌到上層了。解決這個問題的作法就是followers須要主動的向主詢問日誌到底有沒有達成一致,若是有,則本身補充確認日誌。
宕機重啓後,對未達成一致的日誌從新運行paxos時,如log id=4的日誌,若是返回的majority中不包含3號成員,那麼日誌D不會被找出來,這樣就須要將3號成員的log id=4日誌置未一條無操做的日誌記做NOP日誌,D最終也就不會造成多數派。因爲multi-paxos容許日誌亂序接收,而且日誌的長度幾乎都不同,因此在磁盤上log id是亂序的,因此從物理上說,每一個成員的日誌不是如出一轍的。那麼要把log id=4的日誌覆蓋寫成NOP日誌也就比較麻煩,須要爲每條日誌維護索引。實現上能夠不覆蓋寫,直接append一條log id=4的NOP日誌到日誌文件,這樣沒有問題,由於回放的時候只會回放能找到確認日誌的日誌到上層應用中。
multi-paxos處理成員變動比較簡單,規定第i條日誌參與paxos同步的時候,其成員組是第i-k條日誌包含的成員組(每條日誌裏面都包含成員組)。
multi-paxos只是保證你們對日誌達成一致。可是具體multi-paxos運用到真實的系統中時,從應用層面上看,可能會出現一些詭異的問題。考慮以下場景:
如圖,1號主寫了A,B,C,D,其中B,C,D沒有造成多數派,而後A宕機了,2號被選爲了主,客戶端過來讀不到B,C,D,而後B沒寫任何東西,就掛了,這個時候,A起來後從新被選爲主,對B,C,D從新運行paxos,把B,C,D達成了一致,這個時候客戶端再次過來讀,又能讀到B,C,D了。對於multi-paxos自己來講,並無什麼不對的地方,可是上層應用的語義出現了問題:曾經讀不到的東西,什麼都沒作,又能讀到了。
解決這個問題的方法是經過主提供服務以前必須成功寫入一條start working日誌來解決。以下圖:
如圖,每一個成員成爲主提供服務以前都要首先寫一條start working日誌,只有達成多數派才能提供服務。1號在從新成爲主以後,經過對log id=2的日誌運行paxos,將2號start日誌恢復了出來,而後對C和D運行paxos恢復出來後,後續回放的時候,若是發現後面日誌中帶有的timestamp(其實時leader上任時間)比start working帶有的timestamp更小,那麼就不回放到上層。隨後客戶端來讀仍然讀不到B,C,D,先後保持一致。