分散式協調服務 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 選舉、分散式鎖
Ephemeral 是 ZooKeeper 的殺手鐧

「故障偵測」幾乎免費:伺服器掛掉 → 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 是「一次性觸發」

一個 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)都用這招。

應用層 vs ZooKeeper 內部選舉方向相反

應用層:序號最小的 sequential ZNode 當 leader。
ZooKeeper 內部(ZAB):選擁有最新 transaction 歷史的當 leader(並列則 ID 最高)。
這正好說明「建在 ZooKeeper 上的模式」和「ZooKeeper 自己的機制」可以不同。

Distributed Locks(分散式鎖)

也是 sequential ephemeral node,邏輯與 leader 選舉幾乎一樣:

想拿鎖 → 建 sequential ephemeral node
最小序號者持有鎖;其餘各 watch「比自己小一號」的節點
持有者釋放(delete)或當機 → 節點消失 → 下一位收到通知接手

ephemeral 特性讓鎖自帶死活偵測:持有者當機,鎖自動釋放,不會永久卡死。

ZooKeeper 鎖 vs Redis 鎖(高頻出題)

維度 ZooKeeper 鎖 Redis 鎖(Redlock)
一致性 強一致,正確性優先 較弱,效能/簡單優先
適合場景 金融交易、長時間持有(數小時)、階層鎖 票務、短鎖、高吞吐
故障偵測 ephemeral 自動釋放,穩健 需仔細處理 timeout / heartbeat
高頻上鎖 不適合(每秒幾百次就吃力) 適合

正確性 > 效能 → ZooKeeper;效能/簡單 > 強一致 → Redis。詳見 09-Messaging-Coord/04-Distributed-Lock


底層怎麼運作(面試通常不深挖,但概念通用)

ZAB 共識協定

ZooKeeper 幫別人解決協調問題,但它自己作為分散式系統也得先解決自己的協調 —— 靠 ZAB(ZooKeeper Atomic Broadcast)。兩階段:

  1. Leader Election:啟動或 leader 掛掉時投票選新 leader(看 transaction 歷史最新者)。
  2. 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 timeout 是關鍵旋鈕

太短:暫時網路抖動造成不必要的 session 過期/failover。太長:真正故障要等很久才偵測到。另外 transaction log 是效能瓶頸(同步落盤才回覆),生產環境建議獨立磁碟裝置;heap 設好避免 swap(一個請求 swap 到磁碟會拖慢後面整排有序請求)。


故障處理與網路分區

Follower 掛  → 只要還有 quorum,leader 照常運作
Leader 掛    → 自動觸發 ZAB leader 選舉(ID 最高 follower 接任)
客戶端掛     → session 逾時後其 ephemeral nodes 自動刪除 → watch 者被通知
網路分區     → 沒有任何一邊能形成 quorum 時,ZooKeeper 拒絕寫入直到恢復
ZooKeeper 在 CAP 中選 CP

網路分區時寧可拒絕寫入也不犧牲一致性,藉此避免 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/連線數 —— 「用起來簡單,維運起來複雜」

面試話術

不要一上來就搬 ZooKeeper

「除非是深度基礎設施設計題(設計分散式 MQ / 任務排程器),需要多台伺服器謹慎協調,或需要強一致鎖 / 階層鎖這類現代 LB + 服務發現工具給不了的能力,否則我不會把 ZooKeeper 當第一選擇 —— 雲端託管或 etcd/Consul 維運更省事。」這句話展示你知道它的取捨,而不是只會背名詞。

什麼時候值得提 ZooKeeper:

百萬使用者別建百萬 ZNode

熱門聊天 App 不要為每個使用者建 ZNode。改用 consistent hashing:伺服器在 ZooKeeper 註冊,使用者用 user ID hash 對應到伺服器。ZooKeeper 只追蹤伺服器(不追蹤百萬使用者),擴展性更好,仍保有 ephemeral server node 的快速故障偵測。詳見 02-Distributed-Systems/04-Consistent-Hashing


常見 Deep Dive


自我測驗重點

問題 重點
三大 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 更省事