Apache ZooKeeper:分散式系統的協調核心
· tech
#distributed-systems#zookeeper
📑 目錄
上一篇共識帶到 Zab 是 ZooKeeper 的引擎,但 ZooKeeper 本身值得單獨講一篇。它是分散式系統的協調核心(coordination service):你很少直接用它,卻天天間接依賴它——舊版 Kafka、HBase、Hadoop 的高可用,底下都是它在撐。它最漂亮的地方,是把「分散式協調」這件難事,收斂成一棵像檔案系統的小樹加上幾個簡單原語,讓上層系統不必自己去碰共識那個泥沼。
它不是資料庫,是「協調」的專用工具
先擺正定位:ZooKeeper 不是拿來存資料的,它存的是少量、但極關鍵的中繼資料(誰是 leader、有哪些節點活著、設定是什麼),資料量是 KB 等級。它的價值不在容量,而在高可用 + 強一致 + 一組剛好夠用的協調原語。你把「一群機器怎麼對關鍵狀態達成一致」這個最難的問題外包給它,自己就能專心做業務。
資料模型:一棵像檔案系統的樹
ZooKeeper 對外的樣子出乎意料地簡單:一棵樹,每個節點叫 znode,用路徑定址(像 /services/node-1),每個 znode 能存一小塊資料、也能有子節點。真正的巧思在 znode 的三種型別和 watch:
這裡最關鍵的設計,是 ephemeral 節點綁定 session:client 和 ZooKeeper 之間維持一個有心跳的 session,一旦心跳停了(client 崩潰、網路斷)、session 逾時,它建立的所有 ephemeral 節點會自動消失。這一招把分散式系統裡最惱人的「怎麼知道某個節點死了」直接內建成資料模型的行為——你不用自己寫心跳偵測,只要看那個 ephemeral 節點還在不在。
架構:一個 ensemble,寫走 leader、讀就地
ZooKeeper 自己也是分散式的——它由一組(奇數台,通常 3 或 5)伺服器組成一個 ensemble,靠 Zab 選出一個 leader,其餘是 follower。讀寫走的是兩條很不對稱的路:
sync())。這個「寫強一致、讀可擴展但容許稍舊」的取捨,正是它能扛住海量協調流量的關鍵一致性上抓三個重點就夠:寫是線性化的(全序、過半才算數)、同一個 client 的操作依送出順序生效、而讀可能稍舊(有界的、不是任意舊)。這組保證對「協調」剛剛好——你要的是「對關鍵狀態達成一致」,不是「每次讀都毫秒級最新」。
怎麼用它:協調原語都是「znode + watch」拼出來的
ZooKeeper 只給你這些積木,不直接給你「鎖」或「leader 選舉」——那些是你用積木拼出來的「食譜(recipes)」。最經典的一道,是用 ephemeral + sequential 做分散式鎖 / leader 選舉:
同一套積木還能拼出其他協調原語:
- 服務發現 / 成員管理:每個服務在
/services下建一個 ephemeral 節點,它活著節點就在、它一死節點就消失——/services底下有哪些子節點,就是「現在誰還活著」的即時名單。 - 設定管理:把設定存成一個 znode,所有 client 對它下 watch;設定一改,大家立刻收到通知去重載,不用輪詢、也不用重啟。
- 屏障(barrier)、佇列:一樣是 znode + watch 的變形,協調「大家都到齊了才開始」這類同步點。
它的極限,與為什麼有些系統開始離開它
ZooKeeper 很強,但不是萬靈丹,用之前要知道它的邊界:
- 只放小中繼資料:它是協調服務,不是資料庫。znode 資料是 KB 級,別拿它存業務資料。
- 它本身是一套要維運的系統:多一個 ZooKeeper ensemble,就多一套要顧的叢集、一個共享的故障點;而且當上層的 metadata 操作量很大時,它會變成瓶頸。這正是 Kafka 走向 KRaft 的原因——把原本外包給 ZooKeeper 的 metadata 管理,用內建的 Raft 收回自己家,少養一套系統。
- 語意有陷阱:watch 是一次性的(觸發後要重新註冊,中間的變更可能漏看)、session 逾時與 ephemeral 節點消失的時機,都是實務上容易踩的坑。
另外補一個對照:ZooKeeper 的現代同類是 etcd(用 Raft),它是 Kubernetes 的狀態儲存核心——你可以把「ZooKeeper 之於 Kafka/HBase」類比成「etcd 之於 Kubernetes」:都是把最難的一致性,收斂到一個被驗證過的協調核心裡。
反思
ZooKeeper 是「別自己造輪子」最具體的化身
共識那篇我下過一個結論:別自己造共識輪子。ZooKeeper 就是那句話最實際的樣子——它把 Zab、leader 選舉、線性化寫這些硬核的東西全部封裝好,對你只露出「一棵樹 + 幾個原語」。我特別欣賞這種抽象的收斂:它沒有要你懂 Paxos/Zab,而是把那些理論的成果,包裝成連新手都能用的 create、watch、getChildren。好的基礎設施就該是這樣——把最難的複雜度吃進肚子裡,對外只吐出簡單。每次我想「這個分散式協調我自己寫一下就好」,都會想起 ZooKeeper 背後那些論文和踩過的坑,然後乖乖去用現成的。
「ephemeral + watch」把「偵測死亡」變成資料模型的副作用
ZooKeeper 設計裡我最拍案叫絕的,是 ephemeral 節點。分散式系統最惱人的問題之一,是「怎麼可靠地知道某個節點死了」——心跳、逾時、誤判,一堆坑。而 ZooKeeper 的解法是:把「活著」這件事,綁進資料模型——你活著,你的 ephemeral 節點就在;你一斷線,它自動消失。於是「偵測死亡」不再是一段你要自己寫的邏輯,而是查一下那個節點還在不在就好。這種「把一個難問題,轉化成另一個系統天生就會處理的形式」的思路,是我覺得最高級的一種工程設計——不是把問題解掉,而是讓它根本不需要被解。
協調核心的真正價值:把一致性收斂到一個地方
用久了 ZooKeeper(以及它的表親 etcd),我體會到一個架構層面的智慧:與其讓系統裡每個元件都自己處理一致性,不如把「需要強一致的那一小撮狀態」,全部收斂到一個專門的協調核心。這跟我在共識那篇說的「只讓最關鍵的狀態走共識」是同一件事的放大版——ZooKeeper 就是那個「唯一需要昂貴一致性」的地方,其他所有服務都變成它的 client,自己維持無狀態、可水平擴展。把最難的部分集中管理、其餘保持簡單,這不只是 ZooKeeper 的設計哲學,幾乎是所有耐得住規模的分散式架構共通的骨架。