問題:讓多個節點達成一致

假設你有 3 個資料庫節點,所有節點要對「目前的 leader 是誰」達成一致。

聽起來簡單,但:

  • 任何節點可能在任何時刻崩潰
  • 任何訊息可能延遲或遺失
  • 節點崩潰和訊息延遲看起來一樣(你不能確定對方是掛了還是很慢)

在這種情況下,讓多個節點就某個值達成一致、不衝突——這就是 consensus 問題


Paxos:歷史上第一個實用的解法

Leslie Lamport 在 1989 年提出(2001 才廣為人知)。核心思想:

Phase 1: Prepare
  Proposer → 所有 Acceptors:「我提議 ID = 42,你能接受嗎?」
  Acceptors → Proposer:「可以,我承諾不接受 ID < 42 的提議」

Phase 2: Accept  
  Proposer → 多數派 Acceptors:「請接受值 V」
  Acceptors 接受並通知 Learners

Quorum(多數派):n 個節點需要 (n/2 + 1) 個同意,確保任意兩個 quorum 至少有一個節點重疊——不會出現兩組人各自決定不同的值。

Paxos 的問題:極難實作正確。Lamport 自己說「Paxos 是出了名的難以理解」。Multi-Paxos(處理連續決策)的完整實作更複雜,工程上容易出 subtle bug。


Raft:可理解性優先的 Consensus

Diego Ongaro 在 2014 年設計 Raft,目標就是:比 Paxos 更容易理解和實作

Raft 的三個子問題

1. Leader Election

每個節點有三種狀態:Follower、Candidate、Leader。

正常運作:
  一個 Leader,其他都是 Follower
  Leader 週期性發送 Heartbeat

Leader 崩潰:
  Follower 等待 election timeout(150~300ms 隨機)
  沒收到 Heartbeat → 自己變 Candidate,遞增 term,發起投票
  獲得多數派票 → 成為新 Leader

隨機 election timeout 確保大多數時候只有一個 Candidate,避免 split vote。

2. Log Replication

Leader 收到客戶端寫入後:

  1. 寫入自己的 log(uncommitted)
  2. 同步複製給其他節點
  3. 多數派確認收到後 → commit
  4. 通知 Follower 也 commit

保證:如果 entry 在某個 term 被 commit,後來所有 term 的 Leader 一定包含這個 entry。

3. Safety

Raft 保證:已 commit 的 entry 不會被覆蓋。選出的 Leader 一定包含所有已 committed 的 log——投票時 Candidate 要比較 log,log 更新的才能得票。


實際系統怎麼用

etcd:Kubernetes 的核心儲存,用 Raft 保證所有節點對「cluster 狀態」達成一致(哪些 Pod 在哪個節點、ConfigMap、Secret)。

ZooKeeper:用 ZAB(ZooKeeper Atomic Broadcast,類 Paxos)。Kafka 早期用 ZooKeeper 做 partition leader election,後來改用 KRaft(Kafka 自己的 Raft 實作)。

Consul:HashiCorp 的 service discovery,用 Raft 儲存服務發現資料和 key-value store。


Quorum 的設計選擇

節點數需要多少票最多容錯節點數
321
532
743

為什麼用奇數節點:偶數節點在 split vote 時可能永遠選不出 Leader(2/4 vs 2/4)。奇數確保多數派唯一。

為什麼 5 個節點比 3 個更常見於生產環境:3 個節點最多容錯 1 個——任何一個節點不可用就可能影響寫入。5 個節點能容錯 2 個,維護時更有彈性。