問題:讓多個節點達成一致
假設你有 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 收到客戶端寫入後:
- 寫入自己的 log(uncommitted)
- 同步複製給其他節點
- 多數派確認收到後 → commit
- 通知 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 的設計選擇
| 節點數 | 需要多少票 | 最多容錯節點數 |
|---|---|---|
| 3 | 2 | 1 |
| 5 | 3 | 2 |
| 7 | 4 | 3 |
為什麼用奇數節點:偶數節點在 split vote 時可能永遠選不出 Leader(2/4 vs 2/4)。奇數確保多數派唯一。
為什麼 5 個節點比 3 個更常見於生產環境:3 個節點最多容錯 1 個——任何一個節點不可用就可能影響寫入。5 個節點能容錯 2 個,維護時更有彈性。