乐趣区

关于分布式:深入浅出-ZooKeeper

ZooKeeper 是一个分布式协调服务,由 Apache 进行保护。

ZooKeeper 能够视为一个高可用的文件系统。

ZooKeeper 能够用于公布 / 订阅、负载平衡、命令服务、分布式协调 / 告诉、集群治理、Master 选举、分布式锁和分布式队列等性能。

一、ZooKeeper 简介

1.1 ZooKeeper 是什么

ZooKeeper 是 Apache 的顶级我的项目。ZooKeeper 为分布式应用提供了高效且牢靠的分布式协调服务,提供了诸如对立命名服务、配置管理和分布式锁等分布式的根底服务。在解决分布式数据一致性方面,ZooKeeper 并没有间接采纳 Paxos 算法,而是采纳了名为 ZAB 的一致性协定。

ZooKeeper 次要用来解决分布式集群中利用零碎的一致性问题,它能提供基于相似于文件系统的目录节点树形式的数据存储。然而 ZooKeeper 并不是用来专门存储数据的,它的作用次要是用来 保护和监控存储数据的状态变动。通过监控这些数据状态的变动,从而能够达到基于数据的集群治理。

很多赫赫有名的框架都基于 ZooKeeper 来实现分布式高可用,如:Dubbo、Kafka 等。

1.2 ZooKeeper 的个性

ZooKeeper 具备以下个性:

  • 程序一致性:所有客户端看到的服务端数据模型都是统一的;从一个客户端发动的事务申请,最终都会严格依照其发动程序被利用到 ZooKeeper 中。具体的实现可见下文:原子播送。
  • 原子性:所有事务申请的处理结果在整个集群中所有机器上的利用状况是统一的,即整个集群要么都胜利利用了某个事务,要么都没有利用。实现形式可见下文:事务。
  • 繁多视图:无论客户端连贯的是哪个 Zookeeper 服务器,其看到的服务端数据模型都是统一的。
  • 高性能:ZooKeeper 将数据全量存储在内存中,所以其性能很高。须要留神的是:因为 ZooKeeper 的所有更新和删除都是基于事务的,因而 ZooKeeper 在读多写少的利用场景中有性能体现较好,如果写操作频繁,性能会大大下滑。
  • 高可用:ZooKeeper 的高可用是基于正本机制实现的,此外 ZooKeeper 反对故障复原,可见下文:选举 Leader。

1.3 ZooKeeper 的设计指标

  • 简略的数据模型
  • 能够构建集群
  • 程序拜访
  • 高性能

二、ZooKeeper 外围概念

2.1  数据模型

ZooKeeper 的数据模型是一个树形构造的文件系统。

树中的节点被称为 znode,其中根节点为 /,每个节点上都会保留本人的数据和节点信息。znode 能够用于存储数据,并且有一个与之相关联的 ACL(详情可见 ACL)。ZooKeeper 的设计指标是实现协调服务,而不是真的作为一个文件存储,因而 znode 存储数据的 大小被限度在 1MB 以内。

ZooKeeper 的数据拜访具备原子性。其读写操作都是要么全副胜利,要么全副失败。

znode 通过门路被援用。znode 节点门路必须是绝对路径。

znode 有两种类型:

  • 长期的(EPHEMERAL):客户端会话完结时,ZooKeeper 就会删除长期的 znode。
  • 长久的(PERSISTENT):除非客户端被动执行删除操作,否则 ZooKeeper 不会删除长久的 znode。

2.2  节点信息

znode 上有一个 程序标记(SEQUENTIAL)。如果在创立 znode 时,设置了 程序标记(SEQUENTIAL),那么 ZooKeeper 会应用计数器为 znode 增加一个枯燥递增的数值,即 zxid。ZooKeeper 正是利用 zxid 实现了严格的程序访问控制能力。

每个 znode 节点在存储数据的同时,都会保护一个叫做 Stat 的数据结构,外面存储了对于该节点的全副状态信息。如下:

2.3 集群角色

Zookeeper 集群是一个基于主从复制的高可用集群,每个服务器承当如下三种角色中的一种。

  • Leader:它负责 发动并保护与各 Follwer 及 Observer 间的心跳。所有的写操作必须要通过 Leader 实现再由 Leader 将写操作播送给其它服务器。一个 Zookeeper 集群同一时间只会有一个理论工作的 Leader。
  • Follower:它会响应 Leader 的心跳。Follower 可间接解决并返回客户端的读申请,同时会将写申请转发给 Leader 解决,并且负责在 Leader 解决写申请时对申请进行投票。一个 Zookeeper 集群可能同时存在多个 Follower。
  • Observer:角色与 Follower 相似,然而无投票权。

2.4 ACL

ZooKeeper 采纳 ACL(Access Control Lists)策略来进行权限管制。

每个 znode 创立时都会带有一个 ACL 列表,用于决定谁能够对它执行何种操作。

ACL 依赖于 ZooKeeper 的客户端认证机制。ZooKeeper 提供了以下几种认证形式:

  • digest:用户名和明码 来辨认客户端
  • sasl:通过 kerberos 来辨认客户端
  • ip:通过 IP 来辨认客户端

ZooKeeper 定义了如下五种权限:

  • CREATE:容许创立子节点;
  • READ:容许从节点获取数据并列出其子节点;
  • WRITE:容许为节点设置数据;
  • DELETE:容许删除子节点;
  • ADMIN:容许为节点设置权限。

三、ZooKeeper 工作原理

3.1 读操作

Leader/Follower/Observer 都可间接解决读申请,从本地内存中读取数据并返回给客户端即可。

因为解决读申请不须要服务器之间的交互,Follower/Observer 越多,整体零碎的读申请吞吐量越大,也即读性能越好。

3.2 写操作

所有的写申请实际上都要交给 Leader 解决。Leader 将写申请以事务模式发给所有 Follower 并期待 ACK,一旦收到半数以上 Follower 的 ACK,即认为写操作胜利。

3.2.1 写 Leader

由上图可见,通过 Leader 进行写操作,次要分为五步:

  1. 客户端向 Leader 发动写申请。
  2. Leader 将写申请以事务 Proposal 的模式发给所有 Follower 并期待 ACK。
  3. Follower 收到 Leader 的事务 Proposal 后返回 ACK。
  4. Leader 失去过半数的 ACK(Leader 对本人默认有一个 ACK)后向所有的 Follower 和 Observer 发送 Commmit。
  5. Leader 将处理结果返回给客户端。

留神

  • Leader 不须要失去 Observer 的 ACK,即 Observer 无投票权。
  • Leader 不须要失去所有 Follower 的 ACK,只有收到过半的 ACK 即可,同时 Leader 自身对本人有一个 ACK。上图中有 4 个 Follower,只需其中两个返回 ACK 即可,因为 $$(2+1) / (4+1) > 1/2$$。
  • Observer 尽管无投票权,但仍须同步 Leader 的数据从而在解决读申请时能够返回尽可能新的数据。

3.2.2 写 Follower/Observer

Follower/Observer 均可承受写申请,但不能间接解决,而须要将写申请转发给 Leader 解决。

除了多了一步申请转发,其它流程与间接写 Leader 无任何区别。

3.3 事务

对于来自客户端的每个更新申请,ZooKeeper 具备严格的程序访问控制能力。

为了保障事务的程序一致性,ZooKeeper 采纳了递增的事务 id 号(zxid)来标识事务。

Leader 服务会为每一个 Follower 服务器调配一个独自的队列,而后将事务 Proposal 顺次放入队列中,并依据 FIFO(先进先出) 的策略进行音讯发送。Follower 服务在接管到 Proposal 后,会将其以事务日志的模式写入本地磁盘中,并在写入胜利后反馈给 Leader 一个 Ack 响应。当 Leader 接管到超过半数 Follower 的 Ack 响应后,就会播送一个 Commit 音讯给所有的 Follower 以告诉其进行事务提交,之后 Leader 本身也会实现对事务的提交。而每一个 Follower 则在接管到 Commit 音讯后,实现事务的提交。

所有的提议(proposal)都在被提出的时候加上了 zxid。zxid 是一个 64 位的数字,它的高 32 位是 epoch 用来标识 Leader 关系是否扭转,每次一个 Leader 被选出来,它都会有一个新的 epoch,标识以后属于那个 leader 的统治期间。低 32 位用于递增计数。

具体过程如下:

  • Leader 期待 Server 连贯;
  • Follower 连贯 Leader,将最大的 zxid 发送给 Leader;
  • Leader 依据 Follower 的 zxid 确定同步点;
  • 实现同步后告诉 follower 曾经成为 uptodate 状态;
  • Follower 收到 uptodate 音讯后,又能够从新承受 client 的申请进行服务了。

3.4 察看

客户端注册监听它关怀的 znode,当 znode 状态发生变化(数据变动、子节点增减变动)时,ZooKeeper 服务会告诉客户端。

客户端和服务端放弃连贯个别有两种模式:

  • 客户端向服务端一直轮询
  • 服务端向客户端推送状态

Zookeeper 的抉择是服务端被动推送状态,也就是察看机制(Watch)。

ZooKeeper 的察看机制容许用户在指定节点上针对感兴趣的事件注册监听,当事件产生时,监听器会被触发,并将事件信息推送到客户端。

客户端应用 getData 等接口获取 znode 状态时传入了一个用于解决节点变更的回调,那么服务端就会被动向客户端推送节点的变更:

从这个办法中传入的 Watcher 对象实现了相应的 process 办法,每次对应节点呈现了状态的扭转,WatchManager 都会通过以下的形式调用传入 Watcher 的办法:

Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);
    Set<Watcher> watchers;
    synchronized (this) {watchers = watchTable.remove(path);
    }
    for (Watcher w : watchers) {w.process(e);
    }
    return

Zookeeper 中的所有数据其实都是由一个名为 DataTree 的数据结构治理的,所有的读写数据的申请最终都会扭转这颗树的内容,在收回读申请时可能会传入 Watcher 注册一个回调函数,而写申请就可能会触发相应的回调,由 WatchManager 告诉客户端数据的变动。

告诉机制的实现其实还是比较简单的,通过读申请设置 Watcher 监听事件,写申请在触发事件时就能将告诉发送给指定的客户端。

3.5 会话

ZooKeeper 客户端通过 TCP 长连贯连贯到 ZooKeeper 服务集群。会话 (Session) 从第一次连贯开始就曾经建设,之后通过心跳检测机制来放弃无效的会话状态。通过这个连贯,客户端能够发送申请并接管响应,同时也能够接管到 Watch 事件的告诉。

每个 ZooKeeper 客户端配置中都配置了 ZooKeeper 服务器集群列表。启动时,客户端会遍历列表去尝试建设连贯。如果失败,它会尝试连贯下一个服务器,顺次类推。

一旦一台客户端与一台服务器建设连贯,这台服务器会为这个客户端创立一个新的会话。每个会话都会有一个超时工夫,若服务器在超时工夫内没有收到任何申请,则相应会话被视为过期。一旦会话过期,就无奈再从新关上,且任何与该会话相干的长期 znode 都会被删除。

通常来说,会话应该长期存在,而这须要由客户端来保障。客户端能够通过心跳形式(ping)来放弃会话不过期。

ZooKeeper 的会话具备四个属性:

  • sessionID:会话 ID,惟一标识一个会话,每次客户端创立新的会话时,Zookeeper 都会为其调配一个全局惟一的 sessionID。
  • TimeOut:会话超时工夫,客户端在结构 Zookeeper 实例时,会配置 sessionTimeout 参数用于指定会话的超时工夫,Zookeeper 客户端向服务端发送这个超时工夫后,服务端会依据本人的超时工夫限度最终确定会话的超时工夫。
  • TickTime:下次会话超时工夫点,为了便于 Zookeeper 对会话履行”分桶策略”治理,同时为了高效低耗地实现会话的超时查看与清理,Zookeeper 会为每个会话标记一个下次会话超时工夫点,其值大抵等于以后工夫加上 TimeOut。
  • isClosing:标记一个会话是否曾经被敞开,当服务端检测到会话曾经超时生效时,会将该会话的 isClosing 标记为”已敞开”,这样就能确保不再解决来自该会话的新申请了。

Zookeeper 的会话治理次要是通过 SessionTracker 来负责,其采纳了 分桶策略(将相似的会话放在同一区块中进行治理)进行治理,以便 Zookeeper 对会话进行不同区块的隔离解决以及同一区块的对立解决。

四、ZAB 协定

ZooKeeper 并没有间接采纳 Paxos 算法,而是采纳了名为 ZAB 的一致性协定。ZAB 协定不是 Paxos 算法,只是比拟相似,二者在操作上并不相同。

ZAB 协定是 Zookeeper 专门设计的一种反对解体复原的原子播送协定。

ZAB 协定是 ZooKeeper 的数据一致性和高可用解决方案。

ZAB 协定定义了两个能够 有限循环 的流程:

  • 选举 Leader:用于故障复原,从而保障高可用。
  • 原子播送:用于主从同步,从而保证数据一致性。

4.1 选举 Leader

ZooKeeper 的故障复原

ZooKeeper 集群采纳一主(称为 Leader)多从(称为 Follower)模式,主从节点通过正本机制保证数据统一。

  • 如果 Follower 节点挂了 – ZooKeeper 集群中的每个节点都会独自在内存中保护本身的状态,并且各节点之间都放弃着通信,只有集群中有半数机器可能失常工作,那么整个集群就能够失常提供服务。
  • 如果 Leader 节点挂了 – 如果 Leader 节点挂了,零碎就不能失常工作了。此时,须要通过 ZAB 协定的选举 Leader 机制来进行故障复原。

ZAB 协定的选举 Leader 机制简略来说,就是:基于过半选举机制产生新的 Leader,之后其余机器将从新的 Leader 上同步状态,当有过半机器实现状态同步后,就退出选举 Leader 模式,进入原子播送模式。

4.1.1 术语

myid:每个 Zookeeper 服务器,都须要在数据文件夹下创立一个名为 myid 的文件,该文件蕴含整个 Zookeeper 集群惟一的 ID(整数)。

zxid:相似于 RDBMS 中的事务 ID,用于标识一次更新操作的 Proposal ID。为了保障程序性,该 zkid 必须枯燥递增。因而 Zookeeper 应用一个 64 位的数来示意,高 32 位是 Leader 的 epoch,从 1 开始,每次选出新的 Leader,epoch 加一。低 32 位为该 epoch 内的序号,每次 epoch 变动,都将低 32 位的序号重置。这样保障了 zkid 的全局递增性。

4.1.2 服务器状态

  • LOOKING:不确定 Leader 状态。该状态下的服务器认为以后集群中没有 Leader,会发动 Leader 选举。
  • FOLLOWING:跟随者状态。表明以后服务器角色是 Follower,并且它晓得 Leader 是谁。
  • LEADING:领导者状态。表明以后服务器角色是 Leader,它会保护与 Follower 间的心跳。
  • OBSERVING:观察者状态。表明以后服务器角色是 Observer,与 Folower 惟一的不同在于不参加选举,也不参加集群写操作时的投票。

4.1.3 选票数据结构

每个服务器在进行领导选举时,会发送如下要害信息:

  • logicClock:每个服务器会保护一个自增的整数,名为 logicClock,它示意这是该服务器发动的第多少轮投票。
  • state:以后服务器的状态。
  • self_id:以后服务器的 myid。
  • self_zxid:以后服务器上所保留的数据的最大 zxid。
  • vote_id:被推举的服务器的 myid。
  • vote_zxid:被推举的服务器上所保留的数据的最大 zxid。

4.1.4 投票流程

(1)自增选举轮次

Zookeeper 规定所有无效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对本人保护的 logicClock 进行自增操作。

(2)初始化选票

每个服务器在播送本人的选票前,会将本人的投票箱清空。该投票箱记录了所收到的选票。例:服务器 2 投票给服务器 3,服务器 3 投票给服务器 1,则服务器 1 的投票箱为(2, 3), (3, 1), (1, 1)。票箱中只会记录每一投票者的最初一票,如投票者更新本人的选票,则其它服务器收到该新选票后会在本人票箱中更新该服务器的选票。

(3)发送初始化选票

每个服务器最开始都是通过播送把票投给本人。

(4)接管内部投票

服务器会尝试从其它服务器获取投票,并记入本人的投票箱内。如果无奈获取任何内部投票,则会确认本人是否与集群中其它服务器放弃着无效连贯。如果是,则再次发送本人的投票;如果否,则马上与之建设连贯。

(5)判断选举轮次

收到内部投票后,首先会依据投票信息中所蕴含的 logicClock 来进行不同解决:

  • 内部投票的 logicClock大于 本人的 logicClock。阐明该服务器的选举轮次落后于其它服务器的选举轮次,立刻清空本人的投票箱并将本人的 logicClock 更新为收到的 logicClock,而后再比照本人之前的投票与收到的投票以确定是否须要变更本人的投票,最终再次将本人的投票播送进来。
  • 内部投票的 logicClock小于 本人的 logicClock。以后服务器间接疏忽该投票,持续解决下一个投票。
  • 内部投票的 logickClock 与本人的 相等。过后进行选票 PK。

(6)选票 PK

选票 PK 是基于 (self\_id, self\_zxid) 与(vote\_id, vote\_zxid)的比照:

  • 内部投票的 logicClock大于 本人的 logicClock,则将本人的 logicClock 及本人的选票的 logicClock 变更为收到的 logicClock。
  • logicClock 统一 ,则比照二者的 vote\_zxid,若内部投票的 vote\_zxid 比拟大,则将本人的票中的 vote\_zxid 与 vote\_myid 更新为收到的票中的 vote\_zxid 与 vote\_myid 并播送进来,另外将收到的票及本人更新后的票放入本人的票箱。如果票箱内已存在(self\_myid, self\_zxid) 雷同的选票,则间接笼罩。
  • 若二者 vote_zxid 统一,则比拟二者的 vote\_myid,若内部投票的 vote\_myid 比拟大,则将本人的票中的 vote\_myid 更新为收到的票中的 vote\_myid 并播送进来,另外将收到的票及本人更新后的票放入本人的票箱。

(7)统计选票

如果曾经确定有过半服务器认可了本人的投票(可能是更新后的投票),则终止投票。否则持续接管其它服务器的投票。

(8)更新服务器状态

投票终止后,服务器开始更新本身状态。若过半的票投给了本人,则将本人的服务器状态更新为 LEADING,否则将本人的状态更新为 FOLLOWING。

通过以上流程剖析,咱们不难看出:要使 Leader 取得少数 Server 的反对,则 ZooKeeper 集群节点数必须是奇数。且存活的节点数目不得少于 N + 1

每个 Server 启动后都会反复以上流程。在恢复模式下,如果是刚从解体状态复原的或者刚启动的 server 还会从磁盘快照中复原数据和会话信息,zk 会记录事务日志并定期进行快照,不便在复原时进行状态复原。

4.2 原子播送(Atomic Broadcast)

ZooKeeper 通过正本机制来实现高可用。

那么,ZooKeeper 是如何实现正本机制的呢?答案是:ZAB 协定的原子播送。

ZAB 协定的原子播送要求:

所有的写申请都会被转发给 Leader,Leader 会以原子播送的形式告诉 Follow。当半数以上的 Follow 曾经更新状态长久化后,Leader 才会提交这个更新,而后客户端才会收到一个更新胜利的响应。这有些相似数据库中的两阶段提交协定。

在整个音讯的播送过程中,Leader 服务器会每个事物申请生成对应的 Proposal,并为其调配一个全局惟一的递增的事务 ID(ZXID),之后再对其进行播送。

五、ZooKeeper 利用

ZooKeeper 能够用于公布 / 订阅、负载平衡、命令服务、分布式协调 / 告诉、集群治理、Master 选举、分布式锁和分布式队列等性能。

5.1 命名服务

在分布式系统中,通常须要一个全局惟一的名字,如生成全局惟一的订单号等,ZooKeeper 能够通过程序节点的个性来生成全局惟一 ID,从而能够对分布式系统提供命名服务。

5.2 配置管理

利用 ZooKeeper 的察看机制,能够将其作为一个高可用的配置存储器,容许分布式应用的参与者检索和更新配置文件。

5.3 分布式锁

能够通过 ZooKeeper 的长期节点和 Watcher 机制来实现分布式锁。

举例来说,有一个分布式系统,有三个节点 A、B、C,试图通过 ZooKeeper 获取分布式锁。

(1)拜访 /lock(这个目录门路由程序本人决定),创立 带序列号的长期节点(EPHEMERAL)。

(2)每个节点尝试获取锁时,拿到 /locks 节点下的所有子节点(id\_0000,id\_0001,id_0002),判断本人创立的节点是不是最小的。

  • 如果是,则拿到锁。

    开释锁:执行完操作后,把创立的节点给删掉。

  • 如果不是,则监听比本人要小 1 的节点变动。

(3)开释锁,即删除本人创立的节点。

图中,NodeA 删除本人创立的节点 id_0000,NodeB 监听到变动,发现自己的节点曾经是最小节点,即可获取到锁。

5.4 集群治理

ZooKeeper 还能解决大多数分布式系统中的问题:

  • 如能够通过创立长期节点来建设心跳检测机制。如果分布式系统的某个服务节点宕机了,则其持有的会话会超时,此时该长期节点会被删除,相应的监听事件就会被触发。
  • 分布式系统的每个服务节点还能够将本人的节点状态写入长期节点,从而实现状态报告或节点工作进度汇报。
  • 通过数据的订阅和公布性能,ZooKeeper 还能对分布式系统进行模块的解耦和工作的调度。
  • 通过监听机制,还能对分布式系统的服务节点进行动静高低线,从而实现服务的动静扩容。

5.5 选举 Leader 节点

分布式系统一个重要的模式就是主从模式 (Master/Salves),ZooKeeper 能够用于该模式下的 Matser 选举。能够让所有服务节点去竞争性地创立同一个 ZNode,因为 ZooKeeper 不能有门路雷同的 ZNode,必然只有一个服务节点可能创立胜利,这样该服务节点就能够成为 Master 节点。

5.6 队列治理

ZooKeeper 能够解决两种类型的队列:

  • 当一个队列的成员都聚齐时,这个队列才可用,否则始终期待所有成员达到,这种是同步队列。
  • 队列依照 FIFO 形式进行入队和出队操作,例如实现生产者和消费者模型。

同步队列用 ZooKeeper 实现的实现思路如下:

创立一个父目录 /synchronizing,每个成员都监控标记(Set Watch)位目录 /synchronizing/start 是否存在,而后每个成员都退出这个队列,退出队列的形式就是创立 /synchronizing/member\_i 的长期目录节点,而后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member\_i。判断 i 的值是否曾经是成员的个数,如果小于成员个数期待 /synchronizing/start 的呈现,如果曾经相等就创立 /synchronizing/start。

参考资料

官网

  • ZooKeeper 官网
  • ZooKeeper 官网文档
  • ZooKeeper Github

书籍

  • 《Hadoop 权威指南(第四版)》
  • 《从 Paxos 到 Zookeeper 分布式一致性原理与实际》

文章

  • 分布式服务框架 ZooKeeper — 治理分布式环境中的数据
  • ZooKeeper 的性能以及工作原理
  • ZooKeeper 简介及外围概念
  • 详解分布式协调服务 ZooKeeper
  • 深入浅出 Zookeeper(一)Zookeeper 架构及 FastLeaderElection 机制

作者:ZhangPeng

退出移动版