1. 程式人生 > >【ZooKeeper系列】2.用Java實現ZooKeeper API的呼叫

【ZooKeeper系列】2.用Java實現ZooKeeper API的呼叫

溫馨提示:在這裡我再次提個小要求,希望大家能習慣看官方文件,文件雖然是英文但用詞都比較簡單,基本都能看懂文件表達的意思。授之以魚不如授之以漁的道理相信大家都明白,也希望通過猿人谷的這個ZooKeeper系列,讓大家入門、到熟悉,舉一反三後能精通ZooKeeper。

在前一篇我們介紹了ZooKeeper單機版、偽叢集和叢集環境搭建,通過命令列的方式做了節點的建立、刪除、更新、獲取節點資訊的測試。Zookeeper 的目的是為客戶端構建複雜的協調功能提供簡單、高效的核心 API,這一篇我們用Java通過ZooKeeper提供的API介面來實現這些增刪改查的功能。

1 簡介

org.apache.zookeeper.Zookeeper

是ZooKeeper客戶端的主類,在官方文件中已明確說明(This is the main class of ZooKeeper client library.)。

This is the main class of ZooKeeper client library. To use a ZooKeeper service, an application must first instantiate an object of ZooKeeper class. All the iterations will be done by calling the methods of ZooKeeper class. The methods of this class are thread-safe unless otherwise noted.

Once a connection to a server is established, a session ID is assigned to the client. The client will send heart beats to the server periodically to keep the session valid.

建立一個ZooKeeper的例項來使用org.apache.zookeeper.Zookeeper裡的方法,官方文件已經指出沒有特別宣告的話,ZooKeeper類裡的方法是執行緒安全的。客戶端連線到ZooKeeper服務的時候,會給客戶端分配一個會話ID(session ID),客戶端與服務端會通過心跳來保持會話有效。

org.apache.zookeeper.Zookeeper裡的方法非常多,就不一一列舉了,只列幾個增刪改查的。
|Method | Description |
|--|--|
| create(String path, byte[] data, List acl, CreateMode createMode) | Create a node with the given path. (建立指定路徑的節點)|
| create(String path, byte[] data, List acl, CreateMode createMode, AsyncCallback.Create2Callback cb, Object ctx) | The asynchronous version of create.(非同步形式建立) |
| create(String path, byte[] data, List acl, CreateMode createMode, Stat stat) | Create a node with the given path and returns the Stat of that node.(按指定路徑建立節點並返回節點狀態資訊) |
| delete(String path, int version) | Delete the node with the given path.(刪除指定路徑的節點) |
| delete(String path, int version, AsyncCallback.VoidCallback cb, Object ctx) | The asynchronous version of delete.(非同步刪除指定路徑的節點) |
| exists(String path, boolean watch) | Return the stat of the node of the given path.(返回指定路徑的節點狀態資訊) |
| getChildren(String path, boolean watch) | Return the list of the children of the node of the given path.(返回指定路徑的所有子節點狀態資訊) |
| getData(String path, boolean watch, Stat stat) | Return the data and the stat of the node of the given path.(返回指定路徑的節點資料和狀態資訊) |
| setData(String path, byte[] data, int version) | Set the data for the node of the given path if such a node exists and the given version matches the version of the node (if the given version is -1, it matches any node's versions).(給指定路徑和版本的節點設定新值,如版本為-1,即給所有版本設定值) |

2 測試環境搭建

這裡新建一個Spring Boot的專案來進行測試,新建Spring Boot專案的過程很簡單,也不是這裡的重點,就不做介紹了。

專案裡會需要額外引入兩個包來進行測試:

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.5.2</version>
        </dependency>

3 API測試

完整測試程式碼如下:

/**
 *  簡單測試示例
 * @author 猿人谷
 * @date 2019/12/16
 */
public class ZooKeeperDemo {

    private static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperDemo.class);

    private static final int SESSION_TIME_OUT = 10000;
    // ZooKeeper服務的地址,如為叢集,多個地址用逗號分隔
    private static final String CONNECT_STRING = "127.0.0.1:2181";
    private static final String ZNODE_PATH = "/zk_demo";
    private static final String ZNODE_PATH_PARENT = "/app1";
    private static final String ZNODE_PATH_CHILDREN = "/app1/app1_1";

    private ZooKeeper zk = null;

    @Before
    public void init() throws IOException {
        zk = new ZooKeeper(CONNECT_STRING, SESSION_TIME_OUT, new Watcher(){
            @Override
            public void process(WatchedEvent event) {
                System.out.println("已經觸發了" + event.getType() + "事件!");
            }
        });

    }

    @Test
    public void testCreate() throws KeeperException, InterruptedException {
        zk.create(ZNODE_PATH, "anna2019".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    @Test
    public void testCreateParentZnode() throws KeeperException, InterruptedException {
        zk.create(ZNODE_PATH_PARENT, "anna2019".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    @Test
    public void testCreateChildrenZnode() throws KeeperException, InterruptedException {
        zk.create(ZNODE_PATH_CHILDREN, "anna2020".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    @Test
    public void testGet() throws KeeperException, InterruptedException {
        byte[] data1 = zk.getData(ZNODE_PATH, false, null);
        byte[] data2 = zk.getData(ZNODE_PATH_PARENT, false, null);
        byte[] data3 = zk.getData(ZNODE_PATH_CHILDREN, false, null);
        LOGGER.info("{}的資訊:{}", ZNODE_PATH, new String(data1) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_PARENT, new String(data2) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_CHILDREN, new String(data3) );
    }

    /**
     *  刪除
     * @throws KeeperException
     * @throws InterruptedException
     */
    @Test
    public void testDelete() throws KeeperException, InterruptedException {
        // 指定要刪除的版本,-1表示刪除所有版本
        zk.delete(ZNODE_PATH, -1);
    }

    /**
     *  刪除含有子節點
     * @throws KeeperException
     * @throws InterruptedException
     */
    @Test
    public void testDeleteHasChildrenZnode() throws KeeperException, InterruptedException {
        // 指定要刪除的版本,-1表示刪除所有版本
        zk.delete(ZNODE_PATH_PARENT, -1);
    }

    @Test
    public void testSet() throws KeeperException, InterruptedException {
        Stat stat = zk.setData(ZNODE_PATH, "yuanrengu".getBytes(), -1);
        LOGGER.info(stat.toString());
    }

}

上面有用到@Before,簡單說明下:

  • @BeforeClass – 表示在類中的任意public static void方法執行之前執行
  • @AfterClass – 表示在類中的任意public static void方法執行之後執行
  • @Before – 表示在任意使用@Test註解標註的public void方法執行之前執行
  • @After – 表示在任意使用@Test註解標註的public void方法執行之後執行
  • @Test – 使用該註解標註的public void方法會表示為一個測試方法

如果將SESSION_TIME_OUT設定的時間太短,會報API客戶端異常:org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /zk_demo 。完整的報錯資訊如下:

09:33:52.139 [main-SendThread(106.12.111.172:2181)] DEBUG org.apache.zookeeper.ClientCnxnSocketNIO - Ignoring exception during shutdown input
java.net.SocketException: Socket is not connected
    at sun.nio.ch.Net.translateToSocketException(Net.java:123)
    at sun.nio.ch.Net.translateException(Net.java:157)
    at sun.nio.ch.Net.translateException(Net.java:163)
    at sun.nio.ch.SocketAdaptor.shutdownInput(SocketAdaptor.java:401)
    at org.apache.zookeeper.ClientCnxnSocketNIO.cleanup(ClientCnxnSocketNIO.java:198)
    at org.apache.zookeeper.ClientCnxn$SendThread.cleanup(ClientCnxn.java:1338)
    at org.apache.zookeeper.ClientCnxn$SendThread.cleanAndNotifyState(ClientCnxn.java:1276)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1254)
Caused by: java.nio.channels.NotYetConnectedException: null
    at sun.nio.ch.SocketChannelImpl.shutdownInput(SocketChannelImpl.java:782)
    at sun.nio.ch.SocketAdaptor.shutdownInput(SocketAdaptor.java:399)
    ... 4 common frames omitted
09:33:52.140 [main-SendThread(106.12.111.172:2181)] DEBUG org.apache.zookeeper.ClientCnxnSocketNIO - Ignoring exception during shutdown output
java.net.SocketException: Socket is not connected
    at sun.nio.ch.Net.translateToSocketException(Net.java:123)
    at sun.nio.ch.Net.translateException(Net.java:157)
    at sun.nio.ch.Net.translateException(Net.java:163)
    at sun.nio.ch.SocketAdaptor.shutdownOutput(SocketAdaptor.java:409)
    at org.apache.zookeeper.ClientCnxnSocketNIO.cleanup(ClientCnxnSocketNIO.java:205)
    at org.apache.zookeeper.ClientCnxn$SendThread.cleanup(ClientCnxn.java:1338)
    at org.apache.zookeeper.ClientCnxn$SendThread.cleanAndNotifyState(ClientCnxn.java:1276)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1254)
Caused by: java.nio.channels.NotYetConnectedException: null
    at sun.nio.ch.SocketChannelImpl.shutdownOutput(SocketChannelImpl.java:799)
    at sun.nio.ch.SocketAdaptor.shutdownOutput(SocketAdaptor.java:407)
    ... 4 common frames omitted

org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /zk_demo

    at org.apache.zookeeper.KeeperException.create(KeeperException.java:102)
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:54)
    at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:2131)
    at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:2160)
    at com.yuanrengu.demo.ZooKeeperDemo.testGet(ZooKeeperDemo.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Disconnected from the target VM, address: '127.0.0.1:60454', transport: 'socket'

Process finished with exit code -1

起初以為是ZooKeeper服務部署有問題或服務沒啟動,經檢查確認無誤後,debug除錯發現,是SESSION_TIME_OUT = 2000;設定的值太小,改為10000後,不再報錯。

SESSION_TIME_OUT 是會話超時時間,也就是當一個zookeeper超過該時間沒有心跳,則認為該節點故障。所以,如果此值小於zookeeper的建立時間,則當zookeeper還未來得及建立連線,會話時間已到,因此丟擲異常認為該節點故障。

3.1 新增

public String create(String path,
                     byte[] data,
                     List<ACL> acl,
                     CreateMode createMode)
              throws KeeperException,
                     InterruptedException
                     
Create a node with the given path. The node data will be the given data, and node acl will be the given acl.
The flags argument specifies whether the created node will be ephemeral or not.

An ephemeral node will be removed by the ZooKeeper automatically when the session associated with the creation of the node expires.

The flags argument can also specify to create a sequential node. The actual path name of a sequential node will be the given path plus a suffix "i" where i is the current sequential number of the node. The sequence number is always fixed length of 10 digits, 0 padded. Once such a node is created, the sequential number will be incremented by one.

If a node with the same actual path already exists in the ZooKeeper, a KeeperException with error code KeeperException.NodeExists will be thrown. Note that since a different actual path is used for each invocation of creating sequential node with the same path argument, the call will never throw "file exists" KeeperException.

If the parent node does not exist in the ZooKeeper, a KeeperException with error code KeeperException.NoNode will be thrown.

An ephemeral node cannot have children. If the parent node of the given path is ephemeral, a KeeperException with error code KeeperException.NoChildrenForEphemerals will be thrown.

This operation, if successful, will trigger all the watches left on the node of the given path by exists and getData API calls, and the watches left on the parent node by getChildren API calls.

If a node is created successfully, the ZooKeeper server will trigger the watches on the path left by exists calls, and the watches on the parent of the node by getChildren calls.

The maximum allowable size of the data array is 1 MB (1,048,576 bytes). Arrays larger than this will cause a KeeperExecption to be thrown.

Parameters:
path - the path for the node
data - the initial data for the node
acl - the acl for the node
createMode - specifying whether the node to be created is ephemeral and/or sequential
Returns:
the actual path of the created node
Throws:
KeeperException - if the server returns a non-zero error code
KeeperException.InvalidACLException - if the ACL is invalid, null, or empty
InterruptedException - if the transaction is interrupted
IllegalArgumentException - if an invalid path is specified

Talk is cheap. Show me the code.這裡我們不瞎BB,直接上官方文件。官方文件是不是很容易看懂,而且解釋的非常清楚(而且稍顯囉嗦的感覺)?

這裡簡單列下文件中的幾個關鍵點:

  1. 按指定路徑和節點形式建立,可指定節點為持久節點、臨時節點等。
    這裡要說下CreateMode,大家可能都說ZooKeeper只有4種形式的節點(持久、臨時、持久順序、臨時順序),看文件的話,其實是有7種形式的。
public enum CreateMode {
   PERSISTENT(0, false, false, false, false),
   PERSISTENT_SEQUENTIAL(2, false, true, false, false),
   EPHEMERAL(1, true, false, false, false),
   EPHEMERAL_SEQUENTIAL(3, true, true, false, false),
   CONTAINER(4, false, false, true, false),
   PERSISTENT_WITH_TTL(5, false, false, false, true),
   PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
}
  • PERSISTENT:持久節點(也有叫永久節點的),不會隨著會話的結束而自動刪除。
  • PERSISTENT_SEQUENTIAL:帶單調遞增序號的持久節點,不會隨著會話的結束而自動刪除。
  • EPHEMERAL:臨時節點,會隨著會話的結束而自動刪除。
  • EPHEMERAL_SEQUENTIAL:帶單調遞增序號的臨時節點,會隨著會話的結束而自動刪除。
  • CONTAINER:容器節點,用於Leader、Lock等特殊用途,當容器節點不存在任何子節點時,容器將成為伺服器在將來某個時候刪除的候選節點。
  • PERSISTENT_WITH_TTL:帶TTL(time-to-live,存活時間)的持久節點,節點在TTL時間之內沒有得到更新並且沒有子節點,就會被自動刪除。
  • PERSISTENT_SEQUENTIAL_WITH_TTL:帶TTL(time-to-live,存活時間)和單調遞增序號的持久節點,節點在TTL時間之內沒有得到更新並且沒有子節點,就會被自動刪除。
  1. 如果指令路徑和版本的節點已經存在,則會丟擲一個KeeperException異常。
  2. 臨時節點不能有子節點。如果給臨時節點建立子節點會拋KeeperException異常。
  3. 臨時節點的生命週期與客戶端會話繫結。一旦客戶端會話失效(客戶端與 Zookeeper連線斷開不一定會話失效),那麼這個客戶端建立的所有臨時節點都會被移除。
  4. byte[] data允許的最大資料量為1MB(1,048,576 bytes)。如果超過,會拋KeeperExecption。

執行建立節點的程式碼:

    @Test
    public void testCreate() throws KeeperException, InterruptedException {
        zk.create(ZNODE_PATH, "anna2019".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

可以通過日誌資訊得到節點建立成功:

DEBUG org.apache.zookeeper.ClientCnxn - Reading reply sessionid:0x101402626bb000b, packet:: clientPath:null serverPath:null finished:false header:: 1,1  replyHeader:: 1,12884901937,0  request:: '/zk_demo,#616e6e6132303139,v{s{31,s{'world,'anyone}}},0  response:: '/zk_demo

在服務端檢視,/zk_demo節點建立成功:

[zk: 127.0.0.1:2181(CONNECTED) 21] ls /
[zookeeper, zk_demo]
[zk: 127.0.0.1:2181(CONNECTED) 22] stat /zk_demo
cZxid = 0x300000031
ctime = Tue Dec 17 12:52:50 CST 2019
mZxid = 0x300000031
mtime = Tue Dec 17 12:52:50 CST 2019
pZxid = 0x300000031
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0

3.2 獲取

public byte[] getData(String path,
                      boolean watch,
                      Stat stat)
               throws KeeperException,
                      InterruptedException

Return the data and the stat of the node of the given path.
If the watch is true and the call is successful (no exception is thrown), a watch will be left on the node with the given path. The watch will be triggered by a successful operation that sets data on the node, or deletes the node.

A KeeperException with error code KeeperException.NoNode will be thrown if no node with the given path exists.

Parameters:
path - the given path
watch - whether need to watch this node
stat - the stat of the node
Returns:
the data of the node
Throws:
KeeperException - If the server signals an error with a non-zero error code
InterruptedException - If the server transaction is interrupted.

指定路徑的節點不存在時就拋KeeperException.NoNode異常。

執行:

    @Test
    public void testGet() throws KeeperException, InterruptedException {
        byte[] data1 = zk.getData(ZNODE_PATH, false, null);
        byte[] data2 = zk.getData(ZNODE_PATH_PARENT, false, null);
        byte[] data3 = zk.getData(ZNODE_PATH_CHILDREN, false, null);
        LOGGER.info("{}的資訊:{}", ZNODE_PATH, new String(data1) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_PARENT, new String(data2) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_CHILDREN, new String(data3) );
    }

結果:

13:51:00.288 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /zk_demo的資訊:anna2019
13:51:00.288 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /app1的資訊:anna2019
13:51:00.289 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /app1/app1_1的資訊:anna2020

3.3 更新

public Stat setData(String path,
                    byte[] data,
                    int version)
             throws KeeperException,
                    InterruptedException
                    
Set the data for the node of the given path if such a node exists and the given version matches the version of the node (if the given version is -1, it matches any node's versions). Return the stat of the node.
This operation, if successful, will trigger all the watches on the node of the given path left by getData calls.

A KeeperException with error code KeeperException.NoNode will be thrown if no node with the given path exists.

A KeeperException with error code KeeperException.BadVersion will be thrown if the given version does not match the node's version.

The maximum allowable size of the data array is 1 MB (1,048,576 bytes). Arrays larger than this will cause a KeeperException to be thrown.

Parameters:
path - the path of the node
data - the data to set
version - the expected matching version
Returns:
the state of the node
Throws:
InterruptedException - If the server transaction is interrupted.
KeeperException - If the server signals an error with a non-zero error code.
IllegalArgumentException - if an invalid path is specified

主要注意以下幾點:

  1. 版本為-1時,即代表適配指定路徑節點的所有版本。
  2. 如果指定路徑的節點不存在會拋KeeperException.NoNode異常,該節點沒有傳入的版本,會拋KeeperException.BadVersion異常。
  3. byte[] data允許的最大資料量為1MB(1,048,576 bytes)。如果超過,會拋KeeperExecption。

執行:

    @Test
    public void testGet() throws KeeperException, InterruptedException {
        byte[] data1 = zk.getData(ZNODE_PATH, false, null);
        byte[] data2 = zk.getData(ZNODE_PATH_PARENT, false, null);
        byte[] data3 = zk.getData(ZNODE_PATH_CHILDREN, false, null);
        LOGGER.info("{}的資訊:{}", ZNODE_PATH, new String(data1) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_PARENT, new String(data2) );
        LOGGER.info("{}的資訊:{}", ZNODE_PATH_CHILDREN, new String(data3) );
    }

結果如下:

13:51:00.288 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /zk_demo的資訊:anna2019
13:51:00.288 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /app1的資訊:anna2019
13:51:00.289 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - /app1/app1_1的資訊:anna2020

3.4 刪除

public void delete(String path,
                   int version)
            throws InterruptedException,
                   KeeperException
                   
Delete the node with the given path. The call will succeed if such a node exists, and the given version matches the node's version (if the given version is -1, it matches any node's versions).
A KeeperException with error code KeeperException.NoNode will be thrown if the nodes does not exist.

A KeeperException with error code KeeperException.BadVersion will be thrown if the given version does not match the node's version.

A KeeperException with error code KeeperException.NotEmpty will be thrown if the node has children.

This operation, if successful, will trigger all the watches on the node of the given path left by exists API calls, and the watches on the parent node left by getChildren API calls.

Parameters:
path - the path of the node to be deleted.
version - the expected node version.
Throws:
InterruptedException - IF the server transaction is interrupted
KeeperException - If the server signals an error with a non-zero return code.
IllegalArgumentException - if an invalid path is specified

節點可能含有子節點,刪除節點的操作有幾點需要特別注意:

  1. 版本為-1時,即代表適配指定路徑節點的所有版本。
  2. 如果指定路徑的節點不存在會拋KeeperException.NoNode異常,該節點沒有傳入的版本,會拋KeeperException.BadVersion異常。
  3. 如果節點含有子節點,刪除父節點(parent node)時會拋KeeperException.NotEmpty異常。

/app1有子節點,我們做下刪除操作:

    /**
     *  刪除含有子節點的父節點
     * @throws KeeperException
     * @throws InterruptedException
     */
    @Test
    public void testDeleteHasChildrenZnode() throws KeeperException, InterruptedException {
        // 指定要刪除的版本,-1表示刪除所有版本
        zk.delete(ZNODE_PATH_PARENT, -1);
    }

可以看到日誌:

org.apache.zookeeper.KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /app1

    at org.apache.zookeeper.KeeperException.create(KeeperException.java:132)
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:54)
    at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:1793)
    at com.yuanrengu.demo.ZooKeeperDemo.testDeleteHasChildrenZnode(ZooKeeperDemo.java:89)

4 總結

上面我們實現了節點的增、刪、改、查的測試,後面的篇章會有更多好玩的用法,如實現分散式鎖、配置中心等。

基於上面的分析,總結幾個注意的點:

  1. 節點有7種形式
  • PERSISTENT:持久節點(也有叫永久節點的),不會隨著會話的結束而自動刪除。
  • PERSISTENT_SEQUENTIAL:帶單調遞增序號的持久節點,不會隨著會話的結束而自動刪除。
  • EPHEMERAL:臨時節點,會隨著會話的結束而自動刪除。
  • EPHEMERAL_SEQUENTIAL:帶單調遞增序號的臨時節點,會隨著會話的結束而自動刪除。
  • CONTAINER:容器節點,用於Leader、Lock等特殊用途,當容器節點不存在任何子節點時,容器將成為伺服器在將來某個時候刪除的候選節點。
  • PERSISTENT_WITH_TTL:帶TTL(time-to-live,存活時間)的持久節點,節點在TTL時間之內沒有得到更新並且沒有子節點,就會被自動刪除。
  • PERSISTENT_SEQUENTIAL_WITH_TTL:帶TTL(time-to-live,存活時間)和單調遞增序號的持久節點,節點在TTL時間之內沒有得到更新並且沒有子節點,就會被自動刪除。
  1. 臨時節點不能有子節點。如果給臨時節點建立子節點會拋KeeperException異常。
  2. 臨時節點的生命週期與客戶端會話繫結。一旦客戶端會話失效(客戶端與 Zookeeper連線斷開不一定會話失效),那麼這個客戶端建立的所有臨時節點都會被移除。
  3. byte[] data允許的最大資料量為1MB(1,048,576 bytes)