一、關於zookeeper
Zookeeper 作為一個分布式的服務框架,主要用來解決分布式集群中應用系統的一致性問題,它能提供基於類似於文件系統的目錄節點樹方式的數據存儲, Zookeeper 作用主要是用來維護和監控存儲的數據的狀態變化,通過監控這些數據狀態的變化,從而達到基於數據的集群管理。
簡單的說,zookeeper=文件系統+通知機制。
1. ZNode節點
ZNode被分為持久(persistent)節點,順序(sequential)節點和臨時(ephemeral)節點。
- 持久節點:即使在創建該特定znode的客戶端斷開連接后,持久節點仍然存在。默認情況下,除非另有說明,否則所有znode都是持久的。
- 順序節點:順序節點可以是持久的或臨時的。當一個新的znode被創建為一個順序節點時,ZooKeeper通過將10位的序列號附加到原始名稱來設置znode的路徑。例如,如果將具有路徑 /test 的znode創建為順序節點,則ZooKeeper會將路徑更改為 /test0000000001 ,並將下一個序列號設置為0000000002。如果兩個順序節點是同時創建的,那么ZooKeeper不會對每個znode使用相同的數字。順序節點在鎖定和同步中起重要作用。
- 臨時節點:客戶端活躍時,臨時節點就是有效的。當客戶端與ZooKeeper集合斷開連接時,臨時節點會自動刪除。因此,只有臨時節點不允許有子節點。如果臨時節點被刪除,則下一個合適的節點將填充其位置。臨時節點在leader選舉中起着重要作用。
2. Session回話
會話對於ZooKeeper的操作非常重要。會話中的請求按FIFO順序執行。一旦客戶端連接到服務器,將建立會話並向客戶端分配會話ID 。
客戶端以特定的時間間隔發送心跳以保持會話有效。如果ZooKeeper集合在超過服務器開啟時指定的期間(會話超時)都沒有從客戶端接收到心跳,則它會判定客戶端死機。
會話超時通常以毫秒為單位。當會話由於任何原因結束時,在該會話期間創建的臨時節點也會被刪除。
3.Watcher監視
監視是一種簡單的機制,使客戶端收到關於ZooKeeper集合中的更改的通知。客戶端可以在讀取特定znode時設置Watches。Watches會向注冊的客戶端發送任何znode(客戶端注冊表)更改的通知。
Znode更改是與znode相關的數據的修改或znode的子項中的更改。只觸發一次watches。如果客戶端想要再次通知,則必須通過另一個讀取操作來完成。當連接會話過期時,客戶端將與服務器斷開連接,相關的watches也將被刪除。
二、Zookeeper客戶端命令
執行bin/zkCli.sh連接zookeeper服務器后,我們隨便敲一個aa然后回車,可以看到支持如下命令:
stat path [watch] --- 查看節點狀態 set path data [version] --- 設置節點數據 ls path [watch] --- 列出子節點 delquota [-n|-b] path --- 刪除節點配額 ls2 path [watch] --- 列出子節點,並顯示當前節點信息 setAcl path acl --- 設置節點權限 setquota -n|-b val path --- 設置節點配額 history --- 列出歷史命令 redo cmdno --- 執行歷史命令 printwatches on|off delete path [version] --- 刪除節點,如果待刪除節點包含子節點則刪除失敗 sync path listquota path --- 列出節點配額 get path [watch] --- 獲取節點數據 create [-s] [-e] path data acl --- 創建子節點 addauth scheme auth --- 增加zookeeper用戶認證 quit --- 退出zkCli getAcl path --- 查詢節點權限 close --- 關閉當前連接 connect host:port --- 連接zkServer
1. ls
此命令用於列出和顯示znode的子項。ls命令要求路徑以/開頭,並支持tab鍵自動補全。例如:
- 執行:ls / 輸出[dubbo, task, zookeeper, test, disconf]: 說明根節點下包含5個子節點。
- 執行:ls /test 輸出[test1-2, test1-1]:說明/test節點下面有2個節點。
- 執行:ls /test/test1-2 輸出[]:說明/test/test1-2節點下面沒有子節點。
2. ls2
此命令也用於列出和顯示znode的子項,與ls命令不同的是,該命令同時列出該節點的time、version等信息。例如:
執行 ls /test , 輸出
[test1-2, test1-1] cZxid = 0x69043 ctime = Mon Sep 03 06:37:21 CST 2018 mZxid = 0x69043 mtime = Mon Sep 03 06:37:21 CST 2018 pZxid = 0x69045 cversion = 2 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 2
3. create
命令格式: create [-s] [-e] path data acl
- -s:創建順序節點。當一個新的znode被創建為一個順序節點時,ZooKeeper通過將10位的序列號附加到原始名稱來設置znode的路徑。
- -e:創建臨時節點。客戶端活躍時,臨時節點就是有效的。當客戶端與ZooKeeper集合斷開連接時,臨時節點會自動刪除。
- path:待創建的節點路徑。以/開頭。
- data:帶創建的節點存儲的數據內容。
- acl:權限控制。后面再說。
例如:
- 創建永久節點:create /test/test1-3 "nodeData"
- 創建臨時節點:create -e /test/test1-4 "nodeData"
- 創建循序節點:create -s /test/test1-5 "nodeData"
執行完以上命令以后,再執行 ls /test,可以看到輸出結果:[test1-4, test1-2, test1-3, test1-1, test1-50000000004],可以看到創建的順序節點test1-5被增加了序列號。我們執行quit退出zkCli,再重新執行bin/zkCli.sh重新連接,執行 ls /test,輸出結果:[test1-2, test1-3, test1-1, test1-50000000004],可以看到創建的臨時節點test1-4已經沒有了。
4. get
命令格式: get path [watch]。它返回znode的關聯數據和指定znode的元數據。你將獲得信息,例如上次修改數據的時間,修改的位置以及數據的相關信息。
- path:節點路徑。以/開頭。
- watch:監控。后面再說。
例如:執行 get /test/test1-3 輸出
"nodeData" cZxid = 0x69046 ctime = Mon Sep 03 07:00:25 CST 2018 mZxid = 0x69046 mtime = Mon Sep 03 07:00:25 CST 2018 pZxid = 0x69046 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0
5. set
命令格式 set path data [version]。設置指定znode的數據。
- path:節點路徑。以/開頭。
- data:節點數據內容。
- version: 當前數據版本號。如果該參數和當前數據版本號不一致則會拋異常。
例如:執行 set /test/test1-3 "dataNode-New",然后我們再執行 get /test/test1-3 查看數據,輸出:
"dataNode-New" cZxid = 0x69046 ctime = Mon Sep 03 07:00:25 CST 2018 mZxid = 0x6904b mtime = Mon Sep 03 07:42:46 CST 2018 pZxid = 0x69046 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0
可以看到dataVersion已經發生了改變,當前 dataVersion = 1。
6. delete
命令格式:delete path [version]。刪除指定節點,如果要刪除的節點包含子節點則會刪除失敗。
- path:節點路徑。以/開頭。
- version: 當前數據版本號。如果該參數和當前數據版本號不一致則會拋異常。
例如:執行 delete /test/test1-3,再執行 ls /test,輸出:[test1-2, test1-1, test1-50000000004],可以看到test1-3這個節點已經被刪除掉了。
7. addauth
命令格式:addauth scheme auth。該命令為zookeeper增加一個用戶認證,例如:addauth digest zookeeper:zookeeper-pass。其中zookeeper為用戶名,zookeeper-pass為密碼。
8. history
命令格式:history。該命令用來查看歷史命令,查詢到的歷史命令前會有編號,提供redo操作。
9. redo
命令格式:redo cmdno。再次執行歷史命令,配合history命令使用。參數cmdno即為history命令執行結果中的編號
三、Zookeeper權限控制
zookeeper支持的命令中,有如下兩條命令是和權限相關的:
- setAcl path acl
- getAcl path
- create [-s] [-e] path data acl
zookeeper共支持5中權限,分別是
- create:創建節點
- delete:刪除節點
- read:讀取節點數據以及子節點
- write:修改節點數據
- admin:設置節點權限
zookeeper的ACL,可以從三個維度來理解:一是scheme;二是user;三是permission。通常表示為scheme:id:permissions。
授權模式
- world: 表示所有,創建節點時默認此權限范圍。有唯一的id是anyone。例如:setAcl /test/test1-1 world:anyone:cdrwa。表示/test/test1-1節點所有人都可以執行cdrwa操作。
- auth:它不需要id,只要是通過認證的user都有權限。例如:setAcl /test/test1-2 auth:zookeeper:zookeeper-pass:cdrwa。表示通過密碼驗證的用戶名為zookeeper的用戶可執行cdrwa操作。當然,執行此命令的前提是已經通過addauth命令添加了該用戶名密碼的用戶。執行完setAcl命令后,再執行getAcl /test/test1-2,返回輸出結果:
'digest,'zookeeper:hdtnoeyerDQpPGLJ41lW1u1lCSA= : cdrwa
- digest:類似於auth授權。與auth授權的區別在於,輸入命令的時候,用戶密碼需要先進行SHA-1加密再進行Base64編碼。首先,先生成加密后的密碼,執行:echo -n zookeeper:zookeeper-pass | openssl dgst -binary -sha1 | openssl base64,輸出:hdtnoeyerDQpPGLJ41lW1u1lCSA=。可以看到該輸出值即為auth授權后重新getAcl得到的值。然后,執行digest授權:setAcl /test/test1-3 digest:zookeeper:hdtnoeyerDQpPGLJ41lW1u1lCSA=:cdrwa。通過 getAcl /test/test1-3 可以看到授權結果和 getAcl /test/test1-2完全一致
- ip:它對應的id為客戶機的IP地址,設置的時候可以設置一個ip段,比如執行:setAcl /test/test1-4 ip:192.168.1.0/16:cdrwa, 表示匹配前16個bit的IP段。
四、quota配額
zookeeper可以在znode上設置配額限制。如果超出了配置限制,zookeeper將會在log日志中打印WARN日志。如果超出配額限制,並不會停止行為操作。
1. setquota
命令格式:setquota -n|-b val path。為節點設定配額。例如命令:setquota -n 10 /test/test1-1
- -n :限制子節點個數
- -b :限制節點數據長度
- val : 限制的具體的值
- path : 要做限制的節點路徑
2. delquota
命令格式:delquota [-n|-b] path。刪除節點配額。例如命令:delquota -n /test/test1-1
- -n|-b : 要刪除的配額類型
- path : 要做限制的節點路徑
3. listquota
命令格式:listquota path。列出節點設置的配額。例如命令:listquota /test/test1-1
五、zookeeper in java
@Test public void testClient() throws IOException, InterruptedException, KeeperException { final String zookeeperHost = "10.5.31.155"; final String zookeeperPort = "2181"; // 鏈接zk服務器 final CountDownLatch countDownLatchConnect = new CountDownLatch(1); final ZooKeeper zooKeeper = new ZooKeeper(zookeeperHost + ":" + zookeeperPort, 60000, event -> { if (event.getState().equals(Watcher.Event.KeeperState.SyncConnected)) { countDownLatchConnect.countDown(); } }); countDownLatchConnect.await(); // watch節點 並執行創建 zooKeeper.exists("/clientTestNode", System.out::println); zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // watch節點 並執行刪除 zooKeeper.exists("/clientTestNode", System.out::println); zooKeeper.delete("/clientTestNode", -1); // 1. 創建節點 2. watch節點數據 3. 修改節點數據 4. 刪除節點 zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); byte[] dataOld = zooKeeper.getData("/clientTestNode", System.out::println, null); System.out.println(new String(dataOld)); zooKeeper.setData("/clientTestNode", "clientTestNodeDataNew".getBytes(), -1); byte[] dataNew = zooKeeper.getData("/clientTestNode", System.out::println, null); System.out.println(new String(dataNew)); zooKeeper.delete("/clientTestNode", -1); // 1. 創建節點 2. 監控節點的子節點 3. 創建子節點 4. 監控節點子節點 5. 刪除子節點 zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.create("/clientTestNode/child1", "child1Data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.create("/clientTestNode/child2", "child2Data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.delete("/clientTestNode/child1", -1); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.delete("/clientTestNode/child2", -1); zooKeeper.delete("/clientTestNode", -1); }
需要注意的點:
- 由於zookeeper的連接是異步過程,所以執行new Zookeeper之后不要緊接着執行zookeeper命令。否則可能會出現丟失連接異常。
- 由於zookeeper的watcher是一次性有效(即zookeeper回調watcher后該watcher即被刪除),所以會出現重復注冊watcher的現象