概念

  • ZooKeeper集群中的三种服务器角色
    • Leader
    • Follower
    • Observer

ZAB协议(ZooKeeper Atomic Broadcast)简介

  • 一致性保证

    • 写操作强一致性
    • 读操作顺序一致性
  • 要解决的问题

    • 正确、高效地处理客户端大量的并发请求
    • 分布式环境中,要保证一个全局的变更序列被顺序应用
    • 容错处理(这一点是基于解决第1点问题使用的 主备模式 而提出)
  • 核心概念

    • 协议定义
      • 并不像Paxos算法那样的通用的分布式一致性算法
      • 是一种特别为ZooKeeper设计的崩溃可恢复的原子消息广播算法
    • Leader服务器
      • 所有事务请求必须由一个全局唯一的服务器来协调处理
    • Follower服务器
      • 余下的其他服务器
    • 基本流程
      • Leader负责将一个客户端事务请求转换成一个事务Proposal(提议),并将该Proposal分发给集群中所有Follower
      • Leader等待超过半数的Follower进行正确反馈
      • Leader向所有Follower分发Commit消息,要求将前一个Proposal进行提交
    • 举例
      • Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点
      • 如果是读请求,就直接从当前节点中读取数据
      • 如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交
  • 基本模式

    • 崩溃恢复
      • Leader选举
        • 选举出一个Leader,同时Leader会维护一个Follower列表,客户端可以和这些Follower进行通信
        • 协议并没有规定选举算法,实现中使用的如:Fast Leader Election
      • 数据同步
        • Leader要负责将本身的数据与Follower完成同步,做到多副本存储
        • Follower将队列中未处理完的请求消费完成后,写入本地事务日志中
    • 消息广播
      • Leader可以接受客户端新的事务Proposal,将新的Proposal广播给所有的Follower
  • 关键概念及理解

    • ZAB协议的主备模型
      • 能够很好地处理客户端大量的并发请求
    • 消息广播是基于具有FIFO特性的TCP协议来进行网络通信的
      • 能够很容易的保证消息处理过程中消息收、发的顺序性
    • 换一种说法:Leader与每一个Follower之间都维护了一个单独的FIFO消息队列进行收发消息
      • 消息队列可以做到异步解耦:Leader和Follower之间只需要往队列中发消息即可
    • ZAB协议中Leader等待Follower的ACK反馈消息:只要半数以上的Follower成功反馈即可,不需要收到全部Follower反馈
      • 相比二阶段提交协议,做了简化,提高了性能
  • 崩溃恢复需要保证的两点原则

    • ZAB协议需要确保那些已经在Leader上提交的事务Propocal,最终被所有服务器都提交
    • ZAB协议需要确保在丢弃只在Leader上提出的事务Proposal (而没有被提交)
    • 概括起来说就是:已经被Commit的数据不会丢失,未被Commit的数据要对客户端不可见
  • 崩溃恢复的场景
    - 集群启动
    - 新节点加入
    - Leader重启、宕机
    - Follower重启、宕机

  • Fast Leader Election

    • 关键点
      • 初始投票给自己
        • 广播时会将自己的选票存入投票箱
      • 更新选票
        • 每个节点接收到其他节点的投票时,发生PK时,会根据PK结果更新自己的选票
        • 广播自己的最新选票(同时将自己的投票存入投票箱)
        • 将接收到的选票存入投票箱(覆盖方式)
      • 更新选票优化版
        • 每个节点接收到其他节点的投票时,发生PK时,会根据PK结果更新自己的选票
        • 清空投票箱
        • 广播自己的最新选票(同时将自己的投票存入投票箱)
        • 将接收到的有效选票(与自己最新投票结果一致的选票)存入投票箱
      • 每个节点的投票箱,保存自己和其他节点最新的投票,并且这些投票处于同一轮次,投票箱中的票是用来判断是否已有过半服务器认可了自己的最新投票

ZooKeeper使用场景

服务发现

这是一种常见的情况,即将服务将自身 IP:PORT 注册到 ZK 的某一路径下;客户端获取这一路径下的结点进行连接与访问

分布式锁

  1. 简单分布式互斥锁

获取锁操作为:创建指定的临时结点,当创建成功时代表已经获取锁;当创建失败时,可以设置锁结点的watch(通过读取结点内容设置watch,但结点被删除后触发事件)。获取锁的客户端直接删除锁结点或退出会话时(包括宕机退出)即是释放了锁

但是这个方案在释放锁时会触发结点删除事件并发送给所有等待锁的客户端,但只有一个客户端能够加锁成功,即存在羊群效应

  1. 分布式互斥锁

利用 ZK 的顺序结点机制可以有效避免羊群效应(等待获取锁时只对排在自己前面的最大序号的结点设置watch),伪代码如下:

Lock
1 n = create(l + “/lock-”, EPHEMERAL|SEQUENTIAL)
2 C = getChildren(l, false)
3 if n is lowest znode in C, exit
4 p = znode in C ordered just before n
5 if exists(p, true) wait for watch event
6 goto 2

Unlock
1 delete(n)
  1. 分布式读写锁

采用同样的思路也可以实现读写锁(等待获取写锁以及等待获取读锁时只对排在自己前面的最大序号的写锁结点设置watch),伪代码如下:

Write Lock
1 n = create(l + “/write-”, EPHEMERAL|SEQUENTIAL)
2 C = getChildren(l, false)
3 if n is lowest znode in C, exit
4 p = znode in C ordered just before n
5 if exists(p, true) wait for event
6 goto 2

Read Lock
1 n = create(l + “/read-”, EPHEMERAL|SEQUENTIAL)
2 C = getChildren(l, false)
3 if no write znodes lower than n in C, exit
4 p = write znode in C ordered just before n
5 if exists(p, true) wait for event
6 goto 3
  1. 使用 ZK 实现分布式锁的一些缺点
  • 性能:加解锁是通过结点的创建与删除实现的,而写操作都是由leader执行并同步到所有follower上
  • 并发问题:由于网络问题导致获取锁后客户端与服务端会话断开(并非要释放锁)

分布式leader选举

  • 待补充

    • Fast Paxos算法
  • 问题记录

    • FLE选举,最初Fowller如何确定,需要将自己的选票发送给哪些其他节点?
    • 从广播的角度理解,广播自己的选票,会不会带来广播风暴?

ZooKeeper client API (原生API)

  • 创建会话
    • 会话的建立是异步的过程
    • 当会话真正创建完毕后,ZK server会向会话对应的ZK client发送一个事件通知,因此ZK client只有在获取这个通知后,才算真正建立了会话
    • 判断已建立会话的方法( c api )
      • 通过zoo_state(zh)判断状态值是否为ZOO_CONNECTED_STATE
      • 当client与server会话建立后,触发默认watcher, 接收到type为-1(ZOO_SESSION_EVENT),state为3(ZOO_CONNECTED_STATE)
      • 业务上做判断:即调用zoo_exists()或zoo_get_data()能够返回正确的数据
  • watcher && callback
    • watcher
      • 事件通知,即ZK client可以接收来自ZK server的事件通知并做一些处理(同步处理、异步处理)
      • 不管client/server,注册事件的watcher通知是一次性的,一旦被触发后,该注册就失效了,因此ZK client需要反复注册该事件的watcher通知
      • ZK server发送的仅是一个通知,并不包括节点的变化情况,因此ZK client需要自己重新获取有什么变化
      • client watcher触发(调用watcher callback)是一个串行同步的过程
        • 保证了顺序
        • 不能因为一个watcher的处理逻辑影响了整个客户端的watcher回调
      • default watcher callback( c api )
        • zookeeper_init函数注册default watcher并设置default callback
      • custom watcher callback( c api )
        • zoo_get、zoo_get_children、zoo_exists等函数注册相应watcher并设置其callback
    • callback
      • watcher callback
        • 接收到事件通知后,调用的callback,即有事件通知到来才调用
      • async callback
        • 如调用c api中的异步api,api执行结束后,并且接受到zk server针对此异步api操作的响应时,调用的callback

以下api有同步、异步两个版本,调用异步api需要传入一个回调函数

  • 创建节点
    • 临时结点
      • 如果客户端会话失效,那么这个节点就会被自动清理掉
      • 临时结点只能做为叶子结点
  • 删除节点
  • 读取数据
    • 获取子节点列表
    • 获取节点数据
  • 更新数据
  • 检测节点是否存在
  • 权限控制

ZooKeeper client API (高级API)

  • Session超时重连
  • Watcher重复复注册
  • get/getChildren时,结点被删除后又恢复情况,恢复注册

cpp zookeeper client项目

https://github.com/chenguang9239/CppZooKeeperApi.git

参考

《从Paxos到Zookeeper-分布式一致性原理与实践》
ZooKeeper: Wait-free coordination for Internet-scale systems
ZooKeeper Programmer's Guide
ZooKeeper Internals
FastLeaderElection选主算法流程详解
Zookeeper架构及FastLeaderElection机制
Zookeeper开发常见问题
zookeeper(四)核心watch和watcher
ZooKeeper Watcher 和 AsyncCallback 的区别与实现

posted on 2019-08-10 17:16  chenguang9239  阅读(1262)  评论(1编辑  收藏  举报