舒適提示
:在這裏我再次提個小要求,但願你們能習慣看
官方文檔,文檔雖然是英文但用詞都比較簡單,基本都能看懂文檔表達的意思。
授之以魚不如授之以漁的道理相信你們都明白,也但願經過猿人谷的這個ZooKeeper系列,讓你們入門、到熟悉,觸類旁通後能精通ZooKeeper。
在前一篇咱們介紹了ZooKeeper單機版、僞集羣和集羣環境搭建,經過命令行的方式作了節點的建立、刪除、更新、獲取節點信息的測試。Zookeeper 的目的是爲客戶端構建複雜的協調功能提供簡單、高效的核心 API,這一篇咱們用Java經過ZooKeeper提供的API接口來實現這些增刪改查的功能。html
org.apache.zookeeper.Zookeeper
是ZooKeeper客戶端的主類,在官方文檔中已明確說明(This is the main class of ZooKeeper client library.)。java
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),客戶端與服務端會經過心跳來保持會話有效。node
org.apache.zookeeper.Zookeeper
裏的方法很是多,就不一一列舉了,只列幾個增刪改查的。git
Method | Description |
---|---|
create(String path, byte[] data, List<ACL> acl, CreateMode createMode) | Create a node with the given path. (建立指定路徑的節點) |
create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsyncCallback.Create2Callback cb, Object ctx) | The asynchronous version of create.(異步形式建立) |
create(String path, byte[] data, List<ACL> 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,即給全部版本設置值) |
這裏新建一個Spring Boot的項目來進行測試,新建Spring Boot項目的過程很簡單,也不是這裏的重點,就不作介紹了。shell
項目裏會須要額外引入兩個包來進行測試:apache
<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>
完整測試代碼以下:segmentfault
/** * 簡單測試示例 * @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
,簡單說明下:api
若是將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還將來得及建立鏈接,會話時間已到,所以拋出異常認爲該節點故障。
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,直接上官方文檔。官方文檔是否是很容易看懂,並且解釋的很是清楚(並且稍顯囉嗦的感受)?
這裏簡單列下文檔中的幾個關鍵點:
這裏要說下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); }
CONTAINER
:容器節點,用於Leader、Lock等特殊用途,當容器節點不存在任何子節點時,容器將成爲服務器在未來某個時候刪除的候選節點。PERSISTENT_WITH_TTL
:帶TTL(time-to-live,存活時間)的持久節點,節點在TTL時間以內沒有獲得更新而且沒有子節點,就會被自動刪除。PERSISTENT_SEQUENTIAL_WITH_TTL
:帶TTL(time-to-live,存活時間)和單調遞增序號的持久節點,節點在TTL時間以內沒有獲得更新而且沒有子節點,就會被自動刪除。臨時節點不能有子節點
。若是給臨時節點建立子節點會拋KeeperException異常。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
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
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
主要注意如下幾點:
byte[] data容許的最大數據量爲1MB(1,048,576 bytes)
。若是超過,會拋KeeperExecption。運行:
@Test public void testSet() throws KeeperException, InterruptedException { Stat stat = zk.setData(ZNODE_PATH, "yuanrengu".getBytes(), -1); byte[] data = zk.getData(ZNODE_PATH, false, null); LOGGER.info(new String(data)); }
能夠看到數據已經更新:
15:46:16.472 [main] INFO com.yuanrengu.demo.ZooKeeperDemo - yuanrengu
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
節點可能含有子節點,刪除節點的操做有幾點須要特別注意:
若是節點含有子節點,刪除父節點(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)
上面咱們實現了節點的增、刪、改、查的測試,後面的篇章會有更多好玩的用法,如實現分佈式鎖、配置中心等。
基於上面的分析,總結幾個注意的點:
7種形式
:CONTAINER
:容器節點,用於Leader、Lock等特殊用途,當容器節點不存在任何子節點時,容器將成爲服務器在未來某個時候刪除的候選節點。PERSISTENT_WITH_TTL
:帶TTL(time-to-live,存活時間)的持久節點,節點在TTL時間以內沒有獲得更新而且沒有子節點,就會被自動刪除。PERSISTENT_SEQUENTIAL_WITH_TTL
:帶TTL(time-to-live,存活時間)和單調遞增序號的持久節點,節點在TTL時間以內沒有獲得更新而且沒有子節點,就會被自動刪除。byte[] data容許的最大數據量爲1MB(1,048,576 bytes)
。