Paxos 學習筆記2 - Multi-Paxos
圖片來自 John Ousterhout 的 Raft user study 系列課程
關於 Basic Paxos 可以看本人的這篇文章
? Multi-Paxos 論文裡對很多問題並沒有描述清楚,所以實踐裡往往用的是修改過的 Multi-Paxos 協議或 Raft 協議
用 basic paxos 實現 replicated log 的簡單思路
實現 replicated log 的最簡單思路就是對每個日誌項執行一個 basic paxos
- 在 basic paxos 請求的引數里加一個 index 引數
當提議者收到客戶端發來的一條指令,他會嘗試使用下一個沒有被選中的日誌項位置
比如 S1 從客戶端收到指令 jmp 後
- 會先嚐試日誌項 3,發現已經寫入了 cmp(但還沒有被選中),於是走 basic paxos 的兩階段協議提議在日誌項 3 中放置 cmp
- 等到 cmp 被選中後,發現日誌項 4 是空的,於是提議將在日誌項 4 中放置 jmp ,最終達成共識的不是 jmp(提議之間產生了衝突),於是 S1 就會去嘗試下一個日誌項,直到給 jmp 找到一個可以達成共識的位置
在這種方案中伺服器可以併發地處理多個客戶端請求,只要為他們選擇不同的日誌項,並獨立地執行 basic paxos 流程即可
如果一條指令和它之前的全部指令都已經被選中了,那麼就可以交給狀態機去執行了
可能的效能優化
- 使用領導人來避擴音議者之間的衝突
- basic paxos 的活鎖問題會在高負載的 replicated log 場景下變得更加嚴峻
- 消除多餘的 Prepare 請求
尚未解決的問題
- 確保完全複製
- basic paxos 中共識的結果只有提議者自己知道,但 replicated log 場景中每個伺服器都應該擁有一份已選中日誌的拷貝
- 客戶端協議
- 客戶端如何與 paxos 叢集互動
- 配置改變
- 伺服器加入或離開時,演算法應該做怎樣的改變
領導人選舉
Lamport 提出的選舉方法
- 讓 ID 最大的伺服器作為領導人
- 所有伺服器每隔 T ms 就發其他所有伺服器傳送心跳報文
- 如果一個伺服器在過去的 2T ms 裡都沒有收到其他比他 ID 更大的伺服器發來的心跳報文,他就作為領導人開始工作
- 接受客戶端請求
- 同時作為提議者和接受者
- 如果伺服器不是領導人
- 拒絕客戶端請求
- 只作為接受者
? Chubby 中是通過執行 basic paxos 投票選舉選出領導人,在執行過程中,主節點會通過不斷續租的方式來延長租期(Lease)
消除 Prepare 階段
回憶 Basic Paxos 中 Prepare 階段的用途
- 阻止那些更老的還未完成的提議被選中
- 看看有沒有已經被選中的值,如果有,提議者的提議就應該使用這個值
消除 Prepare 的設計:
- Prepare 請求中對整個日誌使用一個提議號,而不是針對每個日誌項用一個獨立的提議號
- 接受者回復 Prepare 請求時
- 回覆當前日誌項的最大已接收提議
- 即
acceptedProposal[index]
和acceptedValue[index]
- 即
- 檢視 index 之後的所有日誌項,如果沒有已接受的日誌項,就在回覆裡設定 noMoreAccepted
- 回覆當前日誌項的最大已接收提議
noMoreAccepted:
- 如果提議者收到了某個接受者傳送的 noMoreAccepted
- 就可以省略發給該接受者的任何 Prepare 報文
- 直到發給該接受者的某個 Accept 報文被拒絕
- 如果提議者收到了多數接受者傳送的 noMoreAccepted
- 就不需要傳送任何 Prepare 報文了,直接傳送 Accept 報文即可
? 減少演算法中非必要的協商步驟,優化系統效能
完全拷貝
使用上面的方法來實現 replicated log,當提議者發現某個日誌項被選中後,並沒有一種機制能保證該日誌項能拷貝給其他節點,由於我們使用了領導人,因此接受者是沒有任何主動的手段去獲得已選中日誌項的內容的,因此我們引入了下面的機制來保證日誌項被完全地拷貝
機制 1
領導人收到多數 Accept 回覆後
- 在後臺繼續重試傳送 Accept 報文,直到所有接受者都回復
- 不能保證完全拷貝,比如重試過程中領導人當機了,或者伺服器故障期間達成共識的日誌項,就沒法以這種方式同步過來
機制 2
所有節點都必須將已經被選中的日誌項記錄下來
- 通過將
acceptedProposal[i]
設定為 ∞- 表示該日誌項已經被選中了
- 因為只有提議編號大於等於 acceptedProposal 的情況下,該日誌項才會被覆蓋,如果是無窮大,該日誌項就再也不能被覆蓋了
- 維護一個 firstUnchosenIndex
- 表示最早的沒有被選中的日誌項下標
- 即第一個
acceptedProposal[i] != ∞
的日誌項
機制 3
領導人主動告知接受者哪些日誌項被選中了
- 在 Accept 請求中包含 firstUnchosenIndex,也就是告訴接受者小於 firstUnchosenIndex 的日誌項都已經被選中了
- 如果某個日誌項 i 滿足
i < request.firstUnchosenIndex
acceptedProposal[i] == request.proposal
- 提議號相同說明,日誌項中的內容和 Accept 請求來自同一個提議者,firstUnchosenIndex 又告訴我們這條日誌項已經被選中了,說明這條日誌項裡記錄的正好就是被選中的日誌內容
- 將滿足上述條件的所有日誌項標記為已選中
上圖中,日誌項 6 的下標小於 firstUnchosenIndex,且它的 acceptedProposal 等於請求的提議編號,因此可以將它標記為已選中
機制 4
通過上面的三種機制,並不能解決日誌拷貝中的全部問題,比如上圖的日誌項 4,他的 acceptedProposal 和 Accept 請求的提議號是不同的,說明這條記錄來自於其他提議者或來自於同一個提議者在較老的提議輪中提出的提議,這種情況下我們是無法判斷這條日誌項中的內容正確與否的,這種情況往往發生在領導人切換時
為了處理上一任領導人留下來的日誌項
- 接受者在 Accept 回覆中帶上他的 firstUnchosenIndex
- 如果領導人的 firstUnchosenIndex 大於回覆裡的 firstUnchosenIndex,說明存在這樣的不同步的日誌項,這時,領導人會傳送 Success RPC(在後臺)
Success RPC 的格式為 Success (index, v),也就是說在 index 的位置上,有已經被選中的提議值 v,收到 Success 報文後,接受者應該
acceptedValue[index] = v
acceptedProposal[index] = ∞
- 回覆給提議者新的 firstUnchosenIndex
- 如果需要,領導人會繼續傳送 Success RPC,直到和接受者達成同步
通常情況下,只有領導人切換時才需要執行這一步
客戶端協議
客戶端與 Paxos 叢集互動的方式
- 如果客戶端不知道領導人是哪臺機器
- 隨便找一臺伺服器傳送請求
- 如果接受請求的伺服器不是領導人,他就會將訊息轉發給領導人
- 只有請求被記錄到日誌中,並提交給領導人的狀態機執行結束後,領導人才會給客戶端回覆
- 如果請求超時(比如領導人崩潰的情況下)
- 客戶端將請求傳送給其他伺服器
- 請求最終會被轉發給新的領導人
- 新的領導人重試這條請求
這樣做的問題在於如果領導人崩潰前,已經執行了一次客戶端請求,只不過還沒來得及回覆給客戶端,這樣同一條客戶端請求就被執行了兩次
解決方法是客戶端在請求上帶上唯一 id,伺服器記錄已經執行過了哪些指令,狀態機執行新的指令前先檢查是否已經執行過了,如果執行過了就直接將結果回覆給客戶端
只要客戶端不崩潰,上述方法就可以保證 exactly-once 語義
更改配置
分散式系統中系統配置指的是
- 每臺伺服器的 ID、地址
- 多少伺服器能構成 quorum 中的多數
共識機制必須支援對系統配置的更改,比如移除掉故障的機器
安全性要求
配置更改期間,不能出現叢集中不同的部分針對某個日誌項選中了不同的值
- 比如圖中前兩個節點還在使用舊的配置,他們認為自己構成了多數節點,選擇了 v1
- 而後三個節點已經切換到了新的配置,他們也認為自己構成了多數節點,選擇了另一個值 v2
Paxos中配置更改的方式
使用日誌項來管理配置的更改
- 配置也被儲存為日誌項
- 也像其他日誌項一樣在伺服器間拷貝
- 日誌項生效有延遲,放到日誌項 i 中的配置,要在選擇日誌項 i+α 時才能生效
- α 限制了 paxos 的併發度,只有選中了日誌項 i 後,我們才可以開始選擇日誌項 i+α
- 如果想要配置更改快點生效,可以發出多條空指令