文章目录
- 03 ZooKeeper底层原理剖析与命令实战
- 1. Znode数据结构
- 1.1 目录结构
- 1.2 节点类型
- 2. ZK客户端命令行操作
- 3. ZooKeeper会话
- 4. 事件监听机制原理剖析
- 5.广播模式剖析
- 6. ZooKeeper集群的特点
- 7. ZK常见的应用场景
- gitee地址:zookeeper分布式协调服务框架
传送门:
- ZooKeeper初探:/weixin_46649052/article/details/122004447
- ZooKeeper分布式集群安装:/weixin_46649052/article/details/122017216
- ZooKeeper底层原理剖析与命令实战:/weixin_46649052/article/details/122042265
- ZooKeeperAPI实战:/weixin_46649052/article/details/122059148
- ZooKeeper分布式RMI协调实战:/weixin_46649052/article/details/122085619
03 ZooKeeper底层原理剖析与命令实战
1. Znode数据结构
ZooKeeper对外提供服务时有一个统一视图,统一视图维护着相同的树形结构。树形结构中每一个节点称为Znode节点。每一个Znode节点上都可以存储数据。同时,每个节点还可以创建子节点,特殊情况:临时节点不能创建子节点。很多Znode节点共同形成Znode树。ZooKeeper的Znode树是维护在内存中的,读写效率较高,提供快速的查询。另外,每一个Znode节点都是一个路径,我们可以通过路径名定位到这个节点来对它进行查询、修改、删除。
- ZK 有一个最开始的节点 /
- ZK 的节点叫做 znode 节点
- 每个 znode 节点都可存储数据
- 每个 znode 节点(临时节点除外)都可创建自己的子节点
- 多个 znode 节点共同形成了 znode 树
- Znode 树的维系实在内存中,目的是供用户快速的查询
- 每个 znode 节点都是一个路径(通过路径来定位这个节点)
- 每个路径名都是唯一的
1.1 目录结构
- 目录结构是有层次的,便于管理逻辑关系
- 每一个znode节点可以包含最大1MB的数据信息
- znode节点会记录zxid(事务ID)、什么时候被修改、数据的长度、数据的内容等元数据信息
1.2 节点类型
znode 有两种类型,临时的(ephemeral)和持久的(persistent),znode 的类型在创建时确定并且之后不能再修改。
- 临时 znode:客户端会话结束时,ZooKeeper 将该临时 znode 删除,临时 znode 没有子节点
- 持久 znode:不依赖于客户端会话,只有当客户端明确要删除该持久 znode 时才会被删除
znode 支持序列 SEQUENTIAL(自增),有序 znode 节点被分配唯一单调递增的整数。比如:客户端创建有序 znode,路径为/task/task-,则 ZooKeeper 为其分配序号 1,并追加到 znode 节点: /task/task-000000001。有序 znode 节点唯一,同时也可根据该序号查看znode 创建顺序。
znode 有四种形式的目录节点:
- PERSISTENT:普通持久
- EPHEMERAL:普通临时
- PERSISTENT_SEQUENTIAL:顺序持久
- EPHEMERAL_SEQUENTIAL:顺序临时
2. ZK客户端命令行操作
要想执行以下指令,需要在node2、node3、node4上先启动 zk 服务器端
node2:
[root@node2 ~]# cd /opt/zookeeper-3.4.6/bin/
[root@node2 bin]# start
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Starting zookeeper ... STARTED
[root@node2 bin]# status
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Mode: follower
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
node3:
[root@node3 ~]# cd /opt/zookeeper-3.4.6/bin/
[root@node3 bin]# start
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Starting zookeeper ... STARTED
[root@node3 bin]# status
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Mode: leader
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
node4:
[root@node4 ~]# cd /opt/zookeeper-3.4.6/bin/
[root@node4 bin]# start
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Starting zookeeper ... STARTED
[root@node4 bin]# status
JMX enabled by default
Using config: /opt/zookeeper-3.4.6/bin/../conf/
Mode: follower
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
然后,在任意一台机器上,如:node2,启动 zk 客户端
[root@node2 bin]#
Connecting to localhost:2181
2021-12-19 20:23:39,550 [myid:] - INFO [main:Environment@100] - Client environment:=3.4.6-1569965, built on 02/20/2014 09:09 GMT
2021-12-19 20:23:39,552 [myid:] - INFO [main:Environment@100] - Client environment:=node2
2021-12-19 20:23:39,552 [myid:] - INFO [main:Environment@100] - Client environment:=1.8.0_221
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=Oracle Corporation
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/usr/java/jdk1.8.0_221-amd64/jre
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/opt/zookeeper-3.4.6/bin/../build/classes:/opt/zookeeper-3.4.6/bin/../build/lib/*.jar:/opt/zookeeper-3.4.6/bin/../lib/slf4j-log4j12-1.6.:/opt/zookeeper-3.4.6/bin/../lib/slf4j-api-1.6.:/opt/zookeeper-3.4.6/bin/../lib/netty-3.7.:/opt/zookeeper-3.4.6/bin/../lib/log4j-1.2.:/opt/zookeeper-3.4.6/bin/../lib/jline-0.9.:/opt/zookeeper-3.4.6/bin/../zookeeper-3.4.:/opt/zookeeper-3.4.6/bin/../src/java/lib/*.jar:/opt/zookeeper-3.4.6/bin/../conf:
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/tmp
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=<NA>
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=Linux
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=amd64
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=2.6.32-431.el6.x86_64
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=root
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/root
2021-12-19 20:23:39,554 [myid:] - INFO [main:Environment@100] - Client environment:=/opt/zookeeper-3.4.6/bin
2021-12-19 20:23:39,556 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=localhost:2181 sessionTimeout=30000 watcher=$MyWatcher@68de145
Welcome to ZooKeeper!
2021-12-19 20:23:39,580 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@975] - Opening socket connection to server localhost/0:0:0:0:0:0:0:1:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2021-12-19 20:23:39,649 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@852] - Socket connection established to localhost/0:0:0:0:0:0:0:1:2181, initiating session
2021-12-19 20:23:39,671 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server localhost/0:0:0:0:0:0:0:1:2181, sessionid = 0x17dd2a30da60000, negotiated timeout = 30000
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]
- 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
在命令行创建、修改、删除、查询节点有如下命令:
-
ls查看命令
[zk: localhost:2181(CONNECTED) 0] ls / [zookeeper]
- 1
- 2
查看节点下面都有哪些节点,默认提供[zookeeper]节点
-
create 创建节点指令,注意,在创建节点时,要分配初始数据
[zk: localhost:2181(CONNECTED) 1] create /zk01 hello Created /zk01 [zk: localhost:2181(CONNECTED) 2] ls / [zk01, zookeeper]
- 1
- 2
- 3
- 4
内容可以给成空,但不能不给
[zk: localhost:2181(CONNECTED) 3] create /zk02 '' Created /zk02 [zk: localhost:2181(CONNECTED) 4] ls / [zk02, zk01, zookeeper] [zk: localhost:2181(CONNECTED) 5] create /zk03 "" Created /zk03 [zk: localhost:2181(CONNECTED) 6] ls / [zk02, zk01, zookeeper, zk03] [zk: localhost:2181(CONNECTED) 7] create /zk04 [zk: localhost:2181(CONNECTED) 8] ls / [zk02, zk01, zookeeper, zk03]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
-
get 查看节点数据指令,可以查看节点内容
[zk: localhost:2181(CONNECTED) 9] get /zk01 hello cZxid = 0x200000002 ctime = Sun Dec 19 20:27:35 CST 2021 mZxid = 0x200000002 mtime = Sun Dec 19 20:27:35 CST 2021 pZxid = 0x200000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- cZxid:事务ID(创建、修改、删除都会改变当前节点的事务ID)
- ctime:节点的时间戳
- mZxid:修改的事务ID
- mtime:修改的时间戳
- dataVersion:数据版本号(每当数据发生变化,版本号递增 1 )
- ephemeralOwner:临时节点的所有者,当前会话的session ID,与临时节点有关
- dataLength:数据大小
- numChildren:子节点个数
-
set 更新节点数据指令 ( 执行后 mtime 、 dataVersion 肯定会发生变化,dataLength可能会变化)
[zk: localhost:2181(CONNECTED) 10] set /zk01 helloworld cZxid = 0x200000002 ctime = Sun Dec 19 20:27:35 CST 2021 mZxid = 0x200000005 mtime = Sun Dec 19 20:38:58 CST 2021 pZxid = 0x200000002 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
-
delete 删除节点
[zk: localhost:2181(CONNECTED) 11] ls / [zk02, zk01, zookeeper, zk03] [zk: localhost:2181(CONNECTED) 12] delete /zk03 [zk: localhost:2181(CONNECTED) 13] ls / [zk02, zk01, zookeeper]
- 1
- 2
- 3
- 4
- 5
-
create 指令补充:
-
创建子节点
[zk: localhost:2181(CONNECTED) 15] create /zk02/node1 hello Created /zk02/node1 [zk: localhost:2181(CONNECTED) 16] ls / [zk02, zk01, zookeeper] [zk: localhost:2181(CONNECTED) 17] ls /zk02 [node1]
- 1
- 2
- 3
- 4
- 5
- 6
-
Zk 节点分四种类型:普通持久节点、普通临时节点、顺序持久节点、顺序临时节点
-
普通持久节点
[zk: localhost:2181(CONNECTED) 18] create /zk03 hello Created /zk03 [zk: localhost:2181(CONNECTED) 19] ls / [zk02, zk01, zookeeper, zk03]
- 1
- 2
- 3
- 4
-
普通临时节点:创建此临时节点的客户端失去和zk 连接后,此节点消失。zk 是通过临时节点监控哪个服务器挂掉的。
[zk: localhost:2181(CONNECTED) 20] create -e /zk02/node2 aabb Created /zk02/node2 [zk: localhost:2181(CONNECTED) 21] ls /zk02 [node2, node1] [zk: localhost:2181(CONNECTED) 22] get /zk02/node2 aabb cZxid = 0x200000009 ctime = Sun Dec 19 20:50:46 CST 2021 mZxid = 0x200000009 mtime = Sun Dec 19 20:50:46 CST 2021 pZxid = 0x200000009 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x17dd2a30da60000 # dataLength = 4 numChildren = 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
当会话断开,再重新登录后,临时节点消失!
WatchedEvent state:SyncConnected type:None path:null [zk: localhost:2181(CONNECTED) 0] ls /zk02 [node1]
- 1
- 2
- 3
-
顺序持久节点:会根据用户指定的节点路径,自动分配一个递增的顺序号。(顺序节点实现分布式锁的效果,服务器1抢到zk05分配zk050001, 服务器 2抢到 zk05 分配 zk050002)
[zk: localhost:2181(CONNECTED) 1] create -s /zk02/node3 helloworld Created /zk02/node30000000002 [zk: localhost:2181(CONNECTED) 2] ls /zk02 [node30000000002, node1] [zk: localhost:2181(CONNECTED) 3] get /zk02/node30000000002 helloworld cZxid = 0x20000000c ctime = Sun Dec 19 20:58:00 CST 2021 mZxid = 0x20000000c mtime = Sun Dec 19 20:58:00 CST 2021 pZxid = 0x20000000c cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
-
顺序临时节点
[zk: localhost:2181(CONNECTED) 4] create -s -e /zk02/node4 helloworld! Created /zk02/node40000000003 [zk: localhost:2181(CONNECTED) 5] ls /zk02 [node40000000003, node30000000002, node1] [zk: localhost:2181(CONNECTED) 6] get /zk02/node40000000003 helloworld! cZxid = 0x20000000d ctime = Sun Dec 19 21:01:41 CST 2021 mZxid = 0x20000000d mtime = Sun Dec 19 21:01:41 CST 2021 pZxid = 0x20000000d cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x17dd2a30da60001 dataLength = 11 numChildren = 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
一旦会话断开,这个临时节点就是会删除掉。
-
-
3. ZooKeeper会话
如图的ZooKeeper集群中有三台服务器,Sever2作为Leader
-
客户端通过TCP协议会与其中一台服务器建立连接,称为会话;
-
从建立到断开称为一次会话;
-
如果连接的Sever出现问题,在没有超过 Timeout 时间时,可以连接其他节点。 ZooKeeper 客户端透明地转移一个会话到不同的服务器。
-
当发生连接的转换之后,里面的特性与数据还是一样,因为这些服务对外提供的视图是统一的。
-
会话提供顺序保障,即同一个会话中的请求以 FIFO 的顺序执行。如果客户端有多个并发会话,FIFO 顺序在多个会话之间未必能够保持。
-
当一个会话因某种原因终止,在这个会话期间创建的临时节点将会消失。
-
Session 是由谁来创建的?
Leader:产生一个唯一的 session,放到消息队列,让所有 server 知道。同时,消息队列可以保证ZooKeeper集群的有序。
-
过半机制:由过半服务节点来决定session 创建成功或者失败
4. 事件监听机制原理剖析
如果没有事件监听机制,那么客户端轮询指定节点下的数据。通过网络轮询,代价很大。
基于通知(notification)的机制: 客户端向 ZooKeeper 注册需要接收通知的 znode, 通过对 znode 设置监视点(watch)来接收通知。监视点是一个单次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。
事件监听Watcher:Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应。
- 可以设置观察点的操作:exists,getChildren,getData
- 可以触发观察的操作:create,delete,setData
5.广播模式剖析
ZooKeeper 的核心是原子广播,这个机制保证了各个 server 之间的信息同步。实现这个机制的协议叫做 ZAB 协议。
ZAB 协议有两种模式:
- 恢复模式:当服务启动或者在领导者崩溃后,ZAB 就进入了恢复模式。当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 follower 以及 observer 具有相同的系统状态
- 选举过程耗时在 200ms 之内,一般情况下 ZooKeeper 恢复服务时间间隔不超过 200ms
- 广播模式:广播模式需要保证提议( proposal)被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来 保 证 。 所 有 的 提 议 (proposal) 都 在 被 提 出 的 时 候 加 上 了 zxid( 比 如 : 0x300000002)。 epoch 也称为纪元数字,如:3。实现中 zxid 是一个 64 位的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的epoch,低32 位是个递增计数。
广播模式的操作流程如下:
客户端给与它建立连接的Follower服务器发送request请求,然后Follower服务器立即将请求发送给Leader服务器,然后Leader服务器给所有的Follower服务器与Observer发送确认提议,每一个服务器再接收到消息后会给予一个响应,Leader服务器接收到一半以上机器数同意后会进行提交,然后将提交的事同步到所有服务器的消息队列里,然后让所有服务器都进行相同操作。
6. ZooKeeper集群的特点
ZooKeeper集群有以下六个特点:
- 最终一致性:为客户端展示同一个试图,这是ZooKeeper集群里面一个非常重要的功能
- 可靠性:如果消息被一台服务器接受,那么它将被所有服务器接受
- 实时性:ZooKeeper不能保证两个客户端同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口,做一个同步
- 独立性:各个Client之间互不干预
- 原子性:更新只能成功或者失败,没有中间状态
- 顺序性:所有Sever,同一消息发布顺序一致
7. ZK常见的应用场景
-
分布式环境下的统一命名服务
通过子父节点实现统一命名服务
-
分布式环境下的配置管理
假设ZooKeeper集群有3000台服务器,有一个配置文件都是使用了相同的内容,如果每次修改文件,都修改3000台是非常低效的。我们可以使用配置管理的方式,将配置文件的内容放到ZooKeeper节点树上的一个节点中,在这个节点上,我们保存配置文件的信息,一旦发生改变,会触发事件,通知3000台服务器调用对应的程序读取新的配置文件信息,将本地配置文件的内容再做一个同步,实现了配置的管理操作
-
数据发布/订阅
将数据发布到节点上,由于事件监听机制,会监听这个节点是否发生改变。一旦发生改变,就将数据同步到服务器上
-
分布式环境下的分布式锁
临时节点小的先拿到锁。一旦client1断开连接,则node_001临时节点消失,其余同理,这既是分布式锁。分布式锁还有其他实现方案。
-
集群管理问题
在集群下,如果有节点出现问题,就会触发事件 ,给运维人员发送邮件:某一个节点出现问题
-
定时任务的争夺
-
分布式队列
-
分布式计数器
以上这些都可以通过ZooKeeper来实现。