大纲
前言
学习资源
ZooKeeper 客户端的命令
客户端命令行语法
1
| $ ./zkCli.sh -server 127.0.0.1:2181
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 0] help
|
ZNode 节点的类型
四种常见节点类型
ZooKeeper 的节点(ZNode)是其数据存储的基本单元,每个节点有自己的路径、数据和元信息。根据用途和特性,ZooKeeper 的节点分为以下四种类型:
提示
- 创建 ZNode 时设置顺序标识,ZNode 的名称后面会附加一个顺序号,这顺序号是一个单调递增的计数器,由父节点维护。
- 特别注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序。
三种新的节点类型
在 ZooKeeper 3.5.5
或更高版本中,新增了以下三种节点类型:
ZNode 节点的创建
持久节点的创建
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend" Created /backend
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [zk: 127.0.0.1:2181(CONNECTED) 1] get /backend backend
[zk: 127.0.0.1:2181(CONNECTED) 2] get -s /backend backend cZxid = 0x300000020 ctime = Sun Dec 29 17:11:26 CST 2021 mZxid = 0x300000020 mtime = Sun Dec 29 17:11:26 CST 2021 pZxid = 0x300000021 cversion = 1 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 7 numChildren = 1
|
临时节点的创建
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create -e /backend "backend" Created /backend
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 1] ls / [backend, zookeeper]
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 2] quit
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 3] ls / [zookeeper]
|
持久顺序节点的创建
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend" Created /backend
|
1 2 3 4 5 6 7 8
| [zk: 127.0.0.1:2181(CONNECTED) 1] create -s /backend/python "python" Created /backend/python0000000000
[zk: 127.0.0.1:2181(CONNECTED) 2] create -s /backend/python "python" Created /backend/python0000000001
[zk: 127.0.0.1:2181(CONNECTED) 3] create -s /backend/python "python" Created /backend/python0000000002
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 4] ls /backend [python0000000000, python0000000001, python0000000002]
|
临时顺序节点的创建
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend" Created /backend
|
1 2 3 4 5 6 7 8
| [zk: 127.0.0.1:2181(CONNECTED) 1] create -e -s /backend/python "python" Created /backend/python0000000000
[zk: 127.0.0.1:2181(CONNECTED) 2] create -e -s /backend/python "python" Created /backend/python0000000001
[zk: 127.0.0.1:2181(CONNECTED) 3] create -e -s /backend/python "python" Created /backend/python0000000002
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 4] ls /backend [python0000000000, python0000000001, python0000000002]
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 5] quit
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 6] ls /backend []
|
ZNode 节点的删除
1
| [zk: 127.0.0.1:2181(CONNECTED) 0] delete /backend
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 1] delete /backend/python
|
修改 ZNode 节点的值
1
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend"
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| [zk: 127.0.0.1:2181(CONNECTED) 1] get -s /backend backend cZxid = 0x300000055 ctime = Sun Dec 29 17:59:20 CST 2021 mZxid = 0x300000055 mtime = Sun Dec 29 17:59:20 CST 2021 pZxid = 0x300000055 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 7 numChildren = 0
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 2] set /backend "backend_update"
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| [zk: 127.0.0.1:2181(CONNECTED) 3] get -s /backend backend_update cZxid = 0x300000055 ctime = Sun Dec 29 17:59:20 CST 2021 mZxid = 0x300000056 mtime = Sun Dec 29 18:01:13 CST 2021 pZxid = 0x300000055 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0
|
获取 ZNode 节点的值
1
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend"
|
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 1] get /backend backend
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| [zk: 127.0.0.1:2181(CONNECTED) 2] get -s /backend backend cZxid = 0x300000055 ctime = Sun Dec 29 17:59:20 CST 2021 mZxid = 0x300000055 mtime = Sun Dec 29 17:59:20 CST 2021 pZxid = 0x300000055 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 7 numChildren = 0
|
获取 ZNode 节点的信息
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] ls / [zookeeper]
|
1 2 3 4 5 6 7 8 9 10 11 12
| [zk: 127.0.0.1:2181(CONNECTED) 1] ls -s / [zookeeper]cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x30000001d cversion = 3 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 1
|
查看 ZNode 节点的状态
1
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend"
|
1 2 3 4 5 6 7 8 9 10 11 12
| [zk: 127.0.0.1:2181(CONNECTED) 0] stat /backend cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x30000005d cversion = 32 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 0 numChildren = 2
|
字段 | 含义 |
---|
czxid | 节点创建时的事务 ID(ZXID 是事务的唯一标识) |
ctime | 节点创建的时间戳(以毫秒为单位,从 1970 年开始) |
mzxid | 节点最后一次更新时的事务 ID(ZXID) |
mtime | 节点最后一次更新的时间戳(以毫秒为单位,从 1970 年开始) |
pzxid | 最后一次修改子节点列表的事务 ID(ZXID) |
cversion | 节点子节点的版本号(即子节点修改的次数) |
dataVersion | 节点数据的版本号 |
aclVersion | 节点访问控制列表(ACL)的版本号 |
ephemeralOwner | 如果是临时节点,表示创建该节点的会话 ID(Session ID);如果不是临时节点,则是 0 |
dataLength | 节点存储的数据大小(以字节为单位) |
numChildren | 节点的直接子节点数量 |
监听 ZNode 节点的变化
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加 / 删除)时,ZooKeeper 服务器会通知客户端。监听机制可以保证 ZooKeeper 保存的任何数据的任何改变,都能快速地反应给监听了该节点的应用程序。
监听器的原理
- 监听器的工作原理
- (1) 首先要有一个
main
线程。 - (2) 在
main
线程中创建 Zookeeper 客户端,这时就会创建两个线程,一个负责网络连接通信(Connect),一个负责监听(Listener)。 - (3) 通过 Connect 线程将注册的监听事件发送给 Zookeeper 服务器。
- (4) 在 Zookeeper 服务器中,将客户端注册的监听事件添加到监听器列表。
- (5) 当 Zookeeper 服务器监听到有数据或子节点发生变化,就会将这个消息发送给客户端的 Listener 线程。
- (6) 在客户端的 Listener 线程内,调用
process()
方法进行消息处理。
监听命令的语法
监听节点的值变化
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend" Created /backend
|
- 在集群节点一上注册监听
/backend
节点的数据变化
1
| [zk: 127.0.0.1:2181(CONNECTED) 1] get -w /backend
|
1
| [zk: 127.0.0.1:2181(CONNECTED) 2] set /backend "backend_changed"
|
1 2 3
| WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/backend
|
特别注意
ZooKeeper 的监听器注册一次,只能监听一次。如果想再次监听,则需要再次注册。
监听节点的子节点变化
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 0] create /backend "backend" Created /backend
|
- 在集群节点一上注册监听
/backend
节点的子节点变化
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 1] ls -w /backend []
|
- 在集群节点二上为
/backend
节点创建子节点
1 2
| [zk: 127.0.0.1:2181(CONNECTED) 2] create /backend/java Created /backend/java
|
1 2 3
| WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/backend
|
特别注意
ZooKeeper 的监听器注册一次,只能监听一次。如果想再次监听,则需要再次注册。
ZooKeeper 客户端的 API
本节将演示如何使用 ZooKeeper 原生客户端(并不是 Curator 客户端)的 API,比如创建节点、监听子节点变化、判断节点是否存在,完整的案例代码可以直接从 GitHub 下载对应章节 zookeeper-lesson-01
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.3</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> </dependency>
|
1 2 3 4 5 6 7 8
| log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public class ZKClientTest {
private static final String ADDRESS = "192.168.2.235:2181,192.168.2.235:2182,192.168.2.235:2183";
private static final int SESSION_TIMEOUT = 2000;
private static ZooKeeper client;
@BeforeAll public static void init() throws Exception { client = new ZooKeeper(ADDRESS, SESSION_TIMEOUT, new Watcher() {
@Override public void process(WatchedEvent event) { System.out.println("===> Watcher: " + event.getType() + " -- " + event.getPath());
try { List<String> children = client.getChildren("/", true); for (String path : children) { System.out.println("===> " + path); } } catch (Exception e) { e.printStackTrace(); } }
}); }
@Test public void createNode() throws Exception { String path = client.create("/java", "Hello World".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println("===> " + path); }
@Test public void getChildren() throws Exception { List<String> children = client.getChildren("/", true); for (String path : children) { System.out.println("===> " + path); } System.in.read(); }
@Test public void exist() throws Exception { Stat stat = client.exists("/java", false); System.out.println(stat == null ? "not exist" : "exist"); }
}
|
特别注意
- ZooKeeper 的监听器是一次性的,当客户端接收到监听事件后,需要让客户端重新设置监听器。
- 上述案例代码中,ZooKeeper 客户端的版本是
3.6.3
,而 ZooKeeper 服务器的版本是 3.5.7
。
ZooKeeper 客户端的写入原理
当 ZooKeeper 客户端将写请求直接发送给 Leader 节点时,ZooKeeper 服务器的处理流程如下图所示
- (1) Leader 节点让 Follwer 节点写入数据。
- (2) 只要满足一半以上节点写入成功(半数写入),Leader 节点就会立刻返回 ACK(确认消息)给客户端。
- (3) 最后 Leader 节点继续让其他 Follower 节点写入数据。
当 ZooKeeper 客户端将写请求发送给 Follower 节点,ZooKeeper 服务器的处理流程如下图所示
- (1) Follower 节点会将写请求交给 Leader 节点。
- (2) Leader 节点让 Follwer 节点写入数据。
- (3) 只要满足一半以上节点写入成功(半数写入),Leader 节点就会立刻返回 ACK(确认消息)给 Follower 节点,然后 Follower 节点再返回 ACK(确认消息)给客户端。
- (4) 最后 Leader 节点继续让其他 Follower 节点写入数据。