ZooKeeper: 簡介, 配置及運維指南

張浮生發表於2018-06-26

1. 概覽

ZooKeeper是一個供其它分散式應用程式使用的軟體, 它為其它分散式應用程式提供所謂的協調服務. 所謂的協調服務, 是指ZooKeeper的如下能力

  1. naming 命名
  2. configuration management 配置管理
  3. synchronization 同步
  4. group service 分組服務

上面四個功能可能現在不太好說清, 但大致上目前你需要明白ZooKeeper就是為其它分散式應用程式提供一些基礎功能的程式就好了. 我們以其中的配置管理為例. 假設你在寫一個可橫向擴容的叢集應用程式, 在你的預想裡, 你的應用程式最終將部署到成千上萬臺機器上去, 那麼, 這成千上萬臺機器上的成千上萬個例項如何保持配置檔案的一致性呢? 簡單: 你的分散式應用程式不要自己處理配置管理的問題, 而是把配置管理交給ZooKeeper去做, 如果某個例項需要取配置檔案, 去訪問ZooKeeper就好了, ZooKeeper向你保證一致性.

並且用上面說到的四種功能, 還能組合實現出其它高階玩法, 比如:

  1. implement consensus. 在需要一致性的時候, 由ZooKeeper實現一致性
  2. group management. 分組管理
  3. leader election. 叢集選爹或組內選爹
  4. presence protocols. 探測叢集中各個例項的死活

下面各個章節將詳細敘述相關內容.

需要說明的是, ZooKeeper本身的官方文件雖然質量上乘(Apache產品的一貫風格), 但內容上並不完整, 我的這篇譯文也有兩個缺陷: 一是可能錯譯, 由於我本身對ZooKeeper的理解出現了偏差導致的. 二是可能有廢話, 我並不是照章一字一句翻譯的. 總之請作為參考來閱讀, 不要作為標準來閱讀.

1.1 簡介

1.1.1 一個為分散式應用程式提供服務的 分散式協調服務

ZooKeeper是:

  1. 開源的
  2. 為其它分散式應用程式提供服務的
  3. 它本身也是分散式的
  4. 向其它創面式應用程式提供協調功能

ZooKeeper對外提供了一系列的原語(primitives), 其它分散式應用程式利用這些原語, 可以很方便的實現一些高階抽象功能, 比如: 同步, 配置管理維護, 分組, 命名等. ZooKeeper的設計宗旨就有兩條:

  1. 簡單易用, 使用時的編碼難度低.
  2. 使用樹型邏輯結構組織資料.

ZooKeeper是用Java寫的, 其對外的API有Java版本的, 也有C的binding版.

1.1.2 ZooKeeper的設計原則

ZooKeeper是簡單易用的

如果將使用ZooKeeper的分散式應用程式看做是千千萬萬個獨立的程式例項, ZooKeeper則是構建了一個共享的, 層級式的名稱空間(shared hierarchal namespace)以供這千千萬萬個程式例項去訪問. 這個所謂的層級式的名稱空間邏輯上和*nix中的檔案系統很像. 名稱空間資料暫存器(data register)構成, 資料暫存器也被稱謂節點(node), 類比於檔案系統的話, 節點相當於檔案系統中的檔案, 或目錄. 不過與檔案系統不同的是: 檔案系統是在持久化儲存裝置上抽象資料儲存的一層, 但ZooKeeper是把所有資料都放在記憶體中的.

ZooKeeper是既快又穩的

資料全扔在記憶體裡, 保證的ZooKeeper很快, 效能很好. ZooKeeper的設計實現中, 除了簡單易用, 還把以下三點列進了宗旨裡:

  1. 效能要高. 要快, 所以資料扔記憶體裡.
  2. 要高可用. 要穩, 所以ZooKeeper本身是分散式的, 並且掛掉一小半例項都不影響服務.
  3. 嚴格有序的訪問. 保證客戶端使用原語可以實現可靠的同步服務.

ZooKeeper本身是分散式的

ZooKeeper本身是分散式的. 我們把ZooKeeper寄生的多臺機器集合稱為一個ensemble, 這個詞不是很好翻譯, 乾脆不翻譯了, 這個單詞的意思和叢集差不多.

在下圖中, 每個Server是一個ZooKeeper例項程式, 每個Client是一個使用ZooKeeper服務的客戶端, 這個客戶端一般是另外一個分散式程式的一個例項. 一個有效的ZooKeeper服務是由多個Server構成的(實際上並沒有限定每個Server獨佔一臺機器). 每個ZooKeeper服務中的Server都必須知道其它所有Server的位置. 每個Server都在記憶體中儲存了一些資料(in-memory image of state), 並且在磁碟上儲存著事務日誌(transaction logs)快照(snapshot). 只要整個ZooKeeper服務中的大多數Server正常工作著, 那麼整個Server就能正常的向外提供服務.

ZooKeeper: 簡介, 配置及運維指南

而對於ZooKeeper的使用者, 也就是圖中的Client來說, 也就是另外一個分散式應用程式(中的一個例項), 要使用ZooKeeper的服務, 只需要通過TCP連線到ZooKeeper服務中的一個Server上即可, 將這個連線置為長連線, 由Client負責TCP連線的維護, 並通過這個TCP連線向Server傳送請求, 接收回應, 傳送心跳, 獲取監控事件等. 當Server不幸掛掉的時候, Client需要自己來將連線切換到另外一個Server上去.

ZooKeeper是嚴格有序的

ZooKeeper對每次內部變更都有一個版本戳一樣的機制, 這也就是簡單的事務機制. 它保證了ZooKeeper整個服務對外提供的原語是原子性的, 並且是嚴格有序的.

ZooKeeper很快

當讀寫比例超過10:1的時候, ZooKeeper的效能表現很好. 低於這個比例的話, 可能就不是很適合使用ZooKeeper了.

1.1.3 ZooKeeper中的資料模型與層級式名稱空間

ZooKeeper中的層級式名稱空間和檔案系統很類似, 每個節點都有獨一無二的一個路徑, 和檔案系統一樣, 如下圖所示:

ZooKeeper: 簡介, 配置及運維指南

長期節點與短期節點

和檔案系統不同, 每個ZooKeeper節點都有自己的資料, 而檔案系統中, 目錄是不儲存資料的. ZooKeeper中的節點也被簡稱為znode.

znode本身除了資料之外, 還儲存著:

  1. 資料變更的版本號
  2. ACL變更的版本號
  3. 時間戳

每次znode中的資料有變更, 版本號都會遞增, 並且, Client向znode獲取資料時, 實際上不光獲取了資料本身, 還獲取了資料的版本號.

znode中資料的讀寫是原子性的, 讀, 會讀到整個znode中的所有資料, 寫, 會替換掉znode中的所有資料. 每個znode還額外有一個ACL(訪問控制表. Access Control List)來限定讀寫的.

普通的znode是永續性的, 這意味著只要ZooKeeper服務健在, 那麼這個znode就存在著. 但有一種znode不是這樣的: 它隨著某個建立znode的會話的開始, 被建立, 而一旦這個會話被撤除掉, 這個znode就會自動被ZooKeeper刪除. 這個特性在某些場合特別有用.

監控

ZooKeeper支援一個叫監控(watches)的概念: Client可以對某個znode設定監控, 當這個znode有變更的時候, 就會產生監控事件, 這個事件會由ZooKeeper通知至Client, 即是Client會收到來自Server的通知.

另外如果在監控過程中, Client和Server之間的連線掛掉了, 那麼Client會收到一個連線掛掉的通知.

1.1.4 ZooKeeper對使用者的承諾

  1. Sequential Consistency 順序一致性. 同一個Client傳送的多個請求, 會按請求傳送的順序被應用到Server上
  2. Atomicity 原子性. 請求要麼成功, 要麼失敗, 沒有中間狀態
  3. Single System Image 單系統映象. 無論Client接的是哪個Server, ZooKeeper保證Client獲得的服務是完全相同的.
  4. Reliability 可靠性. 一旦一個更新操作被ZooKeeper接受且應用實施, 那麼直至某個Client再次更新之前, ZooKeeper會保持住這個狀態.
  5. Timeliness 及時性. ZooKeeper保證Client看到的都是及時更新的資料.

1.1.5 ZooKeeper的API

API名稱 語義
create 在名稱空間樹中的指定位置建立一個節點
delete 刪除一個指定節點
exists 查詢指定節點是否存在
get data 獲取指定節點上的資料
set data 向指定節點寫資料
get children 獲取一個指定節點的所有子節點
sync 等待資料傳播完成

1.1.5 ZooKeeper的實現

下圖是ZooKeeper的元件圖: ZooKeeper是由下面圖中的幾個元件(component)構成的, 除了圖中的request processor(請求接收器)元件之外, ZooKeeper的每個Server都有著其它元件的單獨例項.

ZooKeeper: 簡介, 配置及運維指南

replicated database(資料庫), 這裡的replicated是指, 這個資料庫在每個Server中都有一個獨立的例項. 並且每個Server中的資料庫都儲存著整個名稱空間樹, 另外注意這個資料庫是記憶體資料庫. 每當資料樹要更新的時候, 都會向磁碟寫入日誌(為了故障恢復使用), 並且對資料樹的寫操作, 是先將資料序列化後寫入磁碟, 再寫入資料庫的.

每個ZooKeeper Server都對外向多個Client提供服務, 而Client只能接一個Server來收發請求迴應. 當Client發出讀請求的時候, 資料是直接從本地資料庫返回的, 而如果Client傳送的是一個寫請求, 那就有點麻煩了, 這個寫請求的處理要經過一個協商協議(agreement protocol)的處理. 這個邏輯很自然, 因為寫操作是要維持多個Server之間資料的一致性的.

那麼核心問題就是, 這個所謂的agreement protocol是如何保證寫操作全域性一致的呢? 它有兩個策略:

  1. ZooKeeper中的所有Server, 有一個是爹(leader), 其它都是兒子(follower)
  2. 爹向兒子傳達什麼資訊, 兒子毫不遲疑的執行.
  3. ZooKeeper內部有一個選爹機制, 保證在爹出現意外(程式掛死, 機器斷網)的情況下, 選出一個新爹
  4. 兒子接收到寫請求時, 先將寫請求交給爹. 之後爹再將寫請求廣播給所有兒子.

ZooKeeper在訊息通訊上使用了一個自定義的原子訊息協議, 訊息協議的原子性保證了ZooKeeper中每個Server, 無論是爹還是兒子, 資料庫中儲存的資料都是實時的. 當爹收到由兒子遞交的一個寫請求後, 會做如下事情:

  1. 對比資料庫中的當前狀態, 預計寫操作執行後的預期狀態, 把兩個狀態之間的差額做成一個事務請求
  2. 將這個事務請求分發給所有兒子

1.1.6 ZooKeeper的用法

ZooKeeper的程式設計介面十分簡單, 這部分內容會在後續章節涉及到. 簡單意味著API數量少, 容易被理解, 但也有一點壞處, 就是介面能實現的功能十分基本基礎, 要實現高階一點的功能, 你需要自己寫邏輯, 當然這也不會十分困難.

1.1.7 ZooKeeper的效能

前面吹了那麼多ZooKeeper的好處, 是時候用一些證據來支撐前面吹的逼了. ZooKeeper的實際效能到底有多少? 我相信很多傳統的C/C++後臺開發人員在看到ZooKeeper是跑在JVM上的Java程式的時候都在內心默默的鄙視了一下ZooKeeper.

但反直覺的是, 根據雅虎的ZooKeeper研發團隊報告, ZooKeeper的效能確實十分強勁, 但效能強勁是有前提條件的: 那就是對資料的讀取操作量遠大於資料寫入操作量. 當然作為一個供其它應用程式使用的協調服務, 讀量大於寫量應該是一個典型現象.

下圖是ZooKeeper的效能圖表, 其中橫軸是指請求量中讀操作的佔比, 縱軸是每秒能處理的請求量. 顏色不同的線代表ZooKeeper的例項個數(顯然每個例項獨佔一臺機器). 可以直觀的看到, 在讀操作佔比超過80%後, ZooKeeper的吞吐量就起飛了. 並且5個例項構成的ensemble在效能的提升上對比3個例項構成的ensemble有顯著提升, 但超過5之後, 增加例項個數對ZooKeeper的整體效能提升就不是很明顯了. 當然這只是一個參考圖表, 應用實施的時候, 各家都有各家的特殊國情, 還需要自行探究.

ZooKeeper: 簡介, 配置及運維指南

上面這邊圖的測試現場大致是這樣的:

  1. 用的ZooKeeper版本為3.2
  2. 每個ZooKeeper例項獨佔一臺機器, 每臺機器雙核心2GHz Xeon處理器, 磁碟兩塊, 磁碟的效能規格是 SATA 盤. 15K RPM. 其中一塊硬碟專用於存放ZooKeeper的日誌, 另一塊磁碟專用於儲存快照.
  3. 讀寫的資料量均是1k位元組. 模組client的機器大約有30臺.
  4. ensemble被設定為爹不允許直連client.

這大致能解釋為什麼ensemble中例項的數量從3到5的時候, 效能有一個飛躍: 因為3個例項組成的ensemble中, 幹活的只有兩個, 另外一個是爹. 變成5個例項組成的ensemble後, 幹活的就有四個了, 提升了一倍.

1.1.8 ZooKeeper的可靠性

除開吞吐量外, 使用者另外關心的一個指標就是可靠性, 下面這張圖依然出自雅虎團隊, 搭建了一個7個例項組成的ensemble, 然後用910個client去瘋狂懟這個ZooKeeper叢集, 懟ZooKeeper時其它引數和上面的效能測試一致, 不同的是寫操作的比例為30%, 讀操作為70%. 在懟的過程中, 依次手動模組瞭如下情況:

  1. 一個兒子掛了, 被恢復
  2. 另外一個兒子掛了, 被恢復
  3. 爹掛了, 被恢復
  4. 兩個兒子掛了, 被恢復
  5. 另外一個爹掛了, 被恢復

圖中橫軸是時間, 單位是秒, 縱軸是吞吐量. 圖中帶圓圈的數字分別代表了有上面對應的錯誤發生.

ZooKeeper: 簡介, 配置及運維指南

client共有910個, 所以比起上一張圖來說, 吞吐圖下降了不少. 從圖中可以看出:

  1. 單個兒子例項有異常波動基本不影響ZooKeeper的效能, 對使用者來講, 基本感覺不到.
  2. 爹掛的時候會影響ZooKeeper的效能, 但很快就能恢復. ZooKeeper在爹掛了之後重新選舉一個爹的時間大致在200ms左右.

1.1.9 ZooKeeper的應用前景

ZooKeeper的版本號, 直至我翻譯這個文件的時候, 穩定版已經到3.4.12了, beta版已經到3.5.4了. 能迭代到這個程式說明Apache對這個專案的態度已經顯然不止於玩票了. 實際上ZooKeeper已經成功的應用在許多業界的產品上了. 雅虎用ZooKeeper做Message Broker的協調和錯誤恢復服務, 這應當是ZooKeeper能傍上的最粗的大腿了.雅虎的這個訊息佇列是一個很基礎的釋出-訂閱訊息系統, 規模很大. 受這個成功案例的影響, 雅虎裡的一標廣告系統在在用ZooKeeper做可靠性服務.

1.2 快速上手指南

這一小節主要是面向開發者的, 大致閱讀本小節的內容, 能讓你快速上手, 在你的程式碼中使用上ZooKeeper的功能, 並且為了配合自測, 這一小節大致會簡單的講一下如何搭建一個由單個例項組成的ZooKeeper ensemble(僅供開發除錯使用的部署模式), 並且講幾個小命令用以檢測你的ZooKeeper是否搭建成功, 再附上一些程式碼片斷幫助你直觀的理解ZooKeeper介面的用法.

這一節不會涉及生產環境中多Server模式的部署方法, 也不會涉及部署中的詳細配置引數等. 這一節只是一個快速上手指南: 並且是面向開發人員的快速上手指南.

1.2.1 系統要求

請參考第四章

1.2.2 下載ZooKeeper

這裡下載ZooKeeper的最新的穩定版.

1.2.3 單例項模式

搭一個單例項模式的ZooKeeper很簡單,

  1. 下載ZooKeeper的安裝包, 解壓
  2. 寫一個配置檔案, 建議把這個配置檔案放在zookeeper/conf/zoo.cfg這個位置, 配置檔案是一個文字檔案, 鑑於現在搭的是一個開發環境, 所以配置檔案裡簡單的寫下面這三行就可以了
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
  1. 來稍微解釋一下這個配置檔案中的配置項, 總共有三個
    1. tickTime: 這是一個基本的時間單位, 後面的值的單位是毫秒, 比如上面我們設定為2000, 意思是一個tickTime代表了2000毫秒. tickTime主要用於傳送心跳, 以及預設的最小會話超時時間是兩個tickTime
    2. dataDir: 這個目錄是用於儲存記憶體資料庫的快照的, 並且預設情況下除非特別指定, 事務日誌也會存在這裡
    3. clientPort: 這是開發給client用於連線的埠號
  2. 在寫完配置檔案, 並且把配置檔案放在zookeeper/conf/zoo.cfg後, 執行zookeeper/bin/zkServer.sh這個指令碼就會啟動一個單例項ZooKeeper服務.

另外需要注意的還有以下幾點:

  1. ZooKeeper用log4j介面來輸出執行日誌. 注意這裡說的是執行日誌, 而不是前面提到的事務日誌. 更多的細節在第二章會涉及. 預設情況下日誌會輸出至控制檯, 如果你希望把日誌寫進一個檔案裡, 需要自行配置log4j
  2. 注意按上面步驟搭的是一個單例項ZooKeeper服務, 沒有什麼可靠性可言, 生產環境中千萬不要這麼幹, 這只是給使用ZooKeeper介面寫程式的開發人員用的一個本地測試環境, 而且由於是單例項, 一旦這個唯一的ZooKeeper server程式掛了, 整個ZooKeeper服務就不可用了.

1.2.4 管理ZooKeeper的儲存

ZooKeeper有兩個地方使用到了本地磁碟儲存, 如果你仔細閱讀了之前的章節的話, 會知道這兩個點分別是:

  1. 事務日誌
  2. 記憶體資料庫快照

生產環境建議這兩個東西分開儲存, 並且最好是物理上分開儲存: 分別使用一塊獨立的硬碟. 但具體細節這一章節就不說了, 後續在運維相關的章節會講到.

1.2.5 連線至ZooKeeper服務

執行安裝包裡的這個指令碼可以連線至上面搭建好的ZooKeeper本地單例項服務上去:

$ zookeeper/bin/zkCli.sh -server 127.0.0.1:2181

這開啟了一個互動式命令列程式, 連線成功後, 大致會有類似於以下的輸出:

Connecting to localhost:2181
log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper).
log4j:WARN Please initialize the log4j system properly.
Welcome to ZooKeeper!
JLine support is enabled
[zkshell: 0]

在這個命令列程式內部, 鍵入help獲取一些基本命令的用法, 比如這樣:

[zkshell: 0] help
ZooKeeper host:port cmd args
        get path [watch]
        ls path [watch]
        set path data [version]
        delquota [-n|-b] path
        quit
        printwatches on|off
        createpath data acl
        stat path [watch]
        listquota path
        history
        setAcl path acl
        getAcl path
        sync path
        redo cmdno
        addauth scheme auth
        delete path [version]
        setquota -n|-b val path

類似於mysql的命令列工具, 你可以在這個命令列工具裡向ZooKeeper服務發出一些指令, 比如你想檢視目前ZooKeeper的層級名稱空間裡都儲存了些啥玩意, 你可以使用ls命令

[zkshell: 8] ls /
[zookeeper]

初始狀態下, 層級名稱空間裡啥都沒有, 根/下只有一個名為zookeeper的節點, 也就是znode. 你可以使用命令create來建立一個節點, 也就是znode, 像下面這樣

[zkshell: 9] create /zk_test my_data
Created /zk_test

上面這個命令建立了一個znode, 這個znode的路徑是/zk_text, 並且把一個字串"my_data"存在了這個znode中. 現在再用ls檢視節點, 輸出會類似如下:

[zkshell: 11] ls /
[zookeeper, zk_test]

現在根名稱空間/下有兩個節點, 一個是預設存在的zookeeper, 一個是我們剛才建立好的zk_text節點.

如果你想取出znode中的資料, 可以使用get命令來獲取, 如下:

[zkshell: 12] get /zk_test
my_data
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 5
mtime = Fri Jun 05 13:57:06 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0
dataLength = 7
numChildren = 0

輸出了不少東西, 正如我們在簡介裡提到的那樣, 當你向znode寫入資料時, znode不光會儲存資料本身, 還會儲存一些其它玩意, 比如資料的建立時間, 資料的版本號之類的, 上面的輸出很豐富, 第一行當然是我們在建立/zk_text節點時寫入的字串"my_data", 餘下的行則是與這個資料有關的其它資訊. 詳細細節這裡先略過.

當你想修改一個znode下的資料的時候, 你可以使用set命令, 就像下面這樣, 我們把上面/zk_text節點裡的資料, 從原先的"my_data"字串, 修改為新字串"junk":

[zkshell: 14] set /zk_test junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0

修改資料也會輸出節點的額外資訊, 接下來再用get命令檢視一下剛才的修改效果:

[zkshell: 15] get /zk_test
junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0

可以看到, 資料已經被修改了.

當你想刪除一個znode的時候, 你可以使用delete命令, 就像下面這樣, 我們刪除上面我們建立的/zk_text節點, 在刪除後再用ls命令檢視一下刪除效果

[zkshell: 16] delete /zk_test
[zkshell: 17] ls /
[zookeeper]
[zkshell: 18]

你看, 這樣是不是就很直觀, 簡介裡說了一大堆, 比不上你自己動手感受一下. 在這個互動式命令列程式裡的各個命令, 其它都對應著ZooKeeper API裡的各個介面, 有了上面的直觀認識, 再參照著程式設計手冊使用ZooKeeper就很容易理解了.

1.2.6 在程式碼中使用ZooKeeper

ZooKeeper對外提供的API有Java版和C語言兩個版本, 功能上是完全一樣的. C語言的API還有兩個子版本: 一個用於在單執行緒環境中使用, 一個用於在多執行緒環境中使用. 更多的具體資訊請參考後續章節.

1.2.7 搭建多例項的ZooKeeper ensemble

生產環境中顯然不能使用單例項的ZooKeeper, 這裡簡單提一下如何搭建多例項構成的ZooKeeper ensemble. 多個例項構成的ensemble中, 每個ZooKeeper server程式都有自己的記憶體資料庫, 但所有例項的記憶體資料庫裡的內容是一毛一樣的, 這也是為什麼官方文件裡把多例項模式的ZooKeeper ensemble稱為Replicated ZooKeeper的原因. 有一點小蛋疼的地方是, 每個ZooKeeper server程式在啟動時都需要一個配置檔案, 在多例項ZooKeeper ensemble中, 這多個例項使用的配置檔案必須一毛一樣, 多個例項正常情況下應當分佈在多個不同的機器上, 所以這個配置檔案的一致性嘛, 就需要運維部署人員手工維護了.

在這篇翻譯的文件裡, 我始終堅持把這種多個ZooKeeper server程式構成的ZooKeeper服務稱為多例項模式ZooKeeper, 而不叫叢集, 原因是: ZooKeeper並沒有嚴格限制你, 必須每個ZooKeeper server獨佔一臺機器. 雖然從邏輯上講, 多例項模式下應當為每個ZooKeeper server分配一臺獨立的機器, 但你非要在一臺機器上啟動100個ZooKeeper server, 這是完全行得通的.

在多例項模式下, 例項的數量是有最低要求的: 最少要有3個例項. 並且強烈建議你, 例項的數目設定為奇數: 我們在簡介中提到過, ZooKeeper服務執行時, 如果有少量例項掛掉了, 整個ZooKeeper服務還會正常運轉. 這裡對於少量的定義, 其實就是看掛掉的例項數量有沒有達到二分之一. 奇數數目的例項, 比較容易判定二分之一.

多例項模式下的配置檔案, 依然建議放在zookeeper/conf/zoo.cfg中, 內容與單例項模式稍有不同

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888

其中tickTime, dataDir, clientPort三個配置項的含義這裡不再重複, 新增的欄位的含義如下:

  1. initLimit: 這是一個超時時間, 它的單位是tickTime, 它限制了ZooKeeper中兒子與爹建立連線的超時時間. 比如上面設定為5, 且tickTime的值設定為2000毫秒, 意思是: 兒子與爹建立連線必須在10秒內完成, 超過10秒, 就會認為這個兒子廢掉了.
  2. syncLimit: 這也是一個超時時間, 單位依然是tickTime. 上面設定為2, 意思是指, 當兒子執行資料同步的時候, 在4秒內必須完成, 超過4秒, 就會認為這個兒子廢掉了.
  3. server.X: 配置檔案裡共有三行這種配置, 每一行描述了當前多例項ZooKeeper服務內的一個例項. 當每個ZooKeeper server例項程式啟動的時候, 它先去 dataDir 目錄下找一個名為myid的文字檔案, 這個檔案裡寫著一個數字, 這個數字為1, 就代表當前啟動的server 程式是配置檔案裡的server.1. 然後程式就會讀取配置檔案裡server.1的值, 這裡值為zoo1:2888:3888, 2888是用於兒子和爹建立連線使用的埠, 3888是用於選舉爹的時候使用的埠號. 注意, 如果你在一臺機器上啟動多個ZooKeeper server例項, 那麼注意多個例項間不能使用相同的埠號.

1.2.8 簡單的優化建議

為了獲取更好的效能, 建議在配置檔案中額外配置一個配置項, 名為dataLogDir, 值為一個目錄地址. 配置該配置項之後, 會導致當前例項會將事務日誌寫向對應地址. 否則會直接寫向dataDir中去.

更多的優化建議, 與更細節的多例項ZooKeeper部署指導會在後續運維章節仔細說明.

2. 面向開發者的文件

// TODO

3. BookKeeper

// TODO

4. 面向系統管理員與運維人員的文件

4.1 系統管理員指南

4.1.1 部署

4.1.1.1 系統要求

支援的作業系統平臺

ZooKeeper下有多個元件. 有些元件在各大平臺上都能跑, 有些則只能跑在指定的一部分平臺上. 這些元件包括:

  1. Client. 指的是ZooKeeper的客戶端庫, 用於其它應用程式與ZooKeeper ensemble建立連線並使用ZooKeeper的功能
  2. Server. 指的是ZooKeeper server. 跑在ZooKeeper ensemble中各個機器上的應用程式, 也就是ZooKeeper server程式.
  3. Native Client. 一個用C語言實現的庫, 功能和Client一樣, 只不過面向的是C系開發者.
  4. Contrib. 其它可選的元件.

下表展示了各個元件在各個作業系統平臺上的受支援程度:

作業系統 Client元件 Server元件 Native Client元件 Contrib其它元件
GNU/Linux 可用於開發及生產 可用於開發及生產 可用於開發及生產 可用於開發及生產
Solaris 可用於開發及生產 可用於開發及生產 不支援 不支援
FreeBSD 可用於開發及生產 可用於開發及生產 不支援 不支援
Windows 可用於開發及生產 可用於開發及生產 不支援 不支援
Max OS X 僅可用於開發 僅可用於開發 不支援 不支援

其它沒提到的作業系統平臺就自求多福吧.

需要的軟體

ZooKeeper是用Java寫的, 所以需要一個版本在1.6或更高的JDK. 對於多例項模式的部署建議使用多臺機器.

4.1.1.2 多例項模式

我們在第一章的快速上手指南中提到過, 生產環境建議使用多例項模式部署ZooKeeper, 並且建議單個例項獨佔一臺機器, 且建議例項的個數為奇數. 箇中緣由這裡不再重複.

下面是如何部署多例項模式中的一個例項的步驟. 在多臺機器上重複以上步驟, 就能搭起一個多例項模式的ZooKeeper服務.

  1. 安裝JDK
  2. 設定Java執行時的"heap size"(堆容量). 額外設定這個是由於當記憶體不足時, swapping操作會嚴重拖慢zookeeper的效能(wtf? 2018年了好吧!). 一般情況下, 如果機器是zookeeper獨佔的, 那麼記憶體多大就設定多大就可以了.
  3. 下載ZooKeeper, 點這裡
  4. 解壓下載好的壓縮包
  5. 寫一個配置檔案, 配置檔案的路徑和檔名隨便啥都行. 內容如下. 配置檔案中各個配置項的具體含義在 4.1.2.10 配置引數中有詳細說明. 如下的配置檔案對應的使用要求是:
    1. 三臺機器, 每個例項獨佔一臺機器. 例項的編號分別為1, 2, 3
    2. 機器的2888埠用於兒子和爹通訊, 3888埠用於爹的選舉
    3. 對外提供服務的埠號為2181
    tickTime=2000
    dataDir=/var/lib/zookeeper/
    clientPort=2181
    initLimit=5
    syncLimit=2
    server.1=zoo1:2888:3888
    server.2=zoo2:2888:3888
    server.3=zoo3:2888:3888
  6. 在配置檔案指定的dataDir路徑下建立一個名為myid的檔案, 這是一個文字檔案, 檔案內容只有一個數字: 例項的編號
  7. 使用類似於下面的命令列啟動例項:
java -cp zookeeper.jar:lib/log4j-1.2.15.jar:conf org.apache.zookeeper.server.quorum.QuorumPeerMain zoo.cfg
0. ZooKeeper server是Java程式, 所以用`java`命令啟動
0. -cp 引數指示JVM虛擬機器去哪裡找需要的class檔案. 這裡指定了兩個jar包檔案, 和一個目錄地址. 用分號`:`隔開
    0. zookeeper.jar. 這個jar包裡存著ZooKeeper server的主要class檔案
    0. lib/log4j-1.2.15.jar. 這個jar包裡存著ZooKeeper輸出執行日誌所需要的`log4j`的class檔案
    0. conf. 這個目錄裡的jar包存著其它啟動ZooKeeper所需要的class檔案
0. `org.apache.zookeeper.server.quorum.QuorumPeerMain`是main函式所在的類路徑
0. `zoo.cfg`是配置檔案路徑
0. 簡單點, 你可以把配置檔案放在`zookeeper/conf/zoo.cfg`中, 然後直接執行`zookeeper/bin/zkServer.sh`指令碼來啟動. 這個指令碼中的內容更豐富. 但核心命令和上面那行基本一樣. 這樣的好處是很無腦, 很方便.
  1. 要測試當前ZooKeeper Server例項程式是否正確執行, 可以使用下面的指令碼進行測試:
$ bin/zkCli.sh -server 127.0.0.1:2181

4.1.1.3 單例項模式

單例項模式一般僅用於開發自測使用, 具體的部署步驟在1.2.3 單例項模式章節有描述, 請翻回去看.

4.1.2 系統管理

4.1.2.1 部署設計

ZooKeeper的可靠性依賴於兩個假設

  1. 假設你的ZooKeeper以多例項模式部署, 且每個例項佔用一臺機器. 這些機器在執行過程中, 不可能出現在同一時間有超過二分之一的機器當機的可能.
  2. 每臺機器有網路狀況與磁碟狀況良好, 多例項程式啟動時執行的jar包程式碼是一致的.

要使第一個假設儘可能的成立, 你可以做如下的事情:

  1. 預估你的機器當機的頻率與持續時間, 恢復時間, 算出基本不可能同時當機的機器臺數. 比如你根據去年的運維記錄, 查一下去年的當機概率, 然後根據這個概率估算出整個機房同時有指定的1臺機器當機的概率, 同時有指定的兩臺機器當機的概率. 把這個概率降低到你能接受的程度, 比如你估算出你的機房中, 同時有指定的5臺機器同時當機的概率不足千萬分之一, 這個你可以接受, 那麼將ZooKeeper多例項的規則設定為 2*5 + 1 == 11個, 即在11臺機器上部署ZooKeeper, 這樣保證在估算正確的情況下, 即便有千萬分之一的概率發生了同時有五臺機器當機, ZooKeeper服務依然能正常工作.
  2. 儘可能的保證uklfZooKeeper的機器之間的當機沒有關聯性. 比如如果你使用雲主機, 儘量保證所有vm都分屬不同的host, 使用實體機, 儘可能的將機器分散至多個IDC之類的.

要使第二個假設儘可能的成立, 你可以做如下的事情:

  1. 如果有條件, 儘可能的使部署的機器上只有一個ZooKeeper server程式.
  2. 如果沒有條件做到上面一條, 那麼儘量避免ZooKeeper server程式與其它程式爭用網路資源或儲存資源. 即預留給ZooKeeper server程式足夠的頻寬或獨佔網路卡, 為ZooKeeper預留足夠的IO資源以及記憶體空間
  3. ZooKeeper裡很拖效能的兩個點就是寫事務日誌內在資料庫快照, 儘量將這兩部分日誌寫向不同的物理磁碟. 即是在啟動配置中, 獨立配置dataDirdataLogDir, 並將兩個配置的路徑指向不同的物理磁碟.
  4. 執行時為JVM分配合理的heap size, 以防止發生swapping拖慢程式.

4.1.2.2 Provisioning

// TODO, 官方文件這裡留空了

4.1.2.3 Things to Consider: ZooKeeper Strengths and Limitations

// TODO, 官方文件這裡留空了

4.1.2.4 Administering

// TODO, 官方文件這裡留空了

4.1.2.5 運維

對於長期執行的ZooKeeper ensemble來說, 運維工作是必須做的, 運維人員需要注意以下幾點:

清理磁碟檔案

ZooKeeper中有兩處使用到了磁碟: 事務日誌記憶體資料庫快照. ZooKeeper名稱空間裡的節點發生變更的時候, 就會有內容寫入事務日誌. 通常情況下, 當單個事務日誌檔案變的越來越大的時候, 事務日誌就需要建立一個新的檔案. 但在建立新的事務日誌檔案之前, ZooKeeper會先把當前的記憶體資料庫的狀態寫入磁碟先做快照, 然後再生成一個新的事務日誌檔案. 這樣就保證了快照檔案和事務日誌檔案是一一對應的. 但快照落地需要時間, 在快照落地期間如果還有事務來臨, 那麼這部分事務的日誌依然會寫向舊的事務日誌檔案裡. 這就導致, 快照檔案對應的那個事務日誌檔案裡, 儲存的事務日誌可能要比當前快照檔案要.

ZooKeeper server程式在預設啟動的情況下, 是不會自動刪除事務日誌檔案和快照檔案的. 當然這是可配置的, 配置項分別是autopurge.snapRetainCountautopurge.purgeInterval. 這兩個配置項的具體含義在4.1.2.10章節有詳細描述. 但需要注意: 如果你要這樣做, 那麼最好為每臺部署的機器提供不同的配置值, 除非這些機器的規格是完全一毛一樣的!

除過在配置檔案中設定, 還有一種方法就是呼叫一個ZooKeeper提供的小工具, 大致如下:

java -cp zookeeper.jar:lib/log4j-1.2.15.jar:conf org.apache.zookeeper.server.PurgeTxnLog <dataDir> <snapDir> -n <count>

其中<dataDir>是事務日誌的儲存目錄, <snapDir>是快照檔案的儲存目錄, <count>是要保留的個數. 建議大於3. 執行該命令後, 除過最近的<count>對事務日誌檔案與快照檔案, 其它檔案都將被刪除. 這是一個一次性命令. 如果你想定期清理, 那麼只能自己寫個指令碼咯.

注意兩點:

  1. 永遠不建議手動刪除事務日誌檔案與快照檔案
  2. 通過配置項使ZooKeeper server自動刪除, 只有在ZooKeeper版本大於3.4後才可用
  3. PrugeTxnLog工具是一個一次性工具, 如果需要定期清理, 你需要自己寫一個指令碼.
  4. 當機器規格不同的時候, 建議按照不同規格定製不同的清楚閾值
清理執行日誌

ZooKeeper用log4j來輸出執行日誌. 如果要更改執行日誌的相關配置, 你需要獨立為log4j提供配置檔案. 建議使用log4j提供的滾動日誌特性, 這樣就免去了清理執行日誌的問題. 更多詳細資訊請參閱4.1.2.8 執行日誌

4.1.2.6 監控ZooKeeper server程式的死活

ZooKeeper的server程式在錯誤發生的時候會立即自殺, ZooKeeper的設計哲學是這樣的:

  1. 單個例項掛掉, 或少量例項掛掉不影響整體服務
  2. 當單個例項遇到錯誤的時候, 例項會立即掛掉
  3. 例項被重啟後會自動加入ensemble
  4. 但例項不會自動重啟

所以搞一個監控程式, 在例項程式掛掉之後將其立即拉起是一個很好的做法. 比如daemontoolsSMF. 還比如我們親愛的織雲.

4.1.2.7 監控ZooKeeper server服務的狀態

要監控ZooKeeper服務的狀態, 有兩個選擇

  1. 用4字命令去檢查. 這個在4.1.2.11 ZooKeeper4字命令中有詳情
  2. JMX. 這個在4.3 JMX中有詳情

4.1.2.8 執行日誌

ZooKeeper使用log4j 1.2來輸出執行日誌. 預設的配置檔案在zookeeper/conf/log4j.properties中. log4j的配置檔案要求要麼放在工作目錄裡, 要麼放在類路徑裡.

詳情請檢視log4j的官方手冊

4.1.2.9 問題定位

由於檔案損壞導致例項不能啟動

ZooKeeper的server程式在事務日誌檔案被損壞的情況下是起不來的. 這時執行日誌會說在載入ZooKeeper database時出現IOException. 這種情況下, 你需要做的是:

  1. 通過四字命令stat檢查ensemble中的其它例項是否正常工作
  2. 如果其它例項正常, 那麼把當前例項dataDir目錄下的version-2子目錄中的所有檔案刪除, 再把dataLogDir下的version-2子目錄下的所有檔案刪除, 然後重啟就可以了.

這種情況是當前例項的事務檔案損壞, 不能重建記憶體資料庫, 刪除掉事務日誌和資料庫快照後, 當前的例項在重啟後會通過其它例項拉取內在資料庫, 重建事務日誌和快照檔案.

4.1.2.10 配置引數

ZooKeeper的行為受配置檔案影響. 所有同一個ensemble中的例項建議使用完全相同的配置檔案. 但使用完全相同的配置檔案有一個前提條件: 就是所有例項所屬的機器上的磁碟佈局是相同的. 磁碟佈局不同意味著不同的機器下的例項在配置dataDirdataLogDir的時候配置值可能有差異, 但除此之外, 一個ensemble中的所有例項的配置檔案必須保證server.x=xxxx這些配置值是完全一致的.

最小配置

下面列出來的是要讓ensemble正常工作, 每個例項都需要配置的配置項

配置項 含義
clientPort 當前例項對外提供服務的埠號. 即是client通過該埠號連線到該例項上. 建議所有例項都配置為相同的值.
dataDir 內在資料庫快照的儲存地址. 如果沒有配置dataLogDir的話, 該目錄還會儲存事務日誌
tickTime ZooKeeper中計量時間的最基本單位. 配置值的單位是毫秒
高階可選配置

下面列出來的是一此可選配置, 屬於高階選項. 你可以用這些配置項進一步個性化ZooKeeper server的行為. 其中一些配置項的值可以通過在啟動server程式的時候寫入Java 系統屬性來設定.

配置項 對應的Java系統屬性名 含義
dataLogDir 事務日誌的寫入地址
globalOutstandingLimit zookeeper.globalOutstandingLimit client向ZooKeeper server遞交請求的速度可以比ZooKeeper server處理請求的速度快, 特別是有多個client訪問同一個ZooKeeper server的時候. 通常情況下對於不能及時處理的請求, server都會將其快取到佇列中. 但這個佇列也不是無限長的, 這個配置項就是在設定這個佇列的長度, 預設值是1000, 超過佇列長度後, 再發請求請求就會被丟棄
preAllocSize zookeeper.preAllocSize 為了避免頻繁的seek操作, ZooKeeper的事務日誌檔案預設是以64M為基本單位的. 設定這個值就可以改寫這個預設的塊大小, 這個配置項的單位是kb
snapCount zookeeper.snapCount 預設值是100000, 這是指每向事務日誌裡記錄十萬個事務, 記憶體資料庫就會被快照一次, 同時事務日誌會開新檔案. 但為了避免所有的ZooKeeper server在同一時刻落地快照更換事務日誌檔案, 為了把這個操作錯開, 所以真實的值是 位於區間 [50001, 100000] 區間的一個隨機數. 即是 [snapCount/2 + 1, snapCount]區間
maxClientCnxns 同一個個client是可以和同一個ZooKeeper ensemble之間建立多個連線的, 這個配置項就是在限制同一個client與ZooKeeper ensemble之間建立的連線數. 這可以有效預防DoS攻擊, 還可以預防ZooKeeper server所在的機器檔案描述符耗盡. 這個值預設是60, 當這個值被設定為0時, 是取消掉這個限制的意思
clientPortAddress 這是一個3.3版本後的新配置項, 這是一個IP地址, 當配置了之後, ZooKeeper server在監聽埠的時候, 就會繫結到這個地址上. 而預設情況下, 在監聽clientPort埠的時候, 繫結的是ANYADDR
minSessionTimeout 這也是一個3.3版本後的新配置項, 這個配置項的單位是毫秒, 而不是tickTime. 指的是會話超時r的最小時間, 預設的會話超時時間是2個tickTime. 這個值可以和client協商.
maxSessionTimeout 這依然是一個3.3版本後的新配置項. 和minSessionTimeout類似, 但這是會話超時的最大時間. 預設是20個tickTime
fsync.warningthresholdms zookeeper.fsync.warningthresholdms 3.3.4版本後的一個新配置項. 這是一個時間量, 單位是毫秒, 預設是1000, 也就是一秒. 這指的是當fsync 事務日誌的耗時大於1秒時, 就會向執行日誌裡輸出一條warning日誌. 注意這個配置項只能通過Java系統屬性設定
autopurge.snapRetainCount 3.4版本後的一個新配置項, 當設定了該配置項後, ZooKeeper將會自動清理快照檔案和對應的事務日誌檔案. 僅保留最近的autopurge.snapRetainCount個. 預設值是3, 最小值是3, 不能設定比3更小的值
autopurge.purgeInterval 這是一個開關配置項, 也是一個時間配置項. 當設定為0的時候, 是關閉自動清理快照和事務日誌檔案的功能的意思. 當設定為1或更高的值時, 是指每隔autopurge.purgeInterval個小時, 就執行一次自動清理任務
syncEnabled zookeeper.observer.syncEnabled 3.4.6有這個配置項, 3.5.0和更高的版本有. observer在預設情況下會和participants一樣記錄事務日誌, 落地快照. 將這個值設定為false就是關掉了observer記錄事務日誌落地快照的行為, 預設是true. observer和participants的概念會在4.4章節介紹
多例項模式下的配置項

下面列出來的配置項是多例項模式下的一些配置項. 有一些配置項可以通過在啟動server程式的時候寫入Java系統屬性來設定.

配置項 對應的Java系統屬性名 含義
electionAlg 選爹演算法. 0 - 原始的基於UDP的選爹演算法. 1 - 不帶身份認證的, 基於UDP的快速選爹演算法. 2 - 帶身份認證的, 基於UDP的快速選爹演算法. 3 - 基於TCP的快速選爹演算法. 預設值是3. 其中, 0, 1, 2被官方文件標記為了deprecated, 也就是說, 除非有特殊需求, 不要動手配置這個配置項
initLimit 時間值, 單位是tickTime, 指的是兒子與爹建立連線並同步資料的超時時限. 當ZooKeeper管理的資料特別多特別大的話, 建議適當增加這個配置值. 這個配置項沒有預設值, 所以配置檔案裡必寫
leaderServes zookeeper.leaderServes 控制爹是否也向client提供服務, 預設是允許的. 如果client的業務比較複雜的話, 建議關閉這個功能, 讓爹專心管兒子
server.x=[hostname]:nnnn[:nnnn] ensemble中的每一個例項都要這樣登記在配置檔案裡. x是dataLog目錄下的myid檔案裡的數字, 即是例項的編號. hostname是對應的例項所在的機器名, 或者直接寫成IP地址, 前一個nnnn是用於爹和兒子通訊用的. 第二個nnnn 是用於選舉爹用的.
syncLimit 時間值, 單位是tickTime, 兒子向爹報平安的時限, 如果兒子在超過這個時間值後沒有給爹報平安, 爹就認為兒子死了. 關於initLimit和syncLimit這兩個配置項之間的不同, 請參考這個郵件列表裡的解釋. 官方文件裡寫的簡直讓人懷疑是不是複製貼上過來的.
group.x=nnnn[:nnnn] 給server例項分組. x是組編號. nnnn則是例項編號. 注意如果你給ensemble中的例項分組的話, 各個組之間不能有交集, 並且要保證所有組的並集就是整個ZooKeeper ensemble. (換句話說你不能單獨為其中某一些例項分組, 所有的例項都必須從屬於一個組), 這裡有一個例子
weight.x=nnnn 為一個組設定權重. ZooKeeper只有在少量的一些場合才需要考慮權重, 已知的兩個場合, 一個是選爹的時候, 權重影響例項投票的影響力, 另外一個是atomic broadcast protocol. 預設情況下權重值是1
cnxTimeout zookeeper.cnxTimeout 時間值, 單位是秒. 爹選出來後, 所有人都需要知道爹是誰, 這個時間就是選爹時每個機器都會把選爹埠開啟的時間, 在這個時間內應當有一個結果, 並且結果將通知到每個例項的選爹埠上. 這個配置項僅在electionAlg的值為3時才有用. 預設值是5秒
4lw.commands.whitelist zookeeper.4lw.commands.whitelist 四字命令白名單, 未出現在名單上的四字命令, ZooKeeper將不處理. 預設值包括除過wchpwchc兩個之外的所有四字命令. 如果要設定這個配置項的值, 注意各個四字命令之間要以逗號區分, 比如: r4lw.commands.whitelist=stat, ruok, conf, isro, 如果你需要開啟所有四字命令, 可以簡單的使用萬用字元4lw.commands.whitelist=*
ipReachableTimeout zookeeper.ipReachableTimeout 3.4.11版本後的新配置項, 時間值, 單位是毫秒. 當一個server的hostname不是IP地址時, 並且恰巧DNS服務或者hosts表裡這個名字後面掛著多個ip地址時, 這個值就有用了. 預設情況下, ZooKeeper會預設使用名字解析出來的第一個IP地址, 而不檢查這個IP是否可達, 而如果這個值設定成了一個大於0的值, 那麼ZooKeeper就會使用InetAddress.isReachable(ipReachableTimeout)來判斷這個IP是否可達, 如果不可達, 就換下一個, 如果全都不可達, 那麼就會絕望的使用第一個IP地址
tcpKeepAlive zookeeper.tcpKeepAlive 3.4.11版本後的新配置項. 如果這個配置項被設定為true, 那麼server之間用來選舉的TCP連線就會被置為長連線, 預設情況下這個值是false
身份認證與授權相關的配置項

下面這些配置項與身份認證授權相關.

為了避免看不懂下面的配置項都在幹嘛, 先大致說一下Zookeeper裡的認證與授權

在ZooKeeper server端, 每個znode儲存兩部分內容: 資料和狀態. 狀態中包含ACL資訊. 建立一個znode會產生一個ACL表, 每個ACL記錄有以下內容:

  1. 驗證模式(scheme)
  2. 具體內容(id). 比如當scheme=="digest"的時候, id為是使用者名稱和密碼, 比如"root:J0sTy9BCUKubtK1y8pkbL7qoxSw="
  3. 這個ACL擁有的許可權

ZooKeeper提供瞭如下幾種驗證模式(scheme)

  1. digest. 就是使用者名稱+密碼.
  2. auth. 不使用任何id, 表示任何已確認使用者
  3. ip. 用client連線至server時使用的IP地址進行驗證
  4. world. 固定ID為"anyone", 為所有client端開放許可權
  5. super. 在這種scheme下, 對應的id擁有超級許可權.

注意的是, exists操作的getAcl操作不受ACL控制, 任何client都可以執行這兩個操作.

znode的許可權主要有以下幾種:

  1. create
  2. read
  3. write
  4. delete
  5. admin. 允許對本節點執行setAcl操作
配置項 對應的Java系統屬性名 含義
zookeeper.DigestAuthenticationProvider.superDigest 僅能通過Java系統屬性設定 3.2版本中的新配置項. 允許管理員以超級使用者的身份來訪問ZooKeeper中的層級名稱空間. 當以超級使用者訪問時, znode的ACL完全放行. 以引數"super:<password>"來呼叫org.apache.zookeeper.server.auth.DigestAuthenticationProvider可以生成一個超級使用者. 然後用前面命令生成的"super:<data>"作為Java系統屬性, 在啟動各個server的時候傳遞給程式, 就開啟了這個功能. 注意當一個client通過scheme=digest的方式來認證, 並且提供的認證資料是超級使用者的認證資料, 也就是前面提到的"super:"`的時候, 就可以對server為所欲為了. 注意啊, 認證資訊從client傳遞給server的過程中, 是明文傳輸的! 所以沒事不要作死.
實驗性的配置項
配置項 對應的Java系統屬性名 含義
Read Only Mode Server readonlymode.enabled 顯然這個值只能有過Java系統屬性來設定. 將其設定為true, 配置ZooKeeper為只讀模式. 具體細節參見ZOOKEEPER-784
不安全的配置項
配置項 對應的Java系統屬性名 含義
forceSync zookeeper.forceSync 預設情況下, ZooKeeper強制要求在有資料變更請求時, 先寫事務日誌, 再執行變更. 如果將這個配置設定為no, ZooKeeper在執行資料變更時就不會等事務日誌寫完再執行了.
jute.maxbuffer jute.maxbuffer 這個配置項僅能通過 Java系統屬性設定. 這設定的是一個znode中能儲存的資料的容量. 預設值是0xfffff, 也就是1M. 注意如果要更改這個值, 所有server都要同步更改. 注意如果你要改這個值, 請先反思一下你使用ZooKeeper的姿勢是不是有點不正確
skipACL zookeeper.skipACL 跳過ACL檢查, 這能帶來極大的效能提升, 但很不安全
quorumListenOnAllIPs 如果設定為true, server在監聽埠的時候, 將嘗試監聽所有本地可用的IP+埠的組合. 預設情況下這個配置是false, 這個行為是關閉的
使用Netty框架進行通訊

這是3.4版本後的一個特性. Netty是一個基於NIO的客戶端-伺服器通訊框架, 這個框架簡化了Java在網路通訊層上的很多繁操作, 並且內建支援SSL和認證授權, 當然SSL和認證授權是額外的可選功能.

3.4之前, ZooKeeper是直接用NIO的, 在3.4之後, NIO只是一個可選項, 但依然是預設選項, 如果要使用Netty的話, 需要把zookeeper.serverCnxnFactory替換為org.apache.zookeeper.server.NettyServerCnxnFactory. 你可以只在client上使用Netty, 也可以在server上使用Netty, 但通常情況下, 建議要改一起改.

蛋疼的是相關的文件官方還沒有寫.

4.1.2.11 四字命令

ZooKeeper支援一系列的四字命令, 你可以在client上通過telnte或nc直接向server傳送這些四字命令.

使用一個四字命令如下所示, 下面使用echo和nc將四字命令ruok傳送給本機的server

$ echo ruok | nc 127.0.0.1 5111

下表是所有支援的四字命令, 注意有些命令僅在特定版本之後才受支援:

命令 語義
conf 3.3版本後支援. 列印出server的配置資訊
cons 3.3版本後支援. 列印出server上的所有連線/會話
crst 3.3版本後支援. 重置有關連線/會話的統計資料
dump 這個命令只有發給ensemble中的爹有才效. 列出所有重要的會話和生命週期隨會話的znode
envi 列印出server執行環境
ruok are you ok, 如果回覆 imok, 則說明這個server很健康. 如果server有問題, 則不會收到回覆. 需要注意的是, 一個server回覆了ruok不代表這個server在ensemble中的狀態是正常的, 這僅代表server程式正常啟動了. 要檢視ensemble的概況需要用stat命令
srst 重置server上的所有統計資料
srvr 3.3後的新命令. 列出server的全部資訊
stat 列出server的細節資訊和與之相連的clients
wchs 3.3後的新命令, 列出對該server的所有監控(watch)
wchc 3.3後的新命令, 列出監控這個server的所有會話, 並列出每個會話監控的名稱空間路徑. 注意, 在會話較多的server上, 這個命令可能會相當耗時
wchp 3.3後的新命令, 列出被監控的所有層級名稱空間路徑, 以及相關的會話. 注意同上, 這個命令也可能會相當耗時
mntr 3.4後的新命令. 列出有關ensemble的一系列狀態值. 通過這些狀態值可以檢視整個ensemble是不是正常
isro 3.4後的新命令. 檢查server是否執行在只讀狀態, 回覆ro代表server在只讀狀態, 回覆rw代表server在可讀可寫狀態
gtmk 獲取當前的trace mask值, 以10進位制64位有符號數值形式返回, 具體trace mask的含義下面會講
stmk 設定trace mask

這裡需要注意的有:

  1. mntr命令的輸出大致長下面這樣. 輸出格式符合java屬性格式, 如果你要寫個指令碼定時傳送這個命令以監控ensemble的執行狀態, 注意輸出的欄位的數量可能會有變化, 寫指令碼的時候注意這一點. 另外有一些欄位是與作業系統平臺相關的, 有些欄位則只有爹才會回覆. 輸出每一行的格式是key \t value, 下面是一個示例
$ echo mntr | nc localhost 2185

zk_version  3.4.0
zk_avg_latency  0
zk_max_latency  0
zk_min_latency  0
zk_packets_received 70
zk_packets_sent 69
zk_outstanding_requests 0
zk_server_state leader
zk_znode_count   4
zk_watch_count  0
zk_ephemerals_count 0
zk_approximate_data_size    27
zk_followers    4                   - only exposed by the Leader
zk_synced_followers 4               - only exposed by the Leader
zk_pending_syncs    0               - only exposed by the Leader
zk_open_file_descriptor_count 23    - only available on Unix platforms
zk_max_file_descriptor_count 1024   - only available on Unix platforms
  1. trace mask是一個64位的數值, 按位設flag表示一些執行日誌開關. 另外log4j必須設定執行日誌級別為TRACE才能看到trace日誌資訊. trace mask中各位的含義如下:
trace mask bit 含義
0b0000000001 保留位
0b0000000010 記錄client的請求, 不包括ping
0b0000000100 保留位
0b0000001000 記錄client的ping請求
0b0000010000 記錄來自爹的資訊, 不包括ping
0b0000100000 記錄會話的建立, 銷燬和核實
0b0001000000 記錄監控事件發生時向client報告的日誌
0b0010000000 記錄來自爹的ping
0b0100000000 保留位
0b1000000000 保留位

預設的trace mask是0b0100110010. 另外stmk的用法也稍微複雜一點, 還要注意將trace mask的值通過stmk命令傳遞給server的時候, 要使用大端位元組序, 也就是網路序. 下面是一個使用perl呼叫stmk命令的例子:

$ perl -e "print 'stmk', pack('q>', 0b0011111010)" | nc localhost 2181
250

呼叫stmk命令時, server會將設定後的trace mask以十進位制數值的形式返回回來.

4.1.2.12 資料檔案管理

將事務日誌檔案和快照檔案儲存在不同的物理磁碟上, 可以提升系統效能.

快照儲存目錄

配置項dataDir指向的目錄路徑中主要儲存兩種檔案:

  1. myid: 這個檔案裡寫著當前server例項的編號
  2. snapshot.<zxid>: 這裡儲存著記憶體資料庫的快照

server例項的編號用在兩個場合: myid檔案裡, 以及配置檔案裡的server.X配置項中. 當前server例項在啟動的時候, 先去配置檔案裡看dataDir的值, 然後去找dataDir/myid這個檔案, 檢視檔案內容, 得知自己的編號, 然後在配置檔案裡再找對應的server.x檢視要開的埠號.

快照檔案的字尾, <zxid>, 是一個事務ID. 這是在落地記憶體資料庫這個過程開始時, 成功執行的最後一個事務的ID號, 但蛋疼的是, 在落地快照的過程中, server還在接受請求, 執行事務, 也就是在落地的過程中, 記憶體資料庫中的資料還處於一個變動的過程中, 這就導致落地後的快照檔案像是一個扭曲的檔案. 像是你在用手機拍攝全景照片的過程中, 有一隻貓隨著你的鏡頭走, 然後最終拍攝出來的照片裡有一隻長度為17米的貓. 最終落地生成的快照檔案裡的資料狀態可能和任何一個時刻記憶體資料庫的狀態都對不上, 就是因為這個原因. 但ZooKeeper依然可以用這種扭曲的快照檔案重建記憶體資料庫, 這是因為ZooKeeper中的update操作是冪等的, 這就保證了在扭曲的快照檔案之上重放事務日誌裡的日誌, 就可以將程式的記憶體狀態恢復到日誌結束時的那個時刻.

事務日誌目錄

在有update請求的時候, server的預設行為是先寫事務日誌, 再執行update操作. 單個事務日誌裡儲存的事務個數超過一個閾值的時候, 就會導致事務日誌新開一個檔案, 同時會導致內在資料庫落地快照, 這個閾值在上面的配置項中有提. 日誌檔案的字尾是日誌檔案裡第一個日誌的ID

檔案管理

快照檔案的格式和事務日誌檔案的格式是死的, 這就允許你從現網的server機器上將事務日誌和記憶體快照拷貝至你的開發機, 在你的開發環境重現現網的情景, 從而進行一些除錯或問題定位操作.

使用舊的事務日誌檔案和快照檔案還能重建過去某個指定時刻server的狀態, LogFormatter類可以用來訪問事務日誌檔案, 以獲取可讀的資訊. 當然使用的時候需要有管理員許可權, 因為資料是加密的.

server程式本來是沒有刪除事務日誌和快照檔案的能力的, 但這在3.4版本中也隨著新的配置項autopurge.snapRetainCountautopurge.purgeInterval新增上了.

4.1.2.13 要避免的事情

下面是幾個你應當在部署運維的時候極力避免的事情:

  1. ensemble中各個server使用的配置檔案中, server.X配置表不一致. 所有的配置檔案中, 都要以server.X配置項的形式列出當前ensemble中的所有server, 包括自己. 如果這個東西不一致, 會炸.
  2. 事務日誌目錄設定不合理. 將事務日誌目錄指向一個IO繁忙的磁碟, 會導致server始終處於一個半死不活的狀態.
  3. 不正確的java heap size. 頻繁的swap操作會嚴重拖慢效能. 保守起見, 如果你的機器有4G記憶體, 把java heap size設定為3G就好了.
  4. 部署的時候不考慮安全性. 建議在生產環境中合理配置防火牆.

相關文章