分散式協調服務 ZooKeeper (ZooKeeper)
ZooKeeper 是一個分散式協調服務,不是資料庫。它提供一個強一致、高可用的「真相來源」,讓幾十、幾百台伺服器在「誰是 leader、設定是什麼、誰還活著」這些問題上達成共識。
核心是三個 primitives —— 階層式 ZNode(資料模型)+ ensemble(多數決叢集)+ watches(變更通知) —— 用它們組合出服務發現、設定管理、leader 選舉、分散式鎖。
面試要點:它解決的是協調問題不是儲存問題(讀多寫少、資料 < 1MB),而且通常不該是你的第一選擇(雲端/etcd/Consul 更省事),只在深度基礎設施題或需要強一致鎖時才搬出來。
為什麼協調很難(動機)
從一個聊天 App 演進看協調問題如何冒出來。Terry 連 Server 1、Bohr 連 Server 2,Server 1 要轉訊息給 Bohr 必須先知道 Bohr 連在哪台。每個「土法」都會撞牆:
| 土法解決方案 | 撞到的牆 |
|---|---|
| 用一張 DB 表存對應 | DB 變單點故障;每次轉發都查一次 → 延遲高、百萬使用者把 DB 打爆 |
| 加快取優化 | 快取一致性問題:使用者換伺服器後快取還指向舊伺服器 → 訊息送錯/丟 |
| 伺服器互相廣播變化 | n² 條連線,幾百台時網路流量爆炸,擴展性極差 |
| heartbeat 偵測故障 | 網路分區:Server 1 以為 Server 2 死了其實沒死 → 狀態認知不一致 |
這些底層挑戰 —— 服務發現、設定共享、故障偵測、leader 選舉、分散式共識 —— 都需要能容忍部分故障、網路延遲、同時維持一致性的演算法,自己寫極難寫對。ZooKeeper 就是把這組難題封裝成簡單 primitives。
與其讓 N 台伺服器兩兩協調(n² 複雜度、各自不一致),不如全部連到一個強一致的中央協調者,由它提供單一視圖並主動通知變化。
三大基礎 primitives
可以把 ZooKeeper 想成一個「同步的 metadata 檔案系統」—— 每個連上的節點看到同一份資料。
1. 資料模型:ZNode(階層式命名空間)
資料組織成一棵樹,節點叫 ZNode,由 path 識別(如 /app1/config)。與檔案系統資料夾不同,ZNode 同時能存資料又能有子節點,但資料很小(< 1MB),數量很大(成千上萬)。
/chat-app
├── /servers (目錄)
│ ├── /server1 → "192.168.1.101:8080" [ephemeral]
│ ├── /server2 → "192.168.1.102:8080" [ephemeral]
│ └── /server3 → "192.168.1.103:8080" [ephemeral]
├── /users
│ ├── /terry → "server1" [ephemeral]
│ └── /bohr → "server2" [ephemeral]
└── /config
├── /max_users → "10000" [persistent]
└── /message_rate → "100/sec" [persistent]
三種 ZNode,各有用途:
| ZNode 類型 | 行為 | 典型用途 |
|---|---|---|
| Persistent | 一直存在直到被明確刪除 | 設定(max message size、限流參數) |
| Ephemeral | 建立它的 session 結束就自動刪除(斷線/逾時) | 「誰還活著」:伺服器/使用者上線狀態 |
| Sequential | 名稱自動加上單調遞增序號(msg- → msg-0000000001) |
訊息排序、leader 選舉、分散式鎖 |
「故障偵測」幾乎免費:伺服器掛掉 → session 逾時 → 它建立的所有 ephemeral nodes 自動消失 → watch 它們的伺服器收到通知。不用自己寫 heartbeat 邏輯,也不會因為網路分區而對「誰死了」各說各話(由 ensemble 統一裁決)。
2. 伺服器角色與 Ensemble
ZooKeeper 不跑單機(那就變單點故障),跑在一群伺服器組成的 ensemble 上。生產環境用 3、5 或 7 台(奇數在「多數決」時較好處理)。
| 角色 | 職責 |
|---|---|
| Leader | 處理所有寫入請求,透過 ZAB 協調更新傳播到 followers |
| Follower | 跟隨 leader;直接服務讀取(用本地記憶體副本) |
只要還有 quorum(多數) 存活,ensemble 就能運作。容錯能力:
3 台 → 容忍 1 台故障 (quorum = 2)
5 台 → 容忍 2 台故障 (quorum = 3)
7 台 → 容忍 3 台故障 (quorum = 4)
4 台和 3 台容錯能力相同(都只容忍 1 台),但 4 台要 3 票才達 quorum、寫入更慢、還多一台維運成本。加偶數台只增成本不增容錯,所以一律用奇數。
3. Watches:知道什麼時候變了
Watch 讓伺服器在 ZNode 變動時收到一次性回呼通知,取代不斷輪詢或 n² 廣播。
沒有 watch:每次送訊息都問 ZooKeeper「收件人在哪」
→ 延遲高 + 每秒幾千次查詢把 ZooKeeper 打成瓶頸
有 watch: Server 1 對 /chat-app/users 設一個 watch
Bohr 從 Server 2 換到 Server 3 → Server 3 更新 ZNode
→ ZooKeeper 主動回呼 Server 1 → 只有關心的伺服器被通知
一個 watch 觸發回呼後就失效,要繼續監聽必須在回呼裡重新註冊。這個細節常被忽略,會導致漏掉後續變更。
這也支撐了一個關鍵模式:伺服器在本地快取 ZooKeeper 狀態,平時查本地快取(零延遲),有變動才被通知更新快取。正因如此 ZooKeeper 是「協調服務」而非「資料庫」—— 它設計來通知變化,不是扛高流量讀取。
四大核心能力
聊天 App 只是例子,ZooKeeper 的四類能力適用所有分散式系統:
Configuration Management(設定管理)
存「執行時可能會變的動態設定」(連線字串、feature flags、服務端點),變更時 ZooKeeper 通知所有 watch 的服務,不用重啟就能更新行為。
set /chat-app/config/enable_reactions "true"
→ 所有 watch 這個節點的伺服器收到通知 → 即時生效
特性:即時傳播、版本化(每 ZNode 有版本號)、原子更新。
只在部署時才變的靜態設定放檔案/環境變數就好,不要塞 ZooKeeper。單一雲端廠商內優先用原生方案(AWS Parameter Store、Azure App Configuration)。
Service Discovery(服務發現)
服務上線時用 ephemeral node 註冊端點,下線/當機時節點自動過期。支撐動態擴縮、負載平衡、健康檢查。
/streaming/services/video-transcoder
├── /instance1 → "10.0.0.1:8080"
└── /instance2 → "10.0.0.2:8080"
新服務找 transcoder:① 讀子節點 ② 連其中一個 ③ 設 watch 監聽變動
現代多用 Consul / etcd / Kubernetes Services / AWS Cloud Map,但模式與 ZooKeeper 相同,有些內部仍用 ZooKeeper。
Leader Election(leader 選舉)
用 sequential ephemeral node 實作,乾淨俐落:
每台建立 sequential ephemeral node:
Server 1 → /leader/node-0000000001 ← 序號最小 = LEADER
Server 2 → /leader/node-0000000002 ← watch node-...001
Server 3 → /leader/node-0000000003 ← watch node-...002
Server 1 掛掉 → node-...001 消失 → Server 2 收到通知 → 成為新 leader
→ Server 3 改 watch node-...002
關鍵:每台只 watch 序號比自己小一號的那個(不是全部 watch leader),避免 leader 掛掉時的「羊群效應 / herd effect」—— 一個事件喚醒所有人。提供自動 failover + 保證同時只有一台執行關鍵操作。HBase、早期 Kafka(controller broker)都用這招。
應用層:序號最小的 sequential ZNode 當 leader。
ZooKeeper 內部(ZAB):選擁有最新 transaction 歷史的當 leader(並列則 ID 最高)。
這正好說明「建在 ZooKeeper 上的模式」和「ZooKeeper 自己的機制」可以不同。
Distributed Locks(分散式鎖)
也是 sequential ephemeral node,邏輯與 leader 選舉幾乎一樣:
想拿鎖 → 建 sequential ephemeral node
最小序號者持有鎖;其餘各 watch「比自己小一號」的節點
持有者釋放(delete)或當機 → 節點消失 → 下一位收到通知接手
ephemeral 特性讓鎖自帶死活偵測:持有者當機,鎖自動釋放,不會永久卡死。
| 維度 | ZooKeeper 鎖 | Redis 鎖(Redlock) |
|---|---|---|
| 一致性 | 強一致,正確性優先 | 較弱,效能/簡單優先 |
| 適合場景 | 金融交易、長時間持有(數小時)、階層鎖 | 票務、短鎖、高吞吐 |
| 故障偵測 | ephemeral 自動釋放,穩健 | 需仔細處理 timeout / heartbeat |
| 高頻上鎖 | 不適合(每秒幾百次就吃力) | 適合 |
正確性 > 效能 → ZooKeeper;效能/簡單 > 強一致 → Redis。詳見 09-Messaging-Coord/04-Distributed-Lock。
底層怎麼運作(面試通常不深挖,但概念通用)
ZAB 共識協定
ZooKeeper 幫別人解決協調問題,但它自己作為分散式系統也得先解決自己的協調 —— 靠 ZAB(ZooKeeper Atomic Broadcast)。兩階段:
- Leader Election:啟動或 leader 掛掉時投票選新 leader(看 transaction 歷史最新者)。
- Atomic Broadcast:寫入全送 leader,leader broadcast 給 followers,多數持久化後才算成功。
Server 2 要建 /chat-app/users/bohr :"server2"
1. Server 2 → ZK Leader:「建這個 ZNode」
2. ZK Leader → 所有 Followers:「提議:加 bohr 節點」(propose)
3. Followers → Leader:「已寫入 transaction log」(需 quorum 確認)
4. Leader → Server 2:「建立成功」(commit)
ZAB 與 Paxos / Raft 同類(ZAB 比 Raft 早),高層目標都是分散式共識。詳見 05-Database-Advanced/03-Replication。
一致性保證
| 保證 | 含義 |
|---|---|
| Sequential Consistency | 同一客戶端的更新依送出順序套用 |
| Atomicity | 更新全成功或全失敗,沒有「寫一半」 |
| Single System Image | 不論連哪台,同步後看到同一份視圖;所有寫入經 leader |
| Durability | 更新一旦套用就持久,伺服器重啟也不丟 |
| Timeliness | 客戶端的視圖在有限時間內會更新到最新 |
Follower 用本地副本服務讀取以追求高吞吐,但可能還沒跟 leader 同步完 → 讀到 stale data。需要最強一致時,客戶端在讀前先做 sync 操作確保跟上。這是 ZooKeeper 用「讀寫分離」換吞吐量的取捨。
讀寫不對稱(讀多寫少優化)
讀取:任一台用本地記憶體副本直接服務 → 高吞吐、低延遲
寫入:必須送 leader → ZAB 傳播到 quorum → 比讀貴很多
ZooKeeper 在「讀寫比約 10:1」的負載表現最好
Session 與儲存
| 機制 | 說明 |
|---|---|
| Session | 客戶端連線時建立,逾時可設(常見 10–30 秒) |
| Heartbeat | 客戶端週期送 heartbeat 維持 session;逾時內沒收到 → 視為故障 |
| Session 恢復 | 斷線後在逾時前連到另一台可恢復同一 session |
| Session 過期 | 該客戶端的所有 ephemeral nodes + watches 全清除 |
| Transaction Log | 每個變更先寫 WAL(write-ahead log)才回覆 → 確保不丟 |
| Snapshot | 週期對記憶體 DB 快照,重啟時載 snapshot + 重放 log 還原 |
太短:暫時網路抖動造成不必要的 session 過期/failover。太長:真正故障要等很久才偵測到。另外 transaction log 是效能瓶頸(同步落盤才回覆),生產環境建議獨立磁碟裝置;heap 設好避免 swap(一個請求 swap 到磁碟會拖慢後面整排有序請求)。
故障處理與網路分區
Follower 掛 → 只要還有 quorum,leader 照常運作
Leader 掛 → 自動觸發 ZAB leader 選舉(ID 最高 follower 接任)
客戶端掛 → session 逾時後其 ephemeral nodes 自動刪除 → watch 者被通知
網路分區 → 沒有任何一邊能形成 quorum 時,ZooKeeper 拒絕寫入直到恢復
網路分區時寧可拒絕寫入也不犧牲一致性,藉此避免 split-brain(系統不同部分對狀態看法不一)。代價是分區期間可用性下降 —— 這正是協調服務該有的取捨:寧可不可用,也不能給出兩個互相矛盾的「leader」。對照 02-Distributed-Systems/02-CAP-Theorem。
ZooKeeper 在現代的位置
2008 年問世,至今久經考驗但已非唯一選項。
現況
| 仍在用 ZooKeeper | 正在/已遷出 |
|---|---|
| HBase、Hadoop、SolrCloud、Storm、NiFi、Pulsar、ClickHouse | Kafka 改用內建 KRaft(Kafka Raft Metadata) |
Kafka 從 ZooKeeper → KRaft 是分散式系統演進的標誌性轉折:降低維運複雜度、移除擴展瓶頸、減少故障點,反映「系統傾向內建共識協定而非依賴外部協調服務」的趨勢。詳見 09-Messaging-Coord/03-Kafka。
替代方案
| 方案 | 定位 |
|---|---|
| etcd | 雲原生主流,Kubernetes 底層;強一致 KV,HTTP/JSON + gRPC API |
| Consul(HashiCorp) | 協調 + 服務發現 + 健康檢查 + KV,還能自動化 LB/防火牆設定 |
| 雲端託管 | AWS Parameter Store、Azure App Configuration、Amazon MSK(託管 ZK) |
限制(基礎設施面試重點)
| 限制 | 說明 |
|---|---|
| Hot Spotting | 大量客戶端 watch 同一 ZNode(leader 選舉/鎖)→ 通知壓垮伺服器,熱門節點變瓶頸 |
| 效能限制 | 寫入貴(必經 leader 到 quorum);記憶體儲存模型限制資料量(ZNode < 1MB、全集要進記憶體) |
| 維運複雜度 | 要調 Java 參數、磁碟配置、監控 timeout/連線數 —— 「用起來簡單,維運起來複雜」 |
面試話術
「除非是深度基礎設施設計題(設計分散式 MQ / 任務排程器),需要多台伺服器謹慎協調,或需要強一致鎖 / 階層鎖這類現代 LB + 服務發現工具給不了的能力,否則我不會把 ZooKeeper 當第一選擇 —— 雲端託管或 etcd/Consul 維運更省事。」這句話展示你知道它的取捨,而不是只會背名詞。
什麼時候值得提 ZooKeeper:
- 特定基礎設施題:「設計分散式訊息佇列」「設計分散式任務排程器」—— ZooKeeper 當共識「大腦」:broker 註冊、topic metadata、每 partition 的 leader 選舉、consumer group rebalance、broker 故障偵測(ephemeral node 消失觸發重分配)。這就是 KRaft 之前 Kafka 的做法。
- 持久 / 階層分散式鎖:分散式檔案系統要巢狀鎖(鎖目錄 + 其檔案)並避免死鎖 —— ZooKeeper 能同時 watch 多節點、監控整個鎖階層,比 Redis 鎖合適。
- Smart Routing(Staff+ 進階):Facebook Live Comments / YouTube Live Chat 想讓同一直播觀眾連同一組伺服器(最小化跨機通訊)。ZooKeeper 當 API gateway 協調點,維護「哪個房間由哪台處理」的對應,新連線時查 ZooKeeper 決定最佳伺服器。
熱門聊天 App 不要為每個使用者建 ZNode。改用 consistent hashing:伺服器在 ZooKeeper 註冊,使用者用 user ID hash 對應到伺服器。ZooKeeper 只追蹤伺服器(不追蹤百萬使用者),擴展性更好,仍保有 ephemeral server node 的快速故障偵測。詳見 02-Distributed-Systems/04-Consistent-Hashing。
常見 Deep Dive
不是。它是協調服務,設計來「通知系統有變化」而非扛高流量讀取。它把整個資料集放記憶體以追求低延遲讀,所以單一 ZNode < 1MB、總資料集要能進記憶體。要存大量資料/扛高寫入,請用真正的資料庫,ZooKeeper 只存 metadata 與協調狀態。
Ephemeral node 綁定建立它的 session。客戶端靠 heartbeat 維持 session;當機/斷線超過 session timeout(10–30 秒)→ session 過期 → 它的所有 ephemeral node 由 ensemble 統一刪除 → watch 者收到通知。關鍵是「誰死了」由強一致的 ensemble 裁決,不會像自建 heartbeat 那樣因網路分區而各說各話。
天真做法是所有候選都 watch 同一個 leader 節點 → leader 掛掉瞬間喚醒全部、再一起競爭。ZooKeeper 模式讓每台只 watch 序號比自己小一號的那個:leader(最小序號)掛掉,只有「次小」那台被喚醒接任,其餘不受打擾。鎖也用同樣技巧,把 O(n) 喚醒降成 O(1)。
為了讀吞吐,follower 用本地記憶體副本直接回應讀請求,但它可能還沒套用 leader 最新的寫入(ZAB 傳播有延遲)。要最強一致,客戶端在讀前呼叫 sync,強制該 follower 先追上 leader 再回應。這是 ZooKeeper 用「讀寫分離」換吞吐的明確取捨 —— 預設給你「最終會一致 + 自身寫後讀有序」,要線性一致得自己加 sync。
同一類共識協定,目標都是讓多副本在故障/分區下對全序更新達成一致。ZAB 比 Raft 早、實作細節不同(ZAB 更聚焦「primary-backup 原子廣播」),但高層概念一致:選一個 leader 排定全序、靠 quorum 確認 commit、兩階段保證原子與持久。理解任一個就能類推其他。
多年依賴外部 ZooKeeper 帶來三個痛:① 維運複雜度(要另外運維一個 ZK 叢集)② 擴展瓶頸(partition/metadata 多到一定規模 ZK 吃緊)③ 多一個故障點與一致性邊界(Kafka 與 ZK 兩套狀態要對齊)。KRaft 把共識內建進 Kafka(用 Raft 管 metadata),單一系統、單一一致性模型。這反映業界趨勢:自帶共識 > 依賴外部協調服務。
先問「正確性還是效能優先」。錢相關 / 不能雙重持有 / 長時間鎖 / 階層鎖 → ZooKeeper(強一致 + ephemeral 自動釋放 + 多節點 watch)。高吞吐 / 短鎖 / 可容忍極端情況偶發雙持 → Redis(簡單、快)。再補一句 fencing token:無論哪種鎖,下游資源都應驗證單調遞增的 fencing token 防止「過期持有者」誤寫。詳見 12-Ops-and-Reliability/01-Dealing-With-Contention。
自我測驗重點
| 問題 | 重點 |
|---|---|
| 三大 primitives | ZNode(資料)+ ensemble(多數決)+ watches(通知) |
| 三種 ZNode | persistent(設定)/ ephemeral(存活)/ sequential(排序+鎖+選舉) |
| ephemeral 的價值 | session 結束自動刪 → 免費故障偵測 |
| 為什麼用奇數台 | 偶數不增容錯只增成本;3→1、5→2、7→3 容錯 |
| 四大能力 | 設定管理 / 服務發現 / leader 選舉 / 分散式鎖 |
| leader 選舉避免羊群效應 | 只 watch 序號小一號的節點,非全部 watch leader |
| 應用層 vs ZAB 選舉方向 | 應用層序號最小當 leader;ZAB 選 transaction 最新者 |
| CAP 取捨 | CP:分區時拒寫避免 split-brain |
| 讀可能 stale | follower 本地讀;要強一致先 sync |
| ZooKeeper 鎖 vs Redis 鎖 | 強一致/長鎖/階層 → ZK;高吞吐/短鎖/簡單 → Redis |
| Kafka 為何遷 KRaft | 降維運/移瓶頸/減故障點,內建共識取代外部協調 |
| 三大限制 | hot spotting / 寫入貴 + 資料量受限 / 維運複雜 |
| 何時別用 | 非深度基礎設施題就別搬;雲端託管 / etcd / Consul 更省事 |
Related Notes
- 09-Messaging-Coord/04-Distributed-Lock — ZooKeeper 用 sequential ephemeral node 實作分散式鎖;與 Redis 鎖的取捨對照
- 09-Messaging-Coord/03-Kafka — 早期 Kafka 用 ZooKeeper 管 partition leadership,後改 KRaft 內建共識
- 09-Messaging-Coord/02-Queue — 「設計分散式訊息佇列」題裡 ZooKeeper 當共識大腦(broker 註冊 / partition 選舉)
- 09-Messaging-Coord/01-Data-Pipeline — 串流管線常依賴 ZooKeeper 做 broker / metadata 協調
- 05-Database-Advanced/03-Replication — ZAB 與 Raft/Paxos 同類,quorum 寫入與 leader-based 複製概念相通
- 02-Distributed-Systems/02-CAP-Theorem — ZooKeeper 是 CP 系統:分區時拒寫保一致、避免 split-brain
- 02-Distributed-Systems/04-Consistent-Hashing — 百萬使用者用一致性雜湊對應到伺服器,ZooKeeper 只追蹤伺服器
- 12-Ops-and-Reliability/01-Dealing-With-Contention — 分散式鎖與 fencing token 防止過期持有者誤寫
- 07-Caching-Storage/04-Redis — Redis 分散式鎖是 ZooKeeper 鎖的常見替代(效能優先場景)
- 11-Infrastructure/01-API-Gateway — Smart Routing:ZooKeeper 當 gateway 協調點維護「房間→伺服器」對應