小白必须知道的 ZooKeeper 知识

一、ZooKeeper 是什么呢?

对于一些富含经验的技术大佬来说 ZooKeeper 都是耳熟能详;但对于广大的应届生或者分布式微服务的初学者,可能是刚听说不久,但是知道笔试或者面试经常也会有它的身影存在!

如果您已经了解了基础,只想知道如何简单搭建一个集群,可以直接阅读该文章: zookeeper 集群搭建

那么 ZooKeeper 是什么呢?

ZooKeeper 最初由雅虎公司开发,并于 2010 年成为 Apache 软件基金会的顶级项目。它的设计灵感来自于 Google 的 Chubby 分布式锁服务。雅虎在构建大规模分布式系统时遇到了一些挑战,包括配置管理、命名服务和协调问题。为了解决这些问题,他们开发了 ZooKeeper。随着时间的推移,ZooKeeper 在开源社区中获得了广泛的认可和采用,并成为了一个重要的 分布式协调服务

ZooKeeper 使用树形结构来组织数据,并通过保持副本在多个服务器上来实现高可用性。它被广泛应用于分布式系统中,如 Apache HadoopApache Kafka 等。


二、ZooKeeper 的特点

特点 说明
一致性 客户端无论连接到集群中的哪个节点,读到的数据都是一样的
实时性 ZooKeeper 保证客户端在一定的时间间隔内获得结果,包括成功和失败,但是由于网络延迟原因,Zookeeper 不能保证两台客户端同时得到刚更新的消息。如果都需要最新的消息需要调用sync()接口。
原子性 leader在同步数据的时候,同步过程保证事务性,要么都成功,要么都失败。
顺序性 一台服务器上如果消息a在消息b前发布,那么所有的server上的消息a都是在消息b前发布的。

三、ZooKeeper 的一些重要概念

1. Session 会话

Session 是指 ZooKeeper 服务器与客服端建立的会话。

ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接;客户端在启动的时候就会和服务器端建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也就开始了。

客户端会定期通过该长连接向服务器端发送心跳包,以确保和服务器端保持有效的会话;同时客户端也是通过该会话连接和服务器端通讯发起请求并接收响应,也是通过此来接收服务器的 Watch 事件通知。

sessionTimeout 配置是用来设置一个客户端会话的超时时间;当服务器压力过大、网络故障或者是客户端主动断开连接等各种原因导致客户端会话连接断开时,只要在 sessionTimeout 规定的时间范围内客户端能重新连接到集群的任何一台服务器,那么之前创建的会话还算是有效会话!

在为客户端创建会话之前,服务端都会给每个客户端分配一个 sessionID,该标志是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论哪台服务器为客户端分配的 sessionID, 都务必保证全局唯一。

2. Znode 节点

常规来说,节点 指代的是集群中的每一台机器。但是在 ZooKeeper 中,节点 分为两类:

  • 机器节点,即构成集群的每一台机器
  • 数据节点 Znode,指数据模型中的数据单元

ZooKeeper 的数据结构,跟 Unix 文件系统非常类似,可以看做是一颗树,每个节点叫做 Znode。每一个节点可以通过路径来标识 (如图)
小白必须知道的 ZooKeeper 知识_第1张图片

Znode 其实也分为两种类型

  • Ephemeral 短暂/临时:当客户端和服务端断开连接后,所创建的 Znode 会自动删除。
  • Persistent 持久:当客户端和服务端断开连接后,所创建的 Znode 不会删除。

下面简单讲下什么时候使用临时节点、什么时候使用持久节点

  • 假设你正在构建一个在线多人游戏服务器。每个玩家在游戏开始时都会创建一个 临时 Znode 来表示他们的在线状态。当玩家断开连接时,与其相关联的 临时 Znode 将被自动删除。这样,其他玩家可以通过监视 ZooKeeper 上的 临时 Znode 来知道哪些玩家当前在线。
  • 假设你正在构建一个分布式配置管理系统。你可以使用 持久 Znode 来存储各种配置信息,例如数据库连接字符串、API 密钥等。这些 持久Znode 将一直存在,即使某个节点发生故障或重启,配置信息也不会丢失。其他节点可以通过监视 ZooKeeper 上的 持久Znode 来获取最新的配置信息。

3. 版本

ZooKeeper 的每个 Znode 上都会存储数据,对应于每个 ZnodeZooKeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 Znode 的三个数据版本,分别是:

  1. version : 当前 Znode 的版本
  2. cversion : 当前 Znode 子节点的版本
  3. aversion : 当前 ZnodeACL 版本

扩展 : ZooKeeper Stat Structure

字段 说明
czxid The zxid of the change that caused this znode to be created.
mzxid The zxid of the change that last modified this znode.
ctime The time in milliseconds from epoch when this znode was created.
mtime The time in milliseconds from epoch when this znode was last modified.
version The number of changes to the data of this znode.
cversion The number of changes to the children of this znode.
aversion The number of changes to the ACL of this znode.
ephemeralOwner The session id of the owner of this znode if the znode is an ephemeral node. If it is not an ephemeral node, it will be zero.
dataLength The length of the data field of this znode.
numChildren The number of children of this znode.

4. Watcher

4.1 定义

Watcher(事件监听器),是 ZooKeeper 实现分布式协调服务的重要特性。

ZooKeeper’s definition of a watch: a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes.

ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去。

ZooKeeper 中的所有读取操作 - getData()getChildren()Exists() - 都可以选择设置监视器。

4.2 常用的监听场景

  1. 监听 Znode 节点的数据变化
  2. 监听子节点的增减变化

4.3 三个关键关注点

1️⃣ One-time trigger (一次性触发)

数据变更的时候,会往客户端推送一个监听事件。

举个例子,如果客户端设置了 getData("/Java/mq", true) ,而之后 /Java/mq 内容变更了或者该节点删除了,客户端就会收到服务器端推送的 /Java/mq 对应的监听事件。

但是如果 /Java/mq 之后又产生了变更行为,除非客户端重新设置了读取并监听该节点数据事件,否则不会再次推送对应的监听事件给到客户端!

2️⃣ Sent to the client (正在发送给客户端)

当你在 ZooKeeper 中设置一个 watch 时,它表示有一个事件即将发送到客户端。然而,在成功返回更改操作的代码给发起更改的客户端之前,该事件可能尚未到达客户端。

Watches 是++异步发送给观察者++的。ZooKeeper 提供了一个顺序保证:客户端在首次看到 watch 事件之前,不会看到其设置了 watchznode 更改内容。

网络延迟或其他因素可能导致不同的客户端在不同的时间看到 watch 和更新的返回代码。++关键点在于,不同客户端所看到的一切都具有一致的顺序++。

3️⃣ The data for which the watch was set (监视特定的数据变化)

ZooKeeper 中,节点的变化可以包括 ++数据的变化 (data watch)++ 和 ++子节点的变化 (child watch)++。

可以使用 getData()exists() 方法设置数据观察,而使用 getChildren() 方法设置子节点观察。

也可以根据返回的数据类型来设置观察,getData()exists() 返回有关节点数据的信息,而 getChildren() 返回子节点列表。

因此假设有一个节点是 /Java,它的子节点是 /Java/mq。根据之前提到的规则:

  • 如果使用 setData() 成功设置了 /Java 节点的数据,那么将触发对该节点的数据观察。
  • 如果使用 create() 成功创建了 /Java/mq 节点,那么将触发对被创建节点 /Java/mq 的数据观察以及对父节点 /Java 的子节点观察。
  • 如果使用 delete() 成功删除了 /Java/mq 节点,那么将触发对被删除节点 /Java/mq 的数据观察和子节点观察(因为没有更多子节点可用),以及对父节点 /Java 的子节点观察。

5. ACL

ZooKeeper 采用 ACL( AccessControlLists )策略来进行权限控制,类似于 UNIX 文件系统的权限控制,与标准 UNIX 权限不同,ZooKeeper 节点不受用户(文件所有者)、组和全局(其他)的三个标准范围的限制。ZooKeeper 没有 Znode 的所有者概念。

5.1 ACL 的 5 种权限

权限 说明
CREATE 创建子节点的权限
READ 获取节点数据和子节点列表的权限
WRITE 更新节点数据的权限
DELETE 删除子节点的权限
ADMIN 设置节点 ACL 的权限

ZooKeeper 不支持 LOOKUP 权限(目录上的执行权限位,允许您进行 LOOKUP,即使您无法列出目录)。每个人都隐式具有 LOOKUP 权限。这允许您对节点进行状态查询,但不能进行其他操作。(问题是,如果您想在不存在的节点上调用 zoo_exists(),则没有要检查的权限。)

5.2 内置的 ACL Schemes

scheme 说明
world 即 anyone,即所有用户都具有该权限。这意味着无论是谁,都可以对节点执行相应的操作,例如读取数据、设置数据、创建子节点或删除子节点。
auth 不使用任何 id,表示任何经过身份验证的用户
digest 使用 用户名:密码 字符串生成 MD5 哈希,然后将其用作 ACL ID 标识。身份验证是通过以明文形式发送 用户名:密码 来完成的。在 ACL 中使用时,表达式将是 用户名:base64编码的SHA1密码 摘要。
host 使用客户端主机名作为 ACL ID 标识。ACL 表达式是主机名后缀。例如,ACL 表达式 host:corp.com 匹配 ids host:host1.corp.comhost:host2.corp.com,但不匹配 host:host1.store.com
ip 使用客户端主机 IP 作为 ACL ID 标识。ACL 表达式的格式为 addr/bits,其中 addr 的最高有效位与客户端主机 IP 的最高有效位进行匹配。假设您希望允许具有 IP 地址为 192.168.0.0/24 的客户端访问某个节点。在 ACL 中,您可以设置 ip:192.168.0.0/24 来表示这个权限。

5.3 鉴权说明

ZooKeeper 支持可插拔的身份验证方案。使用 scheme:id 的形式指定 ids,其中 schemeid 对应的身份验证方案。例如,host:host1.corp.com 是一个名为 host1.corp.com 的主机的 id

当客户端连接到 ZooKeeper 并进行身份验证时,ZooKeeper 将与客户端关联的所有 id 与客户端的连接关联起来。当客户端尝试访问节点时,这些 id 将与 ZnodeACL 进行检查。ACL 由( scheme:expression, perms )对组成。表达式的格式特定于方案。例如,( ip:19.22.0.0/16, READ )这对给具有以 19.22 开头的 IP 地址的任何客户端赋予了读取权限。


四、ZooKeeper 集群

ZooKeeper 集群是由多个 ZooKeeper 服务器组成的分布式系统。每个服务器都是一个独立的实例,它们协同工作以提供高可用性和容错性。

相对于单个 ZooKeeper 实例的 Standalone Mode (独立运行模式), 集群使用的是 Quorum Mode (仲裁模式)。

小白必须知道的 ZooKeeper 知识_第2张图片

1. ZooKeeper 集群的三个角色

角色 职责
Leader 领导者,负责进行投票的发起和决议,更新系统状态,Leader 是由选举产生
Follower 跟随者,用于接受客户端请求并向客户端返回结果,在选主过程中参与投票
Observer 观察者,可以接受客户端连接,接受读写请求,写请求转发给 Leader,但 Observer 不参加投票过程,只同步 Leader 的状态,Observer 的目的是为了扩展系统,提高读取速度

2. ZooKeeper 集群服务器状态

ZooKeeper 集群服务器有以下 4 种状态:

状态 说明
LOOKING 寻找 Leader 状态,当服务器处于该状态时,表示当前集群没有 Leader,因此会进入 Leader 选举状态。
FOLLOWING 跟随者状态,表示当前服务器角色是 Follower
LEADING 领导者状态,表示当前服务器角色是 Leader
OBSERVING 观察者状态,表示当前服务器角色是 Observer

3. ZooKeeper 的选举机制

3.1 三种选举方式

方式 说明
LeaderElection 领导者选举,这是最基本的选举方式,它使用了 ZooKeeper 中的++原子广播协议++。在 LeaderElection 中,每个节点都可以成为候选人,并通过与其他节点进行通信来达成共识,选择一个节点作为领导者。
AuthFastLeaderElection 认证快速领导者选举, 这是一种改进的选举方式,用于提高 ZooKeeper 集群的安全性。AuthFastLeaderElectionLeaderElection 的基础上添加了身份验证和加密功能,以确保只有经过身份验证的节点才能参与选举过程。
FastLeaderElection 快速领导者选举, 这是 ZooKeeper 中最新的默认选举方式。FastLeaderElection 使用了一种称为 Fast Paxos 的一致性算法,它具有更高的性能和更低的延迟。该算法通过减少消息传递次数和选举轮次来提高选举效率。

3.2 触发选举的场景

  1. 集群在启动初始化的时候会发生选举
  2. 集群的 Leader 失联的时候会发生选举

3.3 选举的前提条件

  1. ZooKeeper 服务器处于 LOOKING 竞选状态 (注意观察者状态不能参与竞选投票
  2. ZooKeeper 的集群规模至少要有 3 台机器或以上

❓ 为啥集群的规模至少要 3 台机器呢?

集群规则为2N + 1 台(避免脑裂),N > 0,即最少需要 3 台。

因为 ZooKeeper 集群的机制是++只有超过半数的节点正常,集群才能正常提供服务++。只有在 ZooKeeper 节点挂得太多,只剩一半或不到一半节点能工作时,集群才会失效。

简单用例子代入理解:

  • 3 个节点的 Cluster 可以挂掉 1 个节点(Leader 可以得到 2 票 > 1.5)
  • 2 个节点的 Cluster 就不能挂掉任何 1 个节点了(Leader 可以得到 1 票 <= 1)

3.4 集群的初始选举

以三个节点的集群举例子:

server myid zxid 运行状态
zk1 1 0 ✔️
zk2 2 0 ✔️
zk3 3 0 ✔️
  • 其中 zxidZooKeeper 事务 ID 的缩写,每个事务都会被分配一个唯一的 zxid 值,它由两部分组成:高 32位 表示 LeaderEpoch(领导者轮次),低 32位 表示事务计数器。zxid 的作用是确保 ZooKeeper 中的所有事务操作都具有全局唯一的标识,以实现数据的一致性和顺序性。
  • myid 是一个用于标识 ZooKeeper 集群中每个节点的唯一整数。每个节点都需要在其配置文件中指定一个 myid 值,该值范围从 1~255myid 的作用是帮助 ZooKeeper 识别每个节点,并为其分配相应的角色(如领导者、跟随者或观察者)。通过 myidZooKeeper 可以维护集群中各个节点的状态和角色信息。

依次启动 3 台服务器 ,初始情况下事务 ID (zxid) 都为 0。

1️⃣ 初始投票

服务启动后,每个 Server 都会默认给自己投上一票,每次投票都会包含 myidzxid,为方便表示,后续统一使用 Server(myid, zxid) 的格式来体现!

此时投票的数据依次为: zk1(1, 0)zk2(2, 0)zk3(3, 0)

2️⃣ 同步投票结果

集群中的服务器在投票后,会将各自的投票结果同步给集群中其他服务器。

3️⃣ 检查投票有效性

各服务器在收到投票后会检查投票的有效性,如:是否本轮投票,是否来自 LOOKING 状态的服务器的投票等。

4️⃣ 处理投票

服务器之间会进行投票比对,规则如下:

  1. 优先检查 zxid,较大的服务器优先作为 Leader
  2. 如果 zxid 相同,则 myid 较大的服务器作为 Leader

比如依次先启动 zk1 -> zk2 ,那么此时的投票数据为 zk1(1, 0)zk2(2, 0)

server myid zxid 运行状态 票数 服务状态
zk1 1 0 ✔️ 1 Looking
zk2 2 0 ✔️ 1 Looking
zk3 3 0 ✖️ ️ 0

由于 zxid 都相同,所以 zk2 的投票胜出,此时 zk1 需要更新自己的投票为 zk1(2, 0)

server myid zxid 运行状态 票数 服务状态
zk1 1 0 ✔️ 0 FOLLOWING
zk2 2 0 ✔️ 2 LEADING
zk3 3 0 ✖️ ️ 0

5️⃣ 统计投票结果

每轮投票比对之后都会统计投票结果,确认是否有超过半数的机器都得到相同的投票结果,如果是,则选出 Leader,否则继续投票。

第四步提到的选举中,由于 zk1zk2 已经都得到相同的投票结果 (2,0) , 其中 2 代表了 zk2 ,可见投票结果 2 > (3/2) 超过了半数机器了,所以此时 zk2 其实已经成为本轮选举的 Leader 了。

所以最后启动的 zk3 识别到集群中已经有 Leader 了,就不再参与选举了,选举也就结束了,后面进来的只能是小弟!

server myid zxid 运行状态 票数 服务状态
zk1 1 0 ✔️ 0 FOLLOWING
zk2 2 0 ✔️ 2 LEADING
zk3 3 0 ✔️ ️ 0 FOLLOWING

6️⃣ 更改服务器状态

一旦选出 Leader,每个服务器就会各自更新自己的状态:

zk1 >>> FOLLOWING
zk2 >>> LEADING
Zk3 >>> FOLLOWING

️️ 从上面的例子我们可简以推导出这样的规律:

  • 集群有 3 台机器,第 2 大的 myid 所在服务器就是 Leader;
  • 集群有 4 台机器,第 3 大的 myid 所在服务器就是 Leader;
  • 集群有 5 台机器,第 3 大的 myid 所在服务器就是 Leader;
  • 集群有 6 台机器,第 4 大的 myid 所在服务器就是 Leader;

3.5 集群的重新选举

服务器的运行不可能一帆风顺,随时可能因为网络故障或者机器故障等原因离线,假如刚好挂的是 Leader 节点的情况下,集群就会产生新一轮的选举,此处还是使用上面的 3 台机器例子来做说明 (假设运行了一段时间后 zk2 挂掉了)。

server myid zxid 运行状态 票数 服务状态
zk1 1 66 ✔️ 0 FOLLOWING
zk2 2 42 ✖️ 2 LEADING
zk3 3 28 ✔️ ️ 0 FOLLOWING

1️⃣ 状态变更

如上图所示,此时 zk2 由于异常已经离线了,目前集群已经不存在 Leader 节点,其他的节点会从 FOLLOWING 变更成 LOOKING,从而开启新一轮的 Leader 选举。

server myid zxid 运行状态 票数 服务状态
zk1 1 66 ✔️ 0 LOOKING
zk2 2 42 ✖️ 2 LEADING
zk3 3 28 ✔️ ️ 0 LOOKING

2️⃣ 开始投票

投票逻辑和启动初始时一致。

此时 zk1zk3 会先投给自己一票,zk2 离线已不能参与投票,新一轮的投票为 zk1(1, 66)zk3(3, 28)

3️⃣ 同步投票结果

同步投票逻辑和启动初始时一致。

4️⃣ 检查投票有效性

检查投票逻辑和启动初始时一致。

5️⃣ 处理投票

处理投票逻辑和启动初始时一致。

此时,zk1zk3 票数一致,先对比 zxidzk1 胜出,因此 zk3 需要更新自己的投票结果为 zk3(1, 28)

6️⃣ 统计投票结果

统计投票逻辑和启动初始时一致。

本轮选举中,zk1zk3 都得到了相同的投票结果 zk1,并且超过了半数的机器 2 > (3 / 2),所以此时 zk1 就成为了本轮选举的 Leader

server myid zxid 运行状态 票数 服务状态
zk1 1 66 ✔️ 0 LEADING
zk2 2 42 ✖️ 2
zk3 3 28 ✔️ ️ 0 FOLLOWING

7️⃣ 更改服务器状态

zk1 >>> LEADING
Zk3 >>> FOLLOWING

4. ZooKeeper 集群的简易搭建

zookeeper 集群搭建

你可能感兴趣的:(k8s的探索之路,中间件,分布式,zookeeper,分布式,中间件)