概述
ZooKeeper 是一个分布式协调服务,其设计初衷是为分布式软件提供一致性服务。ZooKeeper 提供了一个类似 Linux 文件系统的树形结构,ZooKeeper 的每个节点既可以是目录,也可以是数据,同时 ZooKeeper 提供了对每个节点的监控与通知机制。基于 ZooKeeper 的一致性服务,可以方便地实现分布式锁、分布式选举、服务发现和监控、配置中心等功能
ZooKeeper 是一个基于主备复制的高可用集群,ZooKeeper 的角色包括 Leader、Follower、Observer
Leader:一个运行中的 ZooKeeper 集群只有一个 Leader,Leader 主要有两个职责:一是负责集群数据的写操作,二是发起并维护各个 Follower 及 Observer 之间的心跳以监控集群的运行状态。在 ZooKeeper 集群中,所有写操作都必须经过 Leader,只有在 Leader 写操作完成后,才将写操作广播到其他 Follower。只有超过半数的节点(不包括 Observer 节点)写入成功时,该写请求才算写成功
Follower:一个 ZooKeeper 集群可以有多个 Follower,Follower 通过心跳和 Leader 保持连接。Follower 主要有两个职责:一是负责集群数据的读操作,二是参与集群的 Leader 选举。Follower 在接收到一个客户端请求后会先判断该请求是读请求还是写请求,如果是读请求,则 Follower 从本地节点上读取数据并返回给客户端,如果是写请求,则 Follower 将写请求转发给 Leader 处理。同时,在 Leader 失效后,Follower 需要在集群选举时进行投票
Observer:一个 ZooKeeper 集群可以有多个 Observer,Observer 主要负责对集群数据的读操作。Observer 的功能与 Follower 类似主要区别是 Observer 无投票权。ZooKeeper 集群在运行过程中要支持更多的客户端并发操作,就需要增加更多的服务实例,而过多的服务实例会使集群的投票阶段变得复杂,集群选主时间过长,不利于集群故障的快速恢复。因此,ZooKeeper 引入 Observer 角色,Observer 不参与投票,只用于接收客户端的连接并响应客户端的读请求,将写请求转发给 Leader 节点,加入更多的 Observer 节点不仅提高了 ZooKeeper 集群的吞吐量,也保障了系统的稳定性
ZAB 协议
ZAB(ZooKeeper Atomic Broadcast,ZooKeeper 原子消息广播)协议要通过唯一的事务编号 Zxid(ZooKeeper Transaction id)保集群状态的唯一性
- Epoch:指当前集群的周期号(年代号),集群的每次 Leader 变更都会产生一个新的周期号,周期号的产生规则是在上一个周期号的基础上加 1,这样在之前的 Leader 崩溃恢复后会发现自己的周期号比当前的周期号小,说明此时集群已经产生了新的Leader,老的 Leader 会再次以 Follower 的角色加入集群
- Zxid:指 ZAB 协议的事务编号,是一个 64 位的数字,其中低 32 位存储的是一个简单的单调递增的计数器,针对客户端的每一个事务请求,计数器都加 1。高 32 位存储为是 Leader 的周期号 Epoch。在每次选举产生一个新的 Leader 时,该 Leader 都会从当前服务器的日志中取出最大事务的 Zxid,获取其中高 32 位的 Epoch 值并加 1,以此作为新的 Epoch,并将低 32 位从 0 开始重新计数
ZAB 协议有两种模式,分别是恢复模式(集群选主)和广播模式(数据同步)
- 恢复模式:集群在启动、重启或者 Leader 崩溃后,将开始选主,该过程为恢复模式
- 广播模式:Leader 在被选举出来后,会将最新的集群状态广播给其他 Follower,该过程为广播模式。在半数以上的 Follower 完成与 Leader 的状态同步后,广播模式结束
ZAB 协议的四个阶段
- 选举阶段(Leader Election):在集群选举开始时,所有节点都处于选举阶段。当某一个节点的票数超过半数节点后,该节点将被推选为准 Leader。选举阶段的目的就是产生一个准 Leader。只有到达广播阶段(Broadcast)后,准 Leader 才会成为真正的 Leader
- 发现阶段(Discovery):在发现阶段,各个 Follower 开始和准 Leader 通信,同步 Follower 最近接收的事务提议。这时,准 Leader 会产生一个新的 Epoch,并尝试让其他 Follower 接收该 Epoch 后再更新到本地。发现阶段的一个 Follower 只会连接一个 Leader,如果节点 1 认为节点 2 是 Leader,则当节点 1 尝试连接节点 2 时,如果连接被拒绝,则集群会进入重新选举阶段
- 同步阶段(Synchronization):同步阶段主要是将 Leader 在前一阶段获得的最新提议信息同步到集群中所有的副本,只有当半数以上的节点都同步完成时,准 Leader 才会成为真正的 Leader。Follower 只会接收 Zxid 比自己的 lastZxid 大的提议
- 广播阶段(Broadcast):在广播阶段,ZooKeeper 集群开始正式对外提供事务服务,这时 Leader 进行消息广播,将其上的状态通知到其他 Follower,如果后续有新节点加入,则 Leader 会对新节点进行状态同步
ZooKeeper 的选举机制和流程
ZooKeeper 的选举机制被定义为:每个 Server 首先都提议自己是 Leader,并为自己投票,然后将投票结果与其他 Server 的选票进行对比,权重大的胜出,使用权重较大的选票更新自身的选票箱
具体选举过程如下:
- 每个 Server 在启动后询问其他 Server 给谁投票,其他 Server 根据自己的状态回复自己推荐的 Leader 并返回对应的 Leader id和 Zxid。在集群初次启动时,每个 Serve 都会推荐自己为 Leader
- Server 在收到所有其他 Server 的回复后,会计算 Zxid 最大的 Server,并将该 Server 设置成下一次要投票推荐的 Server
- 在计算过程中,票数最多的 Server 将成为获胜者,如果获胜者的票数超过集群个数的一半,则该 Server 将被推选为 Leader。否则,继续投票,直到 Leader 被选举出来
- Leader 等待其他 Server 连接
- Follower 连接 Leader,将最大的 Zxid 发送给 Leader
- Leader 根据 Follower 的 Zxid 确定同步点,至此,选举阶段完成
Zookeeper 的数据模型
ZooKeeper 使用一个树形结构的命名空间来表示其数据结构,类似文件系统的目录树。ZooKeeper 树中的每个节点都被称为一个 Znode,ZooKeeper 树中的每个节点都可以拥有子节点,ZooKeeper 的每个节点都存储数据信息,同时提供对节点信息的监控操作
Znode 由三个部分组成:
- Stat:状态信息,用于存储该 Znode 的版本、权限、时间戳等信息
- Data:Znode 具体存储的数据
- Children:Znode 子节点的信息描述
Znode 节点虽然可以存储数据,但它并不像数据库那样能存储大量的数据。设计 Znode 的初衷是存储分布式应用中的配置文件、集群状态等元数据信息
Znode 的控制访问:
- ACL:每一个 Znode 节点都拥有一个访问控制列表(Access Control List,ACL),该列表规定了用户对节点的访问权限,应用程序可以根据需求将用户分为只读、只写和读写用户
- 原子操作:每一个 Znode 节点上的数据都具有原子操作的特性,读操作将获取与节点相关的数据,写操作将替换节点上的数据
ZooKeeper 的节点有两种,分别是临时节点和永久节点,节点的类型在创建时被确定并且不能改变:
- 临时节点:临时节点的生命周期取决于过期时间,在临时节点过期后系统会自动删除该节点,临时节点不允许拥有子节点
- 永久节点:永久节点的数据会一直被存储,直到用户调用接口将其数据删除,一般用于存储一些永久性的配置信息
Znode 的节点监控:在ZooKeeper 的每个节点上都有一个 Watch 用于监控节点数据的变化,当节点状态发生改变时会触发 Watch 所对应的操作。在 Watch 被触发时 ZooKeeper 会向监控该节点的客户端发送一条通知,说明节点的变化情况
Zookeeper 应用场景
- 统一命名服务:在分布式环境下,应用程序经常需要对服务进行统一命名,以便识别不同的服务和快速获取服务列表,应用程序可以将服务名称和服务地址信息维护在 ZooKeeper 上,客户端通过 ZooKeeper 获取可用服务列表
- 配置管理:在分布式环境下,应用程序可以将配置文件统一在 ZooKeeper 管理。配置信息可以按照系统配置、告警配置、业务开关配置、业务值配置等分类存储在不同的 Znode 上,各个服务在启动时从 ZooKeeper 读取配置,同时监听各个节点的 Znode,一旦 Znode 中的配置被修改,Zookeeper 就将通知各个服务更新配置
- 集群管理:在分布式环境下,实时管理每个服务的状态是 ZooKeeper 使用最广泛的场景
- 分布式通知协调:基于 Znode 的临时节点和 Watch 特性,应用程序可以很容易地实现一个分布式通知协调系统。比如在集群中为每个服务都创建一个周期为 30s 的临时节点作为服务状态监控,要求各个服务每 10s 定时向 ZooKeeper 汇报监控状态。当 ZooKeeper 连续 30s 未收到服务的状态反馈时,则可以认为该服务异常,将其从服务列表中移除,同时将该结果通知到监控该节点状态的服务
- 分布式锁:由于 ZooKeeper 是强一致性的,所以在多个客户端同时在 ZooKeeper 创建相同的 Znode 时,只能有一个创建成功。基于该机制,应用程序可以实现锁的独占性,当多个客户端同时在 ZooKeeper 创建相同的 Znode 时,创建成功的那个客户端将得到锁,其他客户端则等待