ZooKeeper 基础

本贴最后更新于 1300 天前,其中的信息可能已经事过境迁

Apache ZooKeeper

应用场景

  1. 分布式协调:对 Zookeeper 中的数据做监听,一旦数据发生变动都会感知,为客户端进行选举;
  2. 元数据管理:存放客户端需要的元数据信息,Dubbo、Kafka 等中间件都有用到;
  3. 高可用:利用分布式锁实现高可用,多个节点往 ZK 上注册,注册成功后成为 active,没有注册成功的节点阻塞;
  4. 分布式锁:可以搞,但高并发下性能差,建议用 Redis;

基础知识

数据模型

树形结构

ZooKeeper 数据结构

节点类型与特性

  • 持久节点
    这种节点也是在 ZooKeeper 最为常用的,几乎所有业务场景中都会包含持久节点的创建。之所以叫作持久节点是因为一旦将节点创建为持久节点,该数据节点会一直存储在 ZooKeeper 服务器上,即使创建该节点的客户端与服务端的会话关闭了,该节点依然不会被删除。如果我们想删除持久节点,就要显式调用 delete 函数进行删除操作。
  • 临时节点
    从名称上我们可以看出该节点的一个最重要的特性就是临时性。所谓临时性是指,如果将节点创建为临时节点,那么该节点数据不会一直存储在 ZooKeeper 服务器上。当创建该临时节点的客户端会话因超时或发生异常而关闭时,该节点也相应在 ZooKeeper 服务器上被删除。同样,我们可以像删除持久节点一样主动删除临时节点。
  • 有序节点
    其实有序节点并不算是一种单独种类的节点,而是在之前提到的持久节点和临时节点特性的基础上,增加了一个节点有序的性质。所谓节点有序是说在我们创建有序节点的时候,ZooKeeper 服务器会自动使用一个单调递增的数字作为后缀,追加到我们创建节点的后边。例如一个客户端创建了一个路径为 works/task- 的有序节点,那么 ZooKeeper 将会生成一个序号并追加到该节点的路径后,最后该节点的路径为 works/task-1。通过这种方式我们可以直观的查看到节点的创建顺序。

节点状态结构解释

Watch 机制

  • 客户端、服务端分别有 ZKWatchManager 个 WatchManager,用来存放对应的观察者列表
  • 客户端工作内容
    • 当发送一个带有 Watch 事件的请求,客户端首先将该会话标记为 Watch 事件,之后通过 DataWatchRegistration 类保存 Watch 事件和节点的对应关系
    • 客户端将请求封装成一个 Packet 对象,将该对象添加到等待发送队列 outgoingQueue 中,最后将请求逐个发送给服务端
    • 最后调用负责处理响应的 SendThread 线程类中的 readResponse 方法接收服务端的回调。最后调用 finishPacket 方法将 Watch 注册到 ZKWatchManager 中
  • 服务端工作内容:
    • 当 zookeeper 服务端收到请求时,会判断请求中是否包含 Watch 事件(底层通过 FinalRequestProcessor 类中的 processRequest 方法实现)
    • 当 getDataRequest.getWatch 为 True 时,表明该请求需要进行 Watch 监控注册通过 zks.getZKDatabase().getData 将 Watch 事件注册到服务端的 WatchManager 中

服务端 Watch 事件触发过程

  • 在 setData 方法中执行完对节点数据的变更后会调用 WatchManager.triggerWatch 方法触发数据变更事件
  • triggerWatch 方法内容
    • 首先封装了一个具有会话状态、事件类型、数据节点 3 种属性的 WatchedEvent 对象;
    • 查询该节点注册的 Watch 事件,如果为空说明没有注册 Watch 事件,存在则将 Watch 事件添加到 Watchers 集合中
    • 将 WatchManager 中的 Watch 事件删除,最后通过 process 方法向客户端发送通知
  • 客户端回调处理过程
    • SendThread.readResponse() 方法来统一处理服务端的响应
    • 反序列化服务器发送请求头信息 replyHdr.deserialize(bbia, “header”),并判断相属性字段 xid 的值为 -1,表示该请求响应为通知类型
    • 在处理通知类型时,先将已收到的字节流反序列化为 WatcherEvent 对象
    • 判断客户端是否配置了 chrootPath ,如果配置了 chrootPath 属性,需要对接收到的节点路径进行 chrootPath 处理
    • 调用 eventThread.queueEvent() 方法将收到的事件交给 EventThread 线程处理
    • ventThread.queueEvent() 方法
      • 按照通知事件类型,会从 ZKWatchManager 中查询在客户端注册过的 Watch 事件信息,查询到后将 Watch 信息从 ZKWatchManager 中删除
      • 然后在获取到 Watch 事件信息之后,将查询到的 Watch 存储到 waitingEvents 队列中,调用 EventThread 类中的 run 方法循环取出 Watch 事件进行处理,最后调用 processEvent(event) 方法来最终执行实现了 Watcher 接口的 process()方法。

ACL 机制

  • 授权方式
    • IP 方式:使用的授权对象可以是一个 IP 地址或 IP 地址段
    • Digest 或 Super 方式:对应于一个用户名
    • World 方式:授权系统中所有的用户
  • 授权内容
    • 数据节点(create)创建权限,授予权限的对象可以在数据节点下创建子节点;
    • 数据节点(wirte)更新权限,授予权限的对象可以更新该数据节点;
    • 数据节点(read)读取权限,授予权限的对象可以读取该节点的内容以及子节点的信息;
    • 数据节点(delete)删除权限,授予权限的对象可以删除该数据节点的子节点;
    • 数据节点(admin)管理者权限,授予权限的对象可以对该数据节点体进行 ACL 权限设置。
  • 实现原理
    • 客户端在 ACL 权限请求发送过程的步骤
      • 封装该请求的类型
      • 将权限信息封装到 request 中并发送给服务端
    • 服务器的实现
      • 分析请求类型是否是权限相关操作
      • 根据不同的权限模式(scheme)调用不同的实现类验证权限最后存储权限信息

集群架构

ZooKeeper 集群架构

  • leader(领导者) -- 为客户端提供读和写的功能,负责投票的发起和决议,只有 leader 才能接受写的服务;
  • follower(跟随者) -- 为客户端提供读和写的功能,负责投票的发起和决议;
  • observer(观察者) -- 为客户端提供读服务,如果是写服务就转发个 leader。不参与 leader 的选举投票。也不参与写的过半原则机制。在不影响写的前提下,提高集群读的性能,为 zookeeper3.3 系列新增的角色;
  • client:连接 zookeeper 集群的使用者,请求的发起者,独立于 zookeeper 集群的角色;

选举机制

  • 概念
    • Serverid:服务器 ID,编号越大在选择算法中的权重越大;
    • Zxid:数据 ID,服务器中存放的最大数据 ID,值越大说明数据越新,在选举算法中数据越新权重越大;
    • Epoch:逻辑时钟,或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。
    • Server 状态:选举状态
      • LOOKING,竞选状态;
      • FOLLOWING,随从状态,同步 leader 状态,参与投票;
      • OBSERVING,观察状态,同步 leader 状态,不参与投票;
      • LEADING,领导者状态。
  • 触发结点
    • 集群启动
    • leader 挂掉
    • follower 挂掉后 leader 发现已经没有过半的,leader 发现怎么集群不能对外提供服务了,会将自己的状态改为挂掉,重新进行 leader 选举
  • 选举流程

ZooKeeper 选举流程

  • 选举状态

ZooKeeper 选举状态

过半机制

public class QuorumMaj implements QuorumVerifier {
    private static final Logger LOG = LoggerFactory.getLogger(QuorumMaj.class);
    int half;
    // n表示集群中zkServer的个数(准确的说是参与者的个数,参与者不包括观察者节点)
    public QuorumMaj(int n){
        this.half = n/2;
    }
    // 验证是否符合过半机制
    public boolean containsQuorum(Set<Long> set){
        // half是在构造方法里赋值的
        // set.size()表示某台zkServer获得的票数
        return (set.size() > half);
    }
}

奇数台

  • 既然要大于 n/2,那么当 n 为奇数的时候,必然有一边是大于 n/2 的,这样就能选举出 Leader 了
  • 减少没必要的机器

读写流程

  • 写数据流程
    • 当 Client 向 ZooKeeper 写入数据时,判断节点是不是 Leader,是直接写入;不是转发给 Leader
    • Leader 收到写请求时,不会直接将数据写入到 ZooKeeper 中,而是将数据写入到事务日志中(类似 Mysql 的 binlog)。
    • 当 Leader 将事务日志写完后,会将写请求发送给所有节点(包括自己),收到请求后各个节点开始写自己的事务日志,日志写完后,会给 Leader 回复一个 ACK,表示写日志完成。
    • 当 Leader 收到集群中半数以上的 Follower 回复 ACK 后,Leader 发送一个 commit 消息。
    • 各个节点收到 commit 消息就会把内容放到内存中(保证数据可见)

ZooKeeper 写数据流程

  • 读数据流程
    • 读请求属于非事务性请求。无论在 leader、follower 还是 observer 上都可以直接读取。非事务请求还有 exist

ZAB 协议

  • 将数据都复制到 Follwer 中

复制数据到 Follower

  • 等待 Follwer 回应 Ack,最低超过半数即成功

等待 Follower 回应

  • 当超过半数成功回应,则执行 commit ,同时提交自己

执行 commit

  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 15 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...