Zookeeper 入门

官网:https://zookeeper.apache.org/

什么是zookeeper

Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目。
Zookeeper由雅虎公司创建,由于最初雅虎公司的内部研究小组的项目大多以动物的名字命名,所以后来就以Zookeeper(动物管理员)来命名了,而就是由Zookeeper来负责这些分布式组件环境的协调工作。
Zookeeper 是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等。
Zookeeper 提供了一个类似于 Linux 文件系统的树形结构(可认为是轻量级的内存文件系统,但只适合存少量信息,完全不适合存储大量文件或者大文件),同时提供了对于每个节点的监控与通知机制。

Zookeeper工作机制

zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然 后接受观察者的注 册,一旦这些数据的状态发生变化,Zookeeper就 将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应

  1. 服务1启动时通过在Zookeeper上创建临时节点,注册信息到Zookeeper上
  2. 其他服务启动时,到Zookeeper上获取当前在线服务列表,并注册监听
  3. 服务1下线时
  4. 其他服务可以通过Zookeeper得到下线的事件通知

    Zookeeper角色

    Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种:
  • Leader
  • Follower
  • Observer

image.png

Leader

  1. 一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及 Observer 间的心跳。
  2. 所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。只要有超过半数节点(不包括 observeer 节点)写入成功,该写请求就会被提交(类 2PC 协议)。

    Follower

  3. 一个 Zookeeper 集群可能同时存在多个 Follower,它会响应 Leader 的心跳,

  4. Follower 可直接处理并返回客户端的读请求,同时会将写请求转发给 Leader 处理,
  5. 并且负责在 Leader 处理写请求时对请求进行投票。

    Observer

    角色与 Follower 类似,但是无投票权。Zookeeper 需保证高可用和强一致性,为了支持更多的客户端,需要增加更多 Server;Server 增多,投票阶段延迟增大,影响性能;引入 Observer, Observer 不参与投票; Observers 接受客户端的连接,并将写请求转发给 leader 节点; 加入更多 Observer 节点,提高伸缩性,同时不影响吞吐率。

    投票机制

    每个 sever 首先给自己投票,然后用自己的选票和其他 sever 选票对比,权重大的胜出,使用权重较大的更新自身选票箱。
    具体选举过程如下:

  6. 每个 Server 启动以后都询问其它的 Server 它要投票给谁。对于其他 server 的询问, server 每次根据自己的状态都回复自己推荐的 leader 的 id 和上一次处理事务的 zxid(系 统启动时每个 server 都会推荐自己)

  7. 收到所有 Server 回复以后,就计算出 zxid 最大的哪个 Server,并将这个 Server 相关信息设置成下一次要投票的 Server。
  8. 计算这过程中获得票数最多的的 sever 为获胜者,如果获胜者的票数超过半数,则改 server 被选为 leader。否则,继续这个过程,直到 leader 被选举出来
  9. leader 就会开始等待 server 连接
  10. Follower 连接 leader,将最大的 zxid 发送给 leader
  11. Leader 根据 follower 的 zxid 确定同步点,至此选举阶段完成。
  12. 选举阶段完成 Leader 同步后通知 follower 已经成为 uptodate 状态
  13. Follower 收到 uptodate 消息后,又可以重新接受 client 的请求进行服务了

    示例

    目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5,按编号依次启动,它们的选择举过程如下:

  14. 服务器 1 启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器 1 的状态一直属于 Looking。

  15. 服务器 2 启动,给自己投票,同时与之前启动的服务器 1 交换结果,由于服务器 2 的编号大所以服务器 2 胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
  16. 服务器 3 启动,给自己投票,同时与之前启动的服务器 1,2 交换信息,由于服务器 3 的编号最大所以服务器 3 胜出,此时投票数正好大于半数,所以服务器 3 成为领导者,服务器1,2 成为小弟。
  17. 服务器 4 启动,给自己投票,同时与之前启动的服务器 1,2,3 交换信息,尽管服务器 4 的编号大,但之前服务器 3 已经胜出,所以服务器 4 只能成为小弟。
  18. 服务器 5 启动,后面的逻辑同服务器 4 成为小弟。

    Zookeeper 工作原理(原子广播)

  19. Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。

  20. 当服务启动或者在领导者崩溃后,Zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 的完成了和 leader 的状态同步以后,恢复模式就结束了。
  21. 状态同步保证了 leader 和 server 具有相同的系统状态
  22. 一旦 leader 已经和多数的 follower 进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 zookeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。Zookeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。
  23. 广播模式需要保证 proposal 被按顺序处理,因此 zk 采用了递增的事务 id 号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了 zxid。
  24. 实现中 zxid 是一个 64 为的数字,它高 32 位是 epoch 用来标识 leader 关系是否改变,每次一个 leader 被选出来,它都会有一个新的 epoch。低 32 位是个递增计数。
  25. 当 leader 崩溃或者 leader 失去大多数的 follower,这时候 zk 进入恢复模式,恢复模式需要重新选举出一个新的 leader,让所有的 server 都恢复到一个正确的状态。

Zookeeper特点

image.png

  1. Zookeeper:是由一个Leader(领导者),多个Follower(跟随者)组成的集群;
  2. 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器。
  3. 全局数据一致:每个Server上保存一份相同的数据副本,Client无论连接到那个Server,数据都是一致的;
  4. 更新请求顺序执行:来自同一个Client的更新请求按其发送顺序依次执行;
  5. 数据更新原子性:一次数据更新要么全部成功,要么失败。
    • 每次写操作都有事务id(zxid)
  6. 实时性,在一定时间范围内,Client能够读取到最新的数据。

    Zookeeper的数据结构

    Zookeeper数据模型的结构类似于Unix文件系统,整体上可以看作是一棵树,每个节点称为ZNode,每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。
    image.png

    Zookeeper应用场景

    用于提供的服务有:
  • 统一命名服务
  • 统一配置管理
  • 统一集群管理
  • 服务器节点动态上下线
  • 软负责均衡
  • 统一命名服务

    在分布式环境下,通常需要对应用/服务进行统一命名,来便于识别。
    image.png

    统一配置管理

    在分布式环境中,配置文件同步非常常见。
  1. 一般要求一个集群中,所有节点的配置信息是一致的,如Kafka集群、Hadoop集群等
  2. 配置文件修改后,希望可以快速同步到各个节点上。
  3. Zookeeper可以帮助我们实现配置管理
    1. 将配置信息写入到Zookeeper的一个ZNode上
    2. 各个客户端监听这个ZNode,
    3. 一旦ZNode中的数据被修改,ZooKeeper将及时通知各个客户端服务器

image.png

统一集群管理

在分布式环境中,实时掌握各个节点的状态是必要的,便于根据节点的实时状态做出一些调整。

  • Zookeeper可以实现实时监控节点状态变化
    1. 将节点信息写入到Zookeeper的一个ZNode上
    2. 监听这个ZNode可以获取它的实时状态变化

image.png

服务器动态上下线

客户端可以通过Zookeeper实时洞察到服务器上下线的变化。
image.png

软负载均衡

在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求。
image.png

Zookeeper安装

官网:https://zookeeper.apache.org/
安装前准备:

  • 安装JDK

开始安装:

  1. # 下载
  2. wget https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.7.0/apache-zookeeper-3.7.0-bin.tar.gz
  3. # 解压
  4. tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz apache-zookeeper-3.5.7-bin/
  5. # 安装
  6. mv apache-zookeeper-3.5.7-bin /usr/local/zookeeper

修改配置:

cd conf
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg

修改内容如下:

dataDir=/workspace/data/zookeeper

启动服务端:

bin/zkServer.sh start

启动后,通过jps可以看到一个进程:QuorumPeerMain
查看Zookeeper的状态:

bin/zkServer.sh status

启动客户端:

bin/zkCli.sh

客户端中查看路径:

ls /

# 退出客户端
quit

停止Zookeeper

bin/zkServer.sh stop

配置参数解读

Zookeeper中配置文件zoo.cfg中参数的含义如下:

  • tickTime=2000:通信心跳时间,Zookeeper服务器于客户端的心跳时间,单位是毫秒
    image.png
  • initLimit=10:LF初始通信时限,Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)
    image.png
  • syncLimit = 5:LF同步通信时限,Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
    image.png
  • dataDi:保存Zookeeper中数据的路径
    • 注意:默认的tmp目录,容易被Linux系统定期删除,所以一般不用默认的tmp目录。
  • clientPort = 2181:客户端连接端口,通常不做修改。

    集群操作

    集群安装

    1、集群规划:
    Zookeeper集群最少是三台,这里选择三台机器。
    2、解压安装:参考Zookeeper的单机安装
    3、配置服务器编号
    3.1、创建目录:
    mkdir /workspace/data/zookeeper
    
    3.2、在目录下创建myid文件
    vi myid
    
    在文件中添加与server对应的编号,如:第2台机器是2
    2
    
    注意:上下不要有空行,左右不要有空格
    4、拷贝配置到其他机器上,并修改myid中的编号分别为3、4
    5、配置zoo.cfg文件 ```properties

    修改数据存储路径

    dataDir=/workspace/data/zookeeper

添加服务器集群信息

server.2=server2:2888:3888 server.3=server3:2888:3888 server.4=server4:2888:3888

参数解读:`server.序号=host:port1:port2`

   - 序号是一个数字,表示是第几号服务器,值是数据存储路径下myid中的值,Zookeeper启动时读取此文件,拿到里面的值与zoo.cfg里面的二配置信息进行比较,用来区分server
   - host时服务器的地址
   - port1是服务器Follower与集群中的Leader服务器交换信息的端口
   - port2是在集群中Leader服务器挂了后,用来进行重新选举的端口,而这个端口就是用来执行选举服务器相互通信的端口,用于选出一个新的Leader

6、启动集群(需要几台集群都执行启动)
```shell
bin/zkServer.sh start

查看启动状态, 可以用来查看服务是leader或者follower:

bin/zkServer.sh status

关闭服务:

bin/zkServer.sh stop

集群启动脚本

创建一个脚本zkTool.sh

vi zkTool.sh

脚本内容如下:

#!/bin/bash

case $1 in
"start"){
    for i in server1 server2 server3
    do
        echo ------ Zookeeper $i 启动 ------
        ssh $i "/usr/local/zookeeper/bin/zkServer.sh start"
    done
}
;;
"stop"){
    for i in server1 server2 server3
    do
        echo ------ Zookeeper $i 停止 ------
        ssh $i "/usr/local/zookeeper/bin/zkServer.sh stop"
    done
}
;;
"status"){
    for i in server1 server2 server3
    do
        echo ------ Zookeeper $i 状态 ------
        ssh $i "/usr/local/zookeeper/bin/zkServer.sh status"
    done
}
;;
esac

给脚本赋予执行权限:

chmod a+x zkTool.sh

Zookeeper选举机制

正常启动

  1. 第一台服务器1启动,发起一次选举,服务器1投自己一票,此时服务器1一票,不够半数以上(总共5票,半数以上最低是3票),选举无法完成,服务器1保证LOOKING状态
  2. 第二台服务器2启动,再一次发起选举,服务器1和2分别投自己一票并交换选票结果,此时服务器发现服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1为0票,服务器2为2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
  3. 服务器3启动,发起一次选举,此时服务器1和服务器2都会再给自己投票后,改选为推举服务器3,此时:服务器1为0票,服务器2为0票,服务器3为3票。服务器3的票数已经超过了半数,服务器3当选为Leader, 服务器1和2改为FOLLOWING状态,服务器3改为LEADING状态
  4. 服务器4启动,发起一次选举,此时服务器1、2、3已经不是LOOKING状态,不会更改选票信息。交换选票信息:服务器3为3票,服务器4为1票,此时服务器4服从多数,更改选票结果为服务器3,并更改状态为FOLLOWING状态
  5. 服务器5启动,同服务器4一样为Follower。

    非正常启动

  6. 当Zookeeper集群中的一台服务器出现以下两种情况时,就会开始进入Leader选举:

    • 服务器初始化启动
    • 服务器运行期间无法和Leader保持连接
  7. 当一台进入Leader选举流程时,当前集群可能处于以下两种状态:

    • 集群中本来存在一个Leader
      • 机器试图去选举Leader时,会被告知当前集群的Leader信息,当前机器,只需要和Leader机器建立连接,并进行状态同步即可
    • 集群中不存在Leader

      • 假设Zookeeper由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别是8、8、8、7、7,并且此时SID为3的服务器是Leader,某一时刻,服务器3和5故障,因此开始重新Leader选举;
      • 如果当前SID为1、2、4的机器投票结果如下:
        | | EPOCH | ZXID | SID | | —- | —- | —- | —- | | 1 | 1 | 8 | 1 | | 2 | 1 | 8 | 2 | | 4 | 1 | 7 | 4 |

      • 选举Leader的规则:

        • EPOCH大的直接胜出
        • EPOCH相同,事务ID(ZXID)大的直接胜出
        • 事务id相同,服务器ID(SID)大的胜出
      • 因此,服务器2重新被选为Leader

        客户端

        Zookeeper自带有客户端bin/zkCli.sh,其他的如java的客户端,也可以连接。
        注意:

        Zookeeper客户端Client每次操作服务端:

        • SID:服务器ID,用来唯一标识一台Zookeeper集群中的机器,每天机器不能重复,和myid一致。
        • 写操作都有事务id(ZXID):事务ID,用来标识一次服务状态的变更。再某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和Zookeeper服务器对应客户端“更新请求”的处理逻辑有关。
        • Epoch:每个Leader任期的代号。没有Leader时,同一轮投票过程中的逻辑时钟值时相同的。每投完一次票这个数据就会增加。

客户端命令

客户端连接:

/bin/zkCli.sh -server host:2181端口

操作命令:

  • help:显示所有操作命令
  • ls:查看当前 znode 的子节点 [可监听]
    • -w 监听子节点变化
    • -s附加次级信息,参考下面的【znode节点数据信息】

示例:

ls /
# 附加次级信息
ls -s /
  • create:普通创建
    • -s 含有序列
    • -e 临时(重启或者超时消失)
  • get:获得节点的值 [可监听]
    • -w 监听节点内容变化
    • -s 附加次级信息
  • set:设置节点的具体值
  • stat:查看节点状态
  • delete:删除节点
  • deleteall:递归删除节点

    znode节点数据信息

    使用ls -s /可以查看节点的附加次级信息,附加次级信息如下:

  • czxid:创建节点的事务 zxid,每次修改 ZooKeeper 状态都会产生一个 ZooKeeper 事务 ID。事务 ID 是 ZooKeeper 中所有修改总的次序。每次修改都有唯一的 zxid,如果 zxid1 小于 zxid2,那么 zxid1 在 zxid2 之前发生。

  • ctime:znode 被创建的毫秒数(从 1970 年开始)
  • mzxid:znode 最后更新的事务 zxid
  • mtime:znode 最后修改的毫秒数(从 1970 年开始)
  • pZxid:znode 最后更新的子节点 zxid
  • cversion:znode 子节点变化号,znode 子节点修改次数
  • dataversion:znode 数据变化号
  • aclVersion:znode 访问控制列表的变化号
  • ephemeralOwner:如果是临时节点,这个是 znode 拥有者的 session id。如果不是临时节点则是 0。
  • dataLength:znode 的数据长度
  • numChildren:znode 子节点数量

    Znode节点类型

  • 持久(Persistent): 客户端和服务端断开连接后,创建的节点不删除

  • 短暂(Ephemeral): 客户端和服务器端断开连接后,创建的节点自己删除

    目录节点

    Znode 有四种形式的目录节点:
  1. PERSISTENT持久化目录节点:客户端与Zookeeper断开连接后,该节点依旧存在
  2. PERSISTENT_SEQUENTIAL持久化顺序编号目录节点:客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号;
    说明:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号时一个单调递增的计数器,由父节点维护。
    注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序好推断事件的顺序。
  3. EPHEMERAL临时目录节点:客户端与Zookeeper断开连接后,该节点被删除
  4. EPHEMERAL_SEQUENTIAL临时顺序编号目录节点:客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。

    节点操作

    查看目录

    ls /
    

    创建节点

    创建永久节点

    分别创建2个普通节点(永久节点+ 不带序号)

    create /node1 "name"
    
    create /node1/child1 "chentiefeng"
    

    注意:创建节点时,要赋值

    创建永久节点(带序号)
    create -s /node1/child1/secondChild "第一个带序号的节点"
    

    如果继续创建该节点,就会发现节点自动增加,序号从0000000000开始,然后是0000000001等等。

    创建临时节点
    create -e /tmp/node1 "tmp node1"
    

    创建临时节点(带序号)
    create -e -s /tmp/node1/child "带序号的临时节点"
    

    获取节点的值

    get -s /node1
    
    get -s /node1/child1
    

    修改节点的值

    set /node1 "I am chentiefeng"
    

    删除节点

    delete /node1
    

    递归删除节点:

    deleteall /node1/child1
    

    查看节点状态

    stat /node1
    

    监听器

    客户端注册监听它关心的目录节点,当目录节点发生变化(数据变化、节点删除、子目录节点增加删除)时,Zookeeper会通知客户端。
    监听机制保证Zookeeper保存的任何的数据的任何变化都能快速的响应到监听了该节点的应用程序。

    监听原理

  5. 客户端首先要有一个main()线程

  6. 再main线程中创建Zookeeper客户端,这是就会创建两个线程,要给负责网络连接通信(connect), 一个负责监听(listener)。
  7. 通过connect线程将注册的监听事件发送给Zookeeper
  8. 再Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
  9. Zookeeper监听到有数据或者路径变化,就会将这个消息发送给listener线程。
  10. listener线程内部调用了process()方法

image.png

常见的监听

  1. 监听节点数据的变化
    • get path [watch]
  2. 监听子节点增减的变化
    • ls path [watch]

      监听节点数据的变化

      get -w /node1
      
      注意:注册一次,只能监听一次。想再次监听,需要再次注册

      监听子节点增减的变化

      ls -w /node1
      
      注意:注册一次,只能监听一次。想再次监听,需要再次注册

      客户端向服务端写数据流程

      写流程之写入请求直接发送给Leader节点

      image.png

      写流程之写入请求发送给follower节点

      image.png

      实战

      请查看:Zookeeper实战

      源码分析

      请查看:Zookeeper源码学习