03 ZooKeeper底层原理剖析与命令实战

时间:2024-10-26 06:57:22

文章目录

    • 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节点都是一个路径,我们可以通过路径名定位到这个节点来对它进行查询、修改、删除。

image-20211218214613214

  • 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 是通过临时节点监控哪个服务器挂掉的。

        image-20211219204925642

        [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会话

image-20211219210953623

  如图的ZooKeeper集群中有三台服务器,Sever2作为Leader

  • 客户端通过TCP协议会与其中一台服务器建立连接,称为会话;

  • 从建立到断开称为一次会话;

  • 如果连接的Sever出现问题,在没有超过 Timeout 时间时,可以连接其他节点。 ZooKeeper 客户端透明地转移一个会话到不同的服务器。

  • 当发生连接的转换之后,里面的特性与数据还是一样,因为这些服务对外提供的视图是统一的。

  • 会话提供顺序保障,即同一个会话中的请求以 FIFO 的顺序执行。如果客户端有多个并发会话,FIFO 顺序在多个会话之间未必能够保持。

  • 当一个会话因某种原因终止,在这个会话期间创建的临时节点将会消失。

  • Session 是由谁来创建的?

    Leader:产生一个唯一的 session,放到消息队列,让所有 server 知道。同时,消息队列可以保证ZooKeeper集群的有序。

  • 过半机制:由过半服务节点来决定session 创建成功或者失败

4. 事件监听机制原理剖析

  如果没有事件监听机制,那么客户端轮询指定节点下的数据。通过网络轮询,代价很大。

image-20211219220403901

  基于通知(notification)的机制: 客户端向 ZooKeeper 注册需要接收通知的 znode, 通过对 znode 设置监视点(watch)来接收通知。监视点是一个单次触发的操作,意即监视点会触发一个通知。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。

image-20211219220934795

  事件监听Watcher:Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应。

  • 可以设置观察点的操作:exists,getChildren,getData
  • 可以触发观察的操作:create,delete,setData

5.广播模式剖析

  ZooKeeper 的核心是原子广播,这个机制保证了各个 server 之间的信息同步。实现这个机制的协议叫做 ZAB 协议。

ZAB 协议有两种模式

  1. 恢复模式:当服务启动或者在领导者崩溃后,ZAB 就进入了恢复模式。当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 follower 以及 observer 具有相同的系统状态
    • 选举过程耗时在 200ms 之内,一般情况下 ZooKeeper 恢复服务时间间隔不超过 200ms
  2. 广播模式:广播模式需要保证提议( proposal)被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来 保 证 。 所 有 的 提 议 (proposal) 都 在 被 提 出 的 时 候 加 上 了 zxid( 比 如 : 0x300000002)。 epoch 也称为纪元数字,如:3。实现中 zxid 是一个 64 位的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的epoch,低32 位是个递增计数

广播模式的操作流程如下:

image-20211219222510505

客户端给与它建立连接的Follower服务器发送request请求,然后Follower服务器立即将请求发送给Leader服务器,然后Leader服务器给所有的Follower服务器与Observer发送确认提议,每一个服务器再接收到消息后会给予一个响应,Leader服务器接收到一半以上机器数同意后会进行提交,然后将提交的事同步到所有服务器的消息队列里,然后让所有服务器都进行相同操作。

6. ZooKeeper集群的特点

  ZooKeeper集群有以下六个特点

  • 最终一致性:为客户端展示同一个试图,这是ZooKeeper集群里面一个非常重要的功能
  • 可靠性:如果消息被一台服务器接受,那么它将被所有服务器接受
  • 实时性:ZooKeeper不能保证两个客户端同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口,做一个同步
  • 独立性:各个Client之间互不干预
  • 原子性:更新只能成功或者失败,没有中间状态
  • 顺序性:所有Sever,同一消息发布顺序一致

7. ZK常见的应用场景

  1. 分布式环境下的统一命名服务

    image-20211220144820941

    通过子父节点实现统一命名服务

  2. 分布式环境下的配置管理

    image-20211220144845532

    假设ZooKeeper集群有3000台服务器,有一个配置文件都是使用了相同的内容,如果每次修改文件,都修改3000台是非常低效的。我们可以使用配置管理的方式,将配置文件的内容放到ZooKeeper节点树上的一个节点中,在这个节点上,我们保存配置文件的信息,一旦发生改变,会触发事件,通知3000台服务器调用对应的程序读取新的配置文件信息,将本地配置文件的内容再做一个同步,实现了配置的管理操作

  3. 数据发布/订阅

    将数据发布到节点上,由于事件监听机制,会监听这个节点是否发生改变。一旦发生改变,就将数据同步到服务器上

  4. 分布式环境下的分布式锁

    image-20211220150619852

    临时节点小的先拿到锁。一旦client1断开连接,则node_001临时节点消失,其余同理,这既是分布式锁。分布式锁还有其他实现方案。

  5. 集群管理问题

    在集群下,如果有节点出现问题,就会触发事件 ,给运维人员发送邮件:某一个节点出现问题

  6. 定时任务的争夺

  7. 分布式队列

  8. 分布式计数器

以上这些都可以通过ZooKeeper来实现。