主要從以下幾點理解paxos:
- 基礎paxos算法
- 階段一:prepare
- 階段二:accept
- multi-paxos
- 選擇log entry
- leader選舉
- 消除prepare RPC調用
- 全備份
- 客戶端協議
- 改變系統配置
基礎paxos
只有一個acceptor可以嗎? 不可以。所有的proposer都往一個acceptor上發消息,這個acceptor crashed,那麼整個系統就crashed。 解決方法:使用quorum (仲裁人數)。多個acceptor, 只需要大多數acceptor選中某個value就可以了,萬一某一個acceptor crashed,不影響系統。
每個acceptor 只chose第一個value,這樣可以嗎? 不可以。因爲這樣可能會出現proposal1的acceptor和proposal2的acceptor的數量一樣多,無法選出哪一個proposal作爲chosenproposal。例如server1 選中proposal1,server2 選中proposal1和proposal2,server3 選中proposal2。 解決方案:每個proposer在發起proposal前必須確認當前是否有proposal選中,如果有,則放棄自己的proposal。
chosen過程中會出現衝突,依據衝突不同得出結論: 一旦選中一個proposal,其他競選proposal必須選擇同樣的value;proposal按照proposal number排序,拒絕舊proposal;
通過上述描述, 可設計proposal number 結構如下: 由兩部分組成:
- round number: paxos執行的回數,每個服務器都必須保持最新的maxRound【保證儘量少的出現衝突,都競爭最大編號】
- server id:每個服務器有唯一的id【保證proposal編號不會相同 】
maxRound必須保存在永久存儲介質上,一段server crash,可以重新使用 round number 發起proposal
- paxos各階段目標:
- prepare階段
- 發現任任何被選擇的proposal。發現後將自己的value改爲發現的Value
- 阻塞還未完成的proposal。當proposal number較大的proposal進入prepare階段後,舊的proposal在accept階段會被拒絕。
- accept階段
- 使得所有acceptor接受proposal。
- prepare階段
主要邏輯:
|proposer|acceptor|
|-|-|
|1.選擇proposal number n
||
|2.廣播給acceptor prepare(n)
||
||3. 響應prepare(n)
: if (n > minProposol) then minProposol=n; Return(acceptedProposol,acceptedValue)|
|4. 獲取大多數acceptor回覆:如果有返回,選擇返回中最大的proposal number 對應的value作爲本次proposal的value;如果沒有返回,可繼續使用之前的value||
|5.向所有acceptor廣播accept(n,value)
||
||6. 響應accept(n,value)
:if(n >= minProposol) then acceptedProposol = minProposol = n; accptedValue = value; Return minProposol|
|7. 獲取大多數acceptor返回:如果存在result>n;表示proposal被拒絕,重複1~6,且下次設置的n比result大,否則chosen proposal||
所有競爭的proposal必須有一個公共的acceptor,這樣就能發現新舊proposal,並及時終止舊proposal。
paxos活鎖:導致無proposal chosen。 proposal A 早prepare階段通過,另一proposal B進入prepare, acceptor的minProposal增加,導致A在accept階段被拒絕,A立即重試,並進入prepare階段,導致acceptor的minProposal增加,B進入accept階段後被拒絕。。。如此往復。沒有command能正常進行。
解決方案:每次重試必須在隨機的時延後進行。
multi-paxos
如何選擇log entry?
實現步驟:
- 找到第一個log entry 不知道是否chosen的entry slot,(不一定是第一個爲空的slot)
- 運行basic paxos, value爲client command,
- prepare return 中是否包含acceptedvalue?
- 有:chosen acceptedvalue,並重復步驟1
- 無:chosen client請求,該slot即是尋找的slot
例子:
| |1|2|3|4|5|6|7| |-|-|-|-|-|-|-|-| |server 1|mov|add|cmp|||ret|| |server 2|mov|add||sub||ret|| |server 3|mov|add|cmp||cmp|ret||
如上表,當client請求jmp時,假設server3 crashed,此時到slot3,由於server1和server2不同,不知道是否chosen Proposal,於是以client command的值爲value; 運行paxos,進入prepare(n),server1 slot3已經有acceptedvalue,所以Proposal的value會改爲server 1 slot 3的值,然後進行accept階段,server2 slot3 接收value。將server2 slot3 改爲cmp。
| |1|2|3|4|5|6|7| |-|-|-|-|-|-|-|-| |server 1|mov|add|cmp|||ret|| |server 2|mov|add|cmp|sub||ret|| |server 3|mov|add|cmp||cmp|ret||
同理,server1 slot4 改爲sub:
| |1|2|3|4|5|6|7| |-|-|-|-|-|-|-|-| |server 1|mov|add|cmp|sub||ret|| |server 2|mov|add|cmp|sub||ret|| |server 3|mov|add|cmp||cmp|ret||
然後slot5, acceptedValue=null;次次Proposal的value爲元定義的value,即client的command。
| |1|2|3|4|5|6|7| |-|-|-|-|-|-|-|-| |server 1|mov|add|cmp|sub|jmp|ret|| |server 2|mov|add|cmp|sub|jmp|ret|| |server 3|mov|add|cmp||cmp|ret|| 找到 log entry slot。
注意:上述server3 crashed!
在上述處理過程中:server可以同時接收多個client請求,只要能保證client 請求的log Entry不同即可; 但是在command進入server狀態機執行的時候,必須是順序進行。
如何改善multi-paxos性能?
multi-paxos的問題:
- 多個proposer進行時,會出現衝突和重試,降低系統chosen效率;
- 對每個選定的value需要進行2回RPC調用;
解決方案:
-
選擇learder。一次只有一個leader,這樣就不會有多proposer發生衝突
-
清除絕大多數的prepare RPC調用
- 進行一次prepare
- 大多數的log entry 能在一個RPC調用完成
-
如何選擇leader? 1.讓serverid最大的作爲leader; 2.每隔Tms,每個server發送心跳包給其他server, 3.如果server在2Tms內沒有收到比它大的server心跳,那麼它可以作爲leader,接受client 請求,擁有proposer角色 4.不是leader的server,拒絕client請求將請求重新定向到leader,acceptor角色。
-
怎麼處理prepare階段
- 爲什麼需要prepare階段?
- 阻塞舊Proposal:
- 將Proposal與logEntry slot關聯,每個Proposal number表示一個slot
- 查找可能chosen的value:
- 最大的acceptedProposal 的values;
- 當前log entry slot中,每個server的當前slot都沒有acceptedvalue,返回
noMoreAccepted
- 阻塞舊Proposal:
- 改進後:
- 如果acceptor返回
noMoreAccepted
,則跳過同slot當前server 後的所有的prepare RPC(直到accept拒絕Proposal,拒絕Proposal說明acceptro層接受過Proposal,存在acceptedvalue)。 - 如果leader得知
noMoreAccepted
,不需要使用prepare了,直接進入accpt階段。因爲所有server的該slot均爲空,直接通知acceptor accept Proposal。此時只需要一輪accept RPC。
- 如果acceptor返回
- 爲什麼需要prepare階段?
怎麼全備份? 目標:每個server上的備份都是全備份。
目標細分:
- log entry沒有full replication:full replication
- 只有proposer知道那個entry被chosen:所有server都知道那個entry被選中。
解決步驟:
- proposer在後臺一直accept 請求RPC,直到到所有server都回應。能保證大多數server都是full replication(爲什麼只是大多數?因爲有可能之前有server crash,有些log entry爲空,就算迴應一次,並不能把所有slot都都填充完整,操作布恩那個進入狀態機執行,不能達到full replication)
- track chosen entries
- 使得entries能被知道是否已經chosen:
acceptedEntry[i] = 無窮大
表示第i個entry被chosen。 - 每個server都維護一個
firstUnchosenIndex
,該索引是entry的索引,表示該索引前的entry均被chosen。
- 使得entries能被知道是否已經chosen:
- proposer(也就是leader)告知acceptor選擇entry:
- proposer在accept RPC時,發送
firstUnchosenIndex
。那麼acceptor知道entry索引小於firstUnchosenIndex
的均被chosen。 - acceptor標記同時滿足以下條件的entry爲chosen:
i<firstUnchosenIndex && accptedProposal[i] == proposal
【proposal相等即表示是同一個leader發送的proposal,又index < firstUnchosenIndex,可知前面均chosen,】
- proposer在accept RPC時,發送
- acceptor不能確認proposal number不同的log entry 是否chosen。
解決方案:acceptor在返回時,返回acceptor的
firstUnchosenIndex
,若proposer的firstUnchosenIndex
大於acceptor的firstUnchosenIndex
, 那麼proposer在後臺發送[success] RPC。
success(Index, v):
acceptedValue[index] = v;
acceptedProposal[index]=無窮大
return firstUnchosenIndex
客戶端協議
- 發送command給leader
- 如果leader down, 發送消息給任意的server
- 如果聯繫的server不是leader,自動重定向到leader
- leader在command被log entry選中後,在leader的狀態機中執行,執行出結果,然後迴應client
- 請求超時
- clinet請求別的server
- 重定向leader server
- 重新請求command
- 如果leader在執行後,響應client前crash,一條command不能被執行兩次
- client爲每個command提供唯一的標識
- server 在log entry中保存command id
- 狀態機保最近的每個client的commandid
- 執行command前,狀態機檢查command是否已經執行過,執行過,直接忽略並返回old command的執行結果。
如果client在發送command後,接受響應前crash, 然後重新登陸,會遇到同樣的問題。client會提交兩次command,使用上述措施可以保證每條command只執行一次。
配置修改
系統配置:serverid和address 這些直接會改變quorum。造成系統的majority數量的改變。
爲什麼需要系統設置變化:
- server crash
- replication change
安全原則:在配置變動時,避免對同一個log entry 有不同數量的majority和不同的value選擇。
解決方案:使用paxos中log entry管理配置變動
- 配置保存爲log entry
- 和其他log entry一樣備份
- 在choseing log entry i 時 使用log entry index爲i-a作爲系統配置。(其中a爲系統參數,在系統啓動時定義)
引入a後:
- 系統的併發數量必須在a以內:因爲在log entry i 被chosen後才能 chosen entry a+i; 可以使用一些無操作的command加速配置改變。
核心爲代碼
核心邏輯僞代碼:
--- Paxos Proposer ---
proposer(v):
while not decided:
choose n, unique and higher than any n seen so far
send prepare(n) to all servers including self
if prepare_ok(n, na, va) from majority:
v’ = va with highest na; choose own v otherwise
send accept(n, v’) to all
if accept_ok(n) from majority:
send decided(v’) to all
--- Paxos Acceptor ---
acceptor state on each node (persistent):
np --- highest prepare seen
na, va --- highest accept seen
acceptor’s prepare(n) handler:
if n > np
np = n
reply prepare_ok(n, na, va)
else
reply prepare_reject
acceptor’s accept(n, v) handler:
if n >= np
np = n
na = n
va = v
reply accept_ok(n)
else
reply accept_reject