ZooKeeper

吞佛童子2022年10月10日
  • 中间件
  • ZooKeeper
大约 9 分钟

ZooKeeper

1. 概述

1) 作用

  • 分布式系统提供协调服务
  1. 服务命名
    • 通过 顺序节点 生成全局唯一 id
  2. 服务注册 & 订阅
    • 服务端将自己注册到 ZK 假设 '/server' 节点下
    • 客户端订阅 '/server' 节点,获取有哪些服务端可以提供服务
  3. 分布式通知
    • 通过 监听 想要的节点,进行后续处理
    • 或改变 某节点状态,让其他需要该节点数据的其他服务收到通知
  4. 分布式锁

2) 应用场景

  • Kafka

2. 相关概念

1) 节点

  • ZooKeeper 为 树状目录结构,每个节点称为 znode,以 '/' 分割表示层级
  • img.png

① 四种类型

// -s 顺序节点,节点名称后面自动递增添加数字后缀
// -e 临时节点
create [-s][-e] path data acl
  • 持久节点[Persistent]
    • 客户端与 ZK 服务器端断开连接后,该节点仍然存在,不会被删除
    • create /liuxian "tunfo" 根目录下创建一个 liuxian 的节点,数据值为 tunfo
  • 临时节点[Ephemeral]
    • 客户端与 ZK 服务器端断开连接后,该节点会被删除
    • 只能作为叶子节点
    • /create -e /zhishou "fengchan" 根目录下创建一个 zhishou 的节点,数据值为 fengchan
  • 持久顺序节点[Persistent_sequential]
    • 除了具备持久性外,在注册时,节点名称保证同一父节点下的顺序性
    • create -s /ps 11 根目录下创建一个 ps0000000001 的节点,数据值为 11
  • 临时顺序节点[Ephemeral_sequential]
    • 除了具备临时性外,在注册时,节点名称保证同节点下的顺序性
    • create -e -s /es 11 根目录下创建一个 es0000000001 的节点,数据值为 11

② 数据结构

  • stat

    • 保存状态信息
    • 状态信息有以下内容:
      • cZxid
        • 创建时的事务 id
      • ctime
        • 创建时间,从 1970 年开始
      • mZxid
        • 最后一次修改时的事务 id
      • mtime
        • 最后一次修改时间
      • numChildren
        • 子节点个数
      • pZxid
        • 子节点 最后一次修改时的事务 id
      • cversion
        • 子节点版本号,表示修改次数
      • dataVersion
        • 当前节点数据版本号,表示修改次数
      • aclVersion
        • 当前节点的 ACL[权限控制] 版本号
      • ephemeralOwner
        • 若是持久节点 == 0
        • 若为临时节点 == 创建该临时节点的会话 id
      • dataLength
        • 数据长度
  • data

    • 保存实际存放的数据内容

③ 操作权限

  • create
    • 可以创建子节点
  • delete
    • 可以删除子节点
  • read
    • 可以获取该节点数据及其子节点信息
  • write
    • 可以修改节点数据
  • admin
    • 可以设置该节点的 ACL 权限

④ 节点监听

  1. 流程
    • 客户端可以在任意节点向服务端注册 事件监听器 [Watcher] 监听该节点, 客户端保存该 Watcher 对象到本地的 WatcherManager
    • 当服务端该节点有事件发生时,ZooKeeper 服务端会将事件通知到对此感兴趣的客户端处
      • 监听节点的 数据 发生改变
      • 监听节点的 子节点数 发生改变
    • 客户端收到该通知后,从 WatcherManager 中取出该 Watcher 对象执行回调逻辑
  2. 特点
    • 一次性
      • 一个 Watcher 对象被触发后,客户端取出该 Watcher 对象后,本地存储将不再有该 Watcher 对象
    • 客户端串行
      • 客户端接收到的 Watcher 后执行回调是串行的,若一个回调逻辑阻塞,后续 Watcher 的回调也会被阻塞
    • 轻量
      • 服务端只会通知 状态、事件类型、节点路径,不包含具体事件内容,若需要则客户端需要主动去服务端获取

2) 身份认证方式

  • world
    • default
    • 所有用户均可访问
  • auth
    • 只有已认证用户可访问
  • digest
    • 用户名 : 密码 方式
  • ip
    • 对 ip 进行指定

3) 会话

  • ZooKeeper 服务端与 一个 ZooKeeper 客户端建立的 一次 长连接,就是一个会话
  • 每个会话有唯一 会话 id
  • 客户端 & 服务器端 通过 心跳检测 机制保证双方在线 - TCP
  • 若某时刻某个客户端断线,在 SessionTimeout 范围内,该客户端仍未与 集群中的任一服务器建立连接,则该会话失效

3. 集群

1) 主要角色

  • ZooKeeper 一般以 集群 的形式搭建,主要由以下几种不同角色

① Leader

  • 节点的 写入读取参与 Leader 选举负责数据同步

② Follower

  • 节点的 读取参与 Leader 选举

③ Observer

  • 节点的 读取
  • 防止太多从节点参与选举 & 过半写判断,影响性能;便于横向扩展,只需要增加 Observer 即可

2) Leader 选举

① 选举标准

  • zxid
    • long 型 64 位整数,分为 2 部分:
      • 纪元 epoch
        • 每次选举 epoch 改变
      • 计数器 counter
  • server id
/*
 * We return true if one of the following three cases hold:
 * 1- New epoch is higher 选 epoch 更大的
 * 2- New epoch is the same as current epoch, but new zxid is higher 选 zxid 更大的
 * 3- New epoch is the same as current epoch, new zxid is the same 
 *  as current zxid, but server id is higher. 选 server id 更大的,代表配置更好的机器
 */

return ((newEpoch > curEpoch)
        || ((newEpoch == curEpoch)
            && ((newZxid > curZxid)
                || ((newZxid == curZxid)
                    && (newId > curId)))));

② 选举过程中节点状态

  • Leading
  • Looking
  • Following

③ 选举流程

  1. 服务启动时的选举
    • 每个节点选举自己成为 Leader,并将投票信息广播方式传给集群中的其他节点
    • 节点收到其他节点的投票信息后,与自己的投票情况比较,此时节点处于 Looking 状态
      • 优先选择 zxid 大的投票结果
      • zxid 相同时,选择 myid 大的投票结果
    • 若需要修改自己的投票情况,则再次广播该信息
    • 若集群中过半的机器均选择了某个节点作为 Leader,选举结束
    • 更新节点状态,Leader 为 Leading 状态,Follower 为 Following 状态
    • img_1.png
  2. 服务运行期间的选举
    • 若 Leader 节点宕机,则 Follower 节点会修改自身状态为 Looking,重新进入选举流程
    • 生成投票信息 [myid, zxid],第一轮仍然都投自己,将投票信息比较后,再次广播
    • 直到过半机器选择某个节点,更新节点状态

3) 选举后的数据同步

  • 选举完成后,Follower & Observer [统称 Learner] 会向 Leader 注册,然后开始数据同步的过程

① 同步标准

  • PeerLastZxid
    • Learner 服务器最后处理的 zxid
  • minCommittedLog
    • Leader 提议缓存队列中的 min zxid
  • maxCommittedLog
    • Leader 提议缓存队列中的 max zxid

② 同步方式

  1. 直接差异化同步
    • 前提:
      • PeerLastZxid ∈ [minCommittedLog, maxCommittedLog)
    • 流程:
      • Leader 向 Learner 发送 diff 指令,表示开始进行差异化同步
      • Leader 将 (PeerLastZxid, maxCommittedLog] 数据作为 proposal 发送给 Learner
      • Leader 发送 NEWLEADER 命令给 Learner,表示本节点为选举出来的 Leader,Learner 同步完成后返回 ACK 应答
      • 过半 Learner 响应 ACK 后,Leader 发送 upToDate 命令给 Learner,表示 更新完成,Learner 收到后返回 ACK
  2. 先回滚再差异化同步
    • 前提:
      • PeerLastZxid ∈ [minCommittedLog, maxCommittedLog) 但不存在于 新 Leader 中
    • 流程:
      • 将不存在于 新 Leader 的事务回滚,再进行差异化同步
  3. 仅回滚同步
    • 前提:
      • PeerLastZxid > maxCommittedLog
    • 流程:
      • 将不存在于 新 Leader 的事务回滚
  4. 全量同步
    • 前提:
      • PeerLastZxid < minCommittedLog
      • Leader 上没有提议缓存队列 && PeerLastZxid != Leader の max zxid
    • 流程:
      • Leader 发送 SNAP 命令,将全部数据同步到 Learner

4. 数据一致性问题

  • ZooKeeper 遵循的 CP 一致性协议,保证 分区容忍性 & 数据一致性

1) ZK 如何保证 数据一致性?

  • 借助 ZAB 原子广播协议 实现,该方式分为 两阶段,但不等同于 2PC

2) ZK 什么情况下会出现 数据不一致 的情况?

  • 查询不一致
    • 过半写只能保证过半写成功的节点数据与 Leader 一致,其他的要等到数据同步才可一致

5. ZAB

1) ZAB 状态

public enum ZabState {
    ELECTION, // 选举
    DISCOVERY, // 选举后 Learner 与 Leader 建立连接
    SYNCHRONIZATION, // 与 Leader 进行数据同步
    BROADCAST // 消息广播,可以对外提供服务
}

2) 消息广播

  • ZK 中只有 Leader 可以写入数据,因此其他节点接收到写入请求时,转发给 Leader 进行处理

① 流程

  • Leader 收到写入请求后,生成一个 proposal 提议,每个 proposal 提议拥有一个全局唯一递增的事务 id[zxid]
  • Leader 将提议放入一个 FIFO 队列中,按照 FIFO 顺序发送给所有 Follower
  • Follower 收到提议后,以 事务日志 的形式写入本地磁盘,写入成功后回复 ACK
  • Leader 收到 过半 ACK 后认为可以写入数据,发送 Commit 命令给 Follower 通知可以提交该 proposal
  • img_2.png

6. ZK 分布式锁

1) 如何实现?

  1. 每个服务在某个节点下均创建 临时有序节点
  2. 获取该节点下的 所有子节点 列表,对其进行排序,并判断自己所创建的节点是否为最小的节点
  3. 如果是,说明获得锁,可以继续执行;执行完成 | 客户端断线后,该临时有序节点自动删除
  4. 如果不是,监听自己前一个比它小的节点,判断是否存在,如果不存在,说明自己可以获得锁,进行后续操作

2) 优点

  • 可靠性高,实现简单

3) 缺点

  1. 性能没有 缓存 高
    • 频繁创建、删除 节点性能受到影响,只能由 Leader 进行操作,然后同步到所有的 Follower 中

7. 补充

1) ZK 服务器为什么推荐为 奇数?

  • 假设有 奇数 n,过半 == n / 2 + 1
  • 偶数 x 要想达到过半效果,则过半 == x / 2 + 1
  • n / 2 + 1 == x / 2 + 1 --> x == n + 1
  • 即,要达到同样的过半效果,奇数比偶数 小 1 台,节约了开销

2) 过半机制如何防止脑裂?

  1. 脑裂
    • 某个时刻集群因为网络等原因,分为了一个个小团体,每个小团体选出了各自的 Leader
    • 当恢复为一个团体时,出现了多个 Leader 同时存在的情况
  2. 过半机制防止脑裂
    • 分成小团体后,过半机制保证最多只能有一个小团体可以达到半数以上,从而成功选举出 Leader,其他小团体即使全部同意也达不到过半的硬性要求
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou