那些你可能不知道的 ZooKeeper 知識

削微寒發表於2021-04-15

本文作者:HelloGitHub-老荀

Hi,這裡是 HelloGitHub 推出的 HelloZooKeeper 系列,免費開源、有趣、入門級的 ZooKeeper 教程,面向有程式設計基礎的新手。

專案地址:https://github.com/HelloGitHub-Team/HelloZooKeeper

今天我要介紹些 ZK 的隱藏功能,廢話不多,讓我們開始吧~

一、JMX

JMX(Java Management Extensions,即 Java 管理擴充套件)是一個為應用程式、裝置、系統等植入管理功能的框架。JMX 可以跨越一系列異構作業系統平臺、系統體系結構網路傳輸協議,靈活的開發無縫整合的系統、網路和服務管理應用。

是不是聽不懂?聽不懂就對了,如果你從來沒有開發過 JMX 或者應用過的話,我這裡就簡單介紹下:JMX 就是 Java 提供的一個標準,這個標準可以將一些有需要的 Java 物件執行時的狀態暴露出去(這些物件可以叫 MBean),一般是用於監控或者執行時修改一些配置資訊。

我們既然是講解 ZK,那麼我現在就有問題了,現在我手裡有一個簡單 ZK 叢集正在執行,我想知道哪個節點是 Leader,怎麼辦?

首先 ZK 本身在啟動的時候就會主動將一些物件註冊成為 MBean,而我們直接使用 Java 自帶的工具 jconsole 就能檢視,下面我演示下,我這裡有一個簡單的 ZK 叢集:

$ jps
5266 QuorumPeerMain
2964
10438 Jps
4550 Launcher
5286 QuorumPeerMain
5767 JConsole
5388 QuorumPeerMain
9215 Launcher

可以看到有三個 QuorumPeerMain 的程式,就代表了這個叢集的三個節點。

下面我們使用 jconsole 開啟該工具(安裝了 jdk,這個工具就自動擁有了)

$ jconsole

隨便選擇一個 ZK 的程式,選擇連線即可。

JMX 不光光可以看物件的屬性也可以執行一些方法,比如:

圖中顯示操作的地方都是可以在右側找到一個按鈕進行呼叫的(還可以傳參),關於 ZK 中 JMX 更多的細節,我這裡暫時不披露了,之後有機會單獨講解,反正大家只需要知道 JMX 就是將一些普通 Java 物件暴露出去,可以通過工具檢視屬性或者呼叫該物件的方法的一個標準。

二、四字命令 與 Admin Server

剛剛那個 JMX 檢視還是比較麻煩的,因為現在我們測試訪問的是我本地的程式,如果是遠端的 JVM 程式,用 jconsole 訪問起來就更麻煩了,有沒有簡單一點的方法。肯定是有!ZK 本身支援了一些四字命令(4lw)用於和服務端進行互動。

我這裡做一個簡單的演示,我本地的叢集的客戶端埠分別是:2181、2182、2183,我通過 telnet 命令隨便連線上一個節點:

$ telnet localhost 2181
Trying ::1...
Connected to localhost.
Escape character is '^]'.

就會進入互動模式,然後輸入 srvr 按下回車,就能得到以下輸出

$ telnet localhost 2181
Trying ::1...
Connected to localhost.
Escape character is '^]'.
srvr
Zookeeper version: 3.6.2--803c7f1a12f85978cb049af5e4ef23bd8b688715, built on 09/04/2020 12:44 GMT
Latency min/avg/max: 0/3.8261/44
Received: 30
Sent: 29
Connections: 1
Outstanding: 0
Zxid: 0x100000009
Mode: leader
Node count: 6
Proposal sizes last/min/max: 48/48/94
Connection closed by foreign host.

srvr 命令就是用來檢視服務節點的狀態的,從輸出中的 Mode 欄位就能看到,監聽 2181 埠的這個節點就是 Leader,我們再換一個 2182 節點看看,Mode 就是 Follower。

$ telnet localhost 2182
Trying ::1...
Connected to localhost.
Escape character is '^]'.
srvr
Zookeeper version: 3.6.2--803c7f1a12f85978cb049af5e4ef23bd8b688715, built on 09/04/2020 12:44 GMT
Latency min/avg/max: 0/0.0/0
Received: 3
Sent: 3
Connections: 1
Outstanding: 0
Zxid: 0x100000009
Mode: follower
Node count: 6
Connection closed by foreign host.

這裡要提一下預設的四字命令不是全部開啟的,如果想要啟用所有的四字命令需要在環境變數中指定 zookeeper.4lw.commands.whitelist=*,也可以通過列出具體的命令(逗號分隔)來啟用指定的一些四字命令。我們再換個命令看看吧,比如 envi,就會輸出當前節點的環境引數

java.io.tmpdir=/var/folders/19/bx8xsqgd1c78g5j1mt_zq5v80000gp/T/
java.compiler=<NA>
os.name=Mac OS X
os.arch=x86_64
os.version=10.16
user.name=junjiexun
user.home=/Users/junjiexun
user.dir=/Users/junjiexun/develop/zk
os.memory.free=101MB
os.memory.max=889MB
os.memory.total=123MB

我這裡列一下所有的四字命令的作用,具體就不演示了,留給讀者自己嘗試吧,官網四字命令列表,四字命令其實就是它右邊命令的別名而已,作用是完全一樣的。(* 依舊錶示 TODO,之後開篇單講)

命令 返回資料的作用
conf / configuration 配置資訊(常用的 dataDirclientPort 等)
cons / connections 連線資訊
crst / connection_stat_reset 重置連線統計資訊
dump 會話資訊和臨時節點
envi / environment 環境變數資訊(os.nameuser.home 等)
ruok 服務是否正常
srst / stat_reset 重置統計資訊
srvr / server_stats 服務端資訊概覽
stat 服務端資訊統計
wchs / watch_summary 回撥 watcher 的彙總
wchc / watches 註冊回撥的 session 資訊彙總
dirs log 和 snap 檔案位元組大小
wchp / watches_by_path 註冊回撥的路徑資訊
mntr / monitor 所有監控資訊
isro / is_read_only 當前節點是否是隻讀
hash 數字摘要
gtmk / get_trace_mask* 獲取跟蹤掩碼
stmk / set_trace_mask* 設定跟蹤掩碼
lsnp / last_snapshot 最後一次快照的資訊
icfg / initial_configuration 服務端啟動初始配置
orst / observer_connection_stat_reset 重置 Observer 連線統計資訊
obsr / observers 獲取 Observer 的資訊
sysp / system_properties 環境變數資訊和 envi 不同的是會返回 zookeeper 開頭的自定義配置
lead / leader 當前節點是否是 Leader
voting_view 叢集選票資訊
zabstate ZAB 狀態資訊彙總

我還看到了 ruok,用來檢視伺服器節點是否啟動(能成功返回不代表能對外提供服務 )

除了通過 telnet 方式去呼叫 ZK 提供的四字命令,ZK 還提供了一個更友好的方式就是 Admin Server。

ZK 在啟動後,預設會監聽本機的 8080 埠並啟動一個 Jetty 容器作為 Web 伺服器,如果訪問該埠下的 /commands 路徑的話會得到:

直接訪問這些超連結就可以擁有和之前四字命令一樣的效果~也可以直接在 URL 上訪問 ip:port/commands/<commandName>

mntr為例,可以直接訪問 http://localhost:8080/commands/mntr 或者 http://localhost:8080/commands/monitor 都是一樣的。

因為 Admin Server 預設就是啟用的,而且接受來自任何 IP 的請求,為了安全考慮的話可以通過配置環境變數 zookeeper.admin.serverAddress=10.3.54.12,類似這樣增加請求的 IP 的要求或者直接通過 zookeeper.admin.enableServer=false 禁用 Admin Server。

ZK 官方是推薦直接使用 Admin Server 的,來替代命令列的四字命令的,基於這些官方的介面是可以做一些 ZK 監控平臺的。

三、動態配置

ZK 的叢集配置一般都是在啟動的時候通過讀取配置檔案,之後就不會再變更了,並且如果我要為叢集新增一個新的節點,需要修改配置檔案再重啟方才能生效的。

但是在 3.5.0 之後, ZK 更新了動態配置的功能,叢集的配置不再需要停機重新配置,可以在執行時直接修改,可以直接為叢集增刪節點,修改他們的角色,甚至可以修改叢集的計票規則!是不是碉堡了!

3.1 計票規則

在此之前,我先介紹下 ZK 支援的兩種計票規則。

3.1.1 過半機制

這是 ZK 預設的計票規則,用於各種服務端叢集需要 ACK 的場景,假設現在的配置是這樣:

server.1=zoo1:2888:3888:participant
server.2=zoo2:2888:3888:participant
server.3=zoo3:2888:3888:participant
server.4=zoo4:2888:3888:observer
server.5=zoo5:2888:3888:observer

因為 Observer 是不會算入選票的,實際參與的機器是前三個節點:1、2、3

先不管 Leader 是誰,預設的計票規則需要這三個節點中的至少兩個成功提交 ACK(或是其他需要計票的資訊),這個選舉(或者提案)才能被繼續提交,這就是過半機制。

3.1.2 分組權重

ZK 還提供了一個新的計票規則,這個規則支援將各個節點分成不同的組(當然也可以只有一個組),同一個組中的不同節點也可以被分配成不同的權重,我舉個例子:

group.1=1:2:3
group.2=4:5:6:7:8
group.3=9

weight.1=1
weight.2=1
weight.3=1
weight.4=1
weight.5=1
weight.6=1
weight.7=1
weight.8=1
weight.9=1

group 開頭和 weight 開頭分別對應了分組和權重的配置,規定如下:

  • group 的格式是 group.<groupId>=<serverId>:<serverId>...
  • weight 的格式是 weight.<serverId>=<weight>
  • serverId 就是每一個服務節點配置在 myid 中的數字
  • 每一個節點只能屬於一個 group

那以我現在配置的情況繼續說明的話,現在一共有三個 group,分別的權重計算如下 :

group1 的權重總和 = server1 的權重 + server2 的權重 + server3 的權重 = 1 + 1 + 1 = 3
group2 的權重總和 = server4 的權重 + server5 的權重 + server6 的權重 + server7 的權重 + server8 的權重= 1 + 1 + 1 + 1 + 1 = 5
group3 的權重總和 = server9 的權重 = 1

假如現在成功 ACK 的服務節點有 1、4、5、8、9 並以這樣的配置進行計票的話

  • 首先看 group1 只有 server1 成功回覆 ACK,權重值為 1,並未超過 group1 權重總和 3 一半以上,所以 group1 相當於 ACK 失敗了
  • 再看 group2 有 4、5、8 三個節點成功回覆 ACK,權重值為 3,超過了 group2 權重總和 5 的一半以上(3 > 5/2),所以 group2 ACK 成功
  • 然後看 group3,因為只有一個節點 9,併成功回覆 ACK,所以也滿足了超過 group3 權重總和 1 的一半以上(1 > 1/2),所以 group3 ACK 成功
  • 最後統計成功 ACK 的 group 數量是否超過整體 group 數量的一半以上,現在有 2 個 group 成功 ACK(2 > 3/2) ,所以最終 ACK 通過

感覺看起來和預設的過半機制差不多,No,No,No。我這裡看起來差不多的原因是因為我把權重都設定成了 1,如果設定成別的數字呢?

剛剛的場景中只有 group1 的 ACK 最終失敗了,原因是因為只有 server1 一個節點成功回覆,但是如果我把 group1 的權重改成(另外兩個 group 省略了)

group.1=1:2:3

weight.1=3
weight.2=1
weight.3=1

現在 group1 的權重總和變成了 3 + 1 + 1 = 5,server1 的權重是 3 了,就算只有它一個節點回復了,也超過了 group1 的一半以上(3 > 5/2),如果是此時的權重配置的話,group1 也是算作成功 ACK 的。

還有必須要說明的一點,在 ZK 讀取這些配置的時候就會計算每一個 group 的權重總和,如果計算出來某一個 group 的權重總和是 0,則該 group 被移除出計票規則中了。

和預設的過半機制不同的是,使用權重配置的話,是可以讓 Observer 參與的。

3.2 推薦配置

說了半天,怎麼啟用這個權重的計票規則呢?

  • (推薦)在 zoo.cfg 中配置 dynamicConfigFile 選項用來指定動態配置的路徑地址,將所有的 server 、group 和 weight 開頭的配置都移至該路徑的配置檔案中。
  • 將所有的 server 、group 和 weight 開頭的配置都直接配置在 zoo.cfg 檔案中

只要在配置檔案中被 ZK 發現有 group 或者 weight 開頭的配置,就表示啟用權重的計票規則,否則使用預設的過半機制。

我們之前的 server 字首配置是這樣:

server.1=zoo1:2888:3888:participant

實際的 server 配置的格式應該是這樣的,可以將客戶端的埠配置在 server 配置中的(最後的分號後面)

server.1=zoo1:2888:3888:participant;2181

如果這樣配置的話,zoo.cfg 中就不需要配置 clientPort 選項了。

所以按照推薦的配置方式的話,zoo.cfg 就配置這些(路徑請根據讀者的電腦自行調整)

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/Users/junjiexun/develop/zk/zk01/data
dynamicConfigFile=/Users/junjiexun/develop/zk/zk01/conf/zoo.dyn.cfg
...

而我在 /Users/junjiexun/develop/zk/zk01/conf/zoo.dyn.cfg 的檔案中就可以配置

server.10000000000=127.0.0.1:2888:3888:participant;2181
server.2=127.0.0.1:2887:3887;2182
server.3=127.0.0.1:2886:3886;2183

group.1=10000000000:2:3

weight.10000000000=1
weight.2=1
weight.3=1

更詳細的可以檢視官方文件

3.3 動態修改

那麼問題來了,我這樣配置好了後,怎麼才能動態的往叢集中新增節點或者刪除節點呢?

Java 的客戶端提供了一個 getConfig 的方法

ZooKeeper client = new ZooKeeper("127.0.0.1:2181", 3000, null);
byte[] config = client.getConfig(false, null);
System.out.println(new String(config));
client.close();

列印出來的結果是

server.2=127.0.0.1:2887:3887:participant;0.0.0.0:2182
server.3=127.0.0.1:2886:3886:participant;0.0.0.0:2183
server.10000000000=127.0.0.1:2888:3888:participant;0.0.0.0:2181
group.1=2:3:10000000000
weight.2=1
weight.3=1
weight.10000000000=1
version=0

這些資訊看起來和我們配置的 zoo.dyn.cfg 有點像但是又有點不一樣,不一樣的地方實際就是 ZK 幫我們自動補齊的格式,而這個返回的資料 ZK 是存在哪裡的呢?ZK 在啟動的時候預設會在根路徑建立以下節點

/
|--zookeeper
     |--config
     |--quota

getConfig 返回的資料實際就是 /zookeeper/config 節點的資料,而且這個節點的許可權只有 Read,不信你用 getData 試試,返回的資料是一樣的

ZooKeeper client = new ZooKeeper("127.0.0.1:2181", 3000, null);
byte[] config = client.getData("/zookeeper/config", false, null);
System.out.println(new String(config));
client.close();

現在可以獲取到這個配置了,那怎麼去修改呢?ZK 官方提供了兩種方式:命令列、Java API。

如果使用 Java 內建的命令列工具,在支援的命令中就有一個 reconfig 命令,引數是:

reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]

另一種就是使用 Java 的客戶端程式碼,我們之前一直使用的是 ZooKeeper 這個類,他還有一個子類叫 ZooKeeperAdmin,這個子類就擁有 reconfigure 方法可以對配置進行修改,下面我來演示下,但在此之前我必須說明下動態修改配置的特性

  • 動態修改配置分為:增量和非增量的方式
  • 因為實際上修改的就是 /zookeeper/config 節點的資料,而這個節點預設只有 Read 許可權,所以要麼直接使用管理員許可權進行修改操作,要麼就在環境變數中配置 zookeeper.skipACL=yes 跳過 ACL 的校驗
  • 使用增量方式修改配置的時候,叢集的計票規則必須是過半機制!
  • 使用非增量的方式修改配置時,兩種機制均可。
  • 將 Follower 從叢集配置中刪除,只是相當於把它降級為 Observer,它是仍然可以對外提供服務端,並且也同樣可以接受到 Leader 的訊息
  • 將 Leader 從叢集配置中刪除時,會造成較大的效能影響,整個叢集在選出新的 Leader 之前是無法對外提供服務的,請儘量不要這麼做
  • 而增加節點就輕鬆很多,新加入的節點會自動和 Leader 進行同步資料

3.3.1 增量刪除節點

假設我現在一共有 3 個節點,採用的是過半機制(必須得是),三個 ID 分別是 10000000000、2、3,我們嘗試將 ID 為 3 的節點刪除,我這裡採用直接配置 skipACL 跳過許可權校驗(下同)

ZooKeeperAdmin client = new ZooKeeperAdmin("127.0.0.1:2181", 3000, null);
List<String> leavingServers = new ArrayList<>();
leavingServers.add("3");
byte[] reconfigure = client.reconfigure(null, leavingServers, null, -1, null);
System.out.println(new String(reconfigure));
client.close();

該介面返回的資料就是 /zookeeper/config 修改完成後的配置資訊,可以看到新的配置中和 3 有關的資料就消失了

server.2=127.0.0.1:2887:3887:participant;0.0.0.0:2182
server.10000000000=127.0.0.1:2888:3888:participant;0.0.0.0:2181
version=400000004

這個 version=400000004 是幹嘛的呢?ZK 為修改的配置預設也提供了版本的控制,啟動成功後會在你配置的 dynamicConfigFile 路徑下自動生成一個檔案,我這裡是 zoo.cfg.dynamic.300000000 讀者可能跟我的不一樣。這個 300000000 就是版本號,而當我把 ID 為 3 的節點刪除後,ZK 又自動生成了個檔案 zoo.cfg.dynamic.400000004 這個 400000004 就是新的版本號,如果我們在修改的時候對當前叢集配置的版本號有要求的話就可以在 reconfigure 方法中的第四個引數填入需要的目標版本號即可,我例子中是 -1 代表無視版本號,和 delete、setData 的 version 欄位是一個用意。

3.3.2 增量增加節點

讓我們再把節點 3 加回去

ZooKeeperAdmin client = new ZooKeeperAdmin("127.0.0.1:2181", 3000, null);
List<String> joiningServers = new ArrayList<>();
joiningServers.add("server.3=127.0.0.1:2886:3886;2183");
byte[] reconfigure = client.reconfigure(joiningServers, null, null, -1, null);
System.out.println(new String(reconfigure));
client.close();

得到的新配置內容是

server.10000000000=127.0.0.1:2888:3888:participant;0.0.0.0:2181
server.2=127.0.0.1:2887:3887:participant;0.0.0.0:2182
server.3=127.0.0.1:2886:3886:participant;0.0.0.0:2183
version=400000013

節點 3 加回去了,而且版本號又改變了,又多了一個 zoo.cfg.dynamic.400000013 檔案

3.3.3 非增量

ZooKeeperAdmin client = new ZooKeeperAdmin("127.0.0.1:2181", 3000, null);
List<String> newMembers = new ArrayList<>();
newMembers.add("server.10000000000=127.0.0.1:2888:3888:participant;2181");
newMembers.add("server.3=127.0.0.1:2886:3886;2183");
byte[] reconfigure = client.reconfigure(null, null, newMembers, -1, null);
System.out.println(new String(reconfigure));
client.close();

這樣的話相當於把 ID 為 2 的節點給刪除了(當然也可以新增節點,我這裡就不演示了)

3.4 小節

三種不同的方式實際對應的就是 Java API 的三個引數 joiningServersleavingServersnewMembers,而且 Java API 引數除了使用 List 還可以使用 String(逗號分隔)也可以達到同樣的效果。動態增刪服務節點讓我們可以在避免停機的前提下調整整個 ZK 叢集的服務能力(個人覺得動態增加比較有用)。

而分組權重的計票規則提供了一種新的歸票策略,特別是配合動態配置,可以執行時修改權重,但總體來說,分組權重的計票規則比較雞肋,我也不知道能在什麼樣的場景下使用(高效能的機器可以權重大一點?問題是現在都是雲服務,容器化和虛擬化的,機器的配置都可以動態調整的,而且一般機器配置也都是一樣的,實在是想不到還有什麼用)

如果讀者有關於分組權重的使用思路可以分享給大家噢~關於更多的動態配置可以參考 官方文件

四、ZK 監控

ZK 3.6 之後新增的 Metrics 是 ZK 提供給使用者可查詢的監控指標,官網上也說了可以結合 Prometheus 或者 Grafana 來使用。什麼?你完全沒用過,甚至沒聽說過!那巧了這兩個東西我也沒玩過,藉著這個機會就和大家一起學習下,搞個 Hello World,不過因為本系列是以 ZK 為主的,所以一切從簡配置,Let's GO!

4.1 Prometheus

Mac 安裝特別簡單,其他平臺可以去官網下載壓縮包

$ brew install prometheus

我這裡的預設安裝路徑是 /usr/local/Cellar/prometheus/2.23.0

在此之前還需要在 ZK 節點的配置 zoo.cfg 加兩行

metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000

我是本地起的三個節點,另外兩個節點需要改成 7001 和 7002,因為埠不能重複

之後修改 Prometheus 的預設配置,在我的電腦上路徑為 /usr/local/etc/prometheus.yml 修改為

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: "test-zk"
    static_configs:
      - targets: ["localhost:7000", "localhost:7001", "localhost:7002"]

job_name 可以隨便起,重點是 targets 目標地址和 scrape_interval 訪問間隔,修改完畢後,就可以啟動 Prometheus

$ cd /usr/local/Cellar/prometheus/2.23.0
$ ./bin/prometheus --config.file=/usr/local/etc/prometheus.yml

然後訪問 localhost:9090 就能看到如下介面了:

只要勾上了 Enable autocomplete 就可以在輸入框裡輸入了,馬上就能得到提示,我這裡隨便輸幾個引數看看

簡單的就演示到這裡了,剩下的交給讀者了~

4.2 Grafana

Mac 安裝 Grafana 也非常簡單

$ brew install grafana

安裝完畢後,可以通過命令啟動

$ grafana-server --config=/usr/local/etc/grafana/grafana.ini --homepath /usr/local/share/grafana --packaging=brew cfg:default.paths.logs=/usr/local/var/log/grafana cfg:default.paths.data=/usr/local/var/lib/grafana cfg:default.paths.plugins=/usr/local/var/lib/grafana/plugins

Grafana 預設的埠是 3000,訪問 localhost:3000 預設的使用者名稱密碼均是 admin,就能看到首頁了

Grafana 天然支援了 Prometheus 的資料來源,可以直接新增

預設的配置只需要修改 URL(預設是 localhost:9090)

然後需要新增一個 dashboard 的模板,ZK 官方給我們提供了一個模板,貼心吧

10465 這個數字哪兒來的呢?官方文件的模板

大功告成!比 Prometheus 好看多了~

關於 ZK 的監控就介紹到這裡了。傳統功夫,點到為止~

五、ZK 視覺化開源專案介紹

使用命令列操作 ZK 太麻煩了,所以視覺化就很有必要了,下面推薦幾個不錯的視覺化客戶端,有的是本地客戶端,有的是 Web 服務,大家按需獲取吧~

六、最後

老規矩,如果你有任何對文章中的疑問也可以是建議或者是對 ZK 原理部分的疑問,歡迎來倉庫中提 issue 給我們,或者來語雀話題討論。本文首發於 「HelloGitHub」公眾號

地址:https://www.yuque.com/kaixin1002/yla8hz

相關文章