背景
一切的恐惧,且来源于火力不足
假如有人问题如下问题,你能回答上来吗?如果你能回答上来,那么你可以跳过本文。如何回答不了,本文将给你答案。
- 按顺序启动Zookeeper集群,Leader会是哪个结点?
- ZooKeeper的应用场景有哪些?
- ZooKeeper如何保证数据的一致性?
- ZooKeeper的节点有哪些类型?
- ZooKeeper的数据模型是什么?
架构图
集群模式详解
Zookeeper概述
客户端连接到单个ZooKeeper服务器。客户端维护一个TCP连接,通过该连接发送请求、获取响应、获取监视事件和发送检测信号。如果与服务器的TCP连接中断,客户端将连接到其他服务器。
订购了ZooKeeper。ZooKeeper在每次更新时都会使用一个数字来标记,该数字反映了所有ZooKeeper事务的顺序。后续操作可以使用该顺序来实现更高级别的抽象,例如同步基元。
ZooKeeper速度很快。在“读取主导”工作负载中,它的速度尤其快。ZooKeeper应用程序在数千台机器上运行,在读取比写入更常见的情况下,它的性能最佳,比率约为10:1。
数据模型和分层命名空间
ZooKeeper 提供的命名空间与标准文件系统的命名空间非常相似。名称是一系列由斜杠 (/) 分隔的路径元素。ZooKeeper 命名空间中的每个节点都由路径标识。
永久节点和临时节点
Znodes 维护一个统计信息结构,其中包括数据更改、ACL 更改和时间戳的版本号,以允许缓存验证和协调更新。每当 znode 的数据发生变化时,版本号就会增加。例如,每当客户端检索数据时,它也会接收数据的版本。
存储在命名空间中每个 znode 的数据都是以原子方式读取和写入的。读取获取与 znode 关联的所有数据字节,写入替换所有数据。每个节点都有一个访问控制列表 (ACL),用于限制谁可以执行哪些操作。
ZooKeeper 也有临时节点的概念。只要创建 znode 的会话处于活动状态,这些 znode 就存在。会话结束时,znode 将被删除。
条件更新和监视
ZooKeeper 支持watches的概念。客户端可以在 znode 上设置监视。当 znode 发生变化时,将触发并删除监视。当监视被触发时,客户端会收到一个数据包,指出 znode 已更改。如果客户端与其中一个 ZooKeeper 服务器之间的连接断开,客户端将收到本地通知。
保证
ZooKeeper非常快速且非常简单。但是,由于它的目标是成为构建更复杂服务(例如同步)的基础,因此它提供了一组保证。这些是:
- 顺序一致性-来自客户端的更新将按发送顺序应用。
- 原子性-更新成功或失败。没有部分结果。
- 单个系统映像-无论客户端连接到哪个服务器,它都将看到相同的服务视图。也就是说,即使客户* 端故障转移到具有相同会话的其他服务器,客户端也永远不会看到系统的旧视图。
- 可靠性-应用更新后,它将从那时起一直存在,直到客户端覆盖更新。
- 及时性-保证在一定时间范围内,客户端对系统的看法是最新的。
简单的API
ZooKeeper的设计目标之一是提供一个非常简单的编程接口。因此,它仅支持以下操作:
创建:在树中的某个位置创建一个节点delete:删除节点exists:测试某个位置是否存在节点获取数据:从节点读取数据setdata:将数据写入节点GetChildren:检索节点的子节点列表sync:等待数据传播
Zookeeper集群
跨计算机要求
要使 ZooKeeper 服务处于活动状态,必须有大多数可以相互通信的非故障计算机。对于具有 N 台服务器的 ZooKeeper 集成,如果 N 为奇数,则集成能够容忍多达 N/2 个服务器故障,而不会丢失任何 znode 数据;如果 N 为偶数,则集成最多能够容忍 N/2-1 个服务器故障。
领导人选举
ZooKeeper 进行 leader 选举使用一种简单方法是使用 SEQUENCE|创建代表客户“建议”的 znode 时的临时标志。这个想法是有一个 znode,比如 “/election”,这样每个 znode 创建一个子 znode “/election/guid-n_”,两个标志都是 SEQUENCE|短暂的。使用序列标志,ZooKeeper 会自动附加一个序列号,该序列号大于之前附加到“/election”子项的序列号。创建具有最小附加序列号的 znode 的进程是领导者。
群集选项
本节中的选项设计用于服务器集合,即在部署服务器群集时。
electionAlg :(无 Java 系统属性)要使用的选举实现。值“1”对应于未经身份验证的基于 UDP 的快速领导者选举版本,“2”对应于经过身份验证的基于 UDP 的快速领导者选举版本,“3”对应于基于 TCP 的快速领导者选举版本。算法 3 在 3.2.0 中是默认的,以前的版本(3.0.0 和 3.1.0)也使用算法 1 和 2。
注意
领导者选举 1 和 2 的实现在 3.4.0 中已弃用。由于 3.6.0 只有 FastLeaderElection 可用,因此在升级时,您必须关闭所有服务器并使用 electionAlg=3 重新启动它们(或从配置文件中删除该行)。>maxTimeToWaitForEpoch : (Java 系统属性: zookeeper.leader.maxTimeToWaitForEpoch) 3.6.0 中的新功能:激活领导者时等待投票者 epoch 的最长时间。如果领导者收到来自其投票者之一的 LOOKING 通知,并且它没有收到来自 maxTimeToWaitForEpoch 内多数人的纪元数据包,则它将转到 LOOKING 并再次选举领导者。这可以进行调整以减少仲裁或服务器不可用时间,它可以设置为比 initLimit * tickTime 小得多。在跨数据中心环境中,可以将其设置为类似 2 秒的值。
initLimit :(无 Java 系统属性)允许追随者连接并同步到领导者的时间量,以刻度为单位(参见 tickTime)。如果 ZooKeeper 管理的数据量很大,则根据需要增加此值。connectToLearnerMasterLimit :(Java 系统属性:zookeeper。connectToLearnerMasterLimit) 允许关注者在领导者选举后连接到领导者的时间量(以刻度为单位)(参见 tickTime)。默认值为 initLimit。当 initLimit 较高时使用,因此连接到学习器主节点不会导致更高的超时。
leaderServe :(Java 系统属性:zookeeper。leaderServes) Leader 接受客户端连接。默认值为“yes”。主计算机协调更新。为了以略有读取吞吐量为代价获得更高的更新吞吐量,可以将领导者配置为不接受客户端并专注于协调。此选项的默认值为 yes,这意味着领导者将接受客户端连接。
注意
当一个集合中有三个以上的 ZooKeeper 服务器时,强烈建议打开领导者选择。server.x=[hostname]:nnnnn[:nnnnn] etc : (无 Java 系统属性)构成 ZooKeeper 集合的服务器。当服务器启动时,它通过在数据目录中查找文件 myid 来确定它是哪个服务器。该文件包含 ASCII 格式的服务器编号,并且应与此设置左侧的 server.x 中的 x 匹配。客户端使用的构成 ZooKeeper 服务器的服务器列表必须与每个 ZooKeeper 服务器拥有的 ZooKeeper 服务器列表匹配。有两个端口号 nnnnn。第一个追随者用于与领导者建立联系,第二个追随者用于领导者选举。如果要在一台计算机上测试多个服务器,则可以对每个服务器使用不同的端口。
从 ZooKeeper 3.6.0 开始,可以为每个 ZooKeeper 服务器指定多个地址(参见 ZOOKEEPER-3188)。要启用此功能,必须将 multiAddress.enabled 配置属性设置为 true。这有助于提高可用性,并为 ZooKeeper 增加网络级别的弹性。当服务器使用多个物理网络接口时,ZooKeeper 能够在所有接口上绑定,并在发生网络错误时运行时切换到工作接口。可以在配置中使用竖线 (‘|’) 字符指定不同的地址。使用多个地址的有效配置如下所示:
server.1=zoo1-net1:2888:3888|zoo1-net2:2889:3889server.2=zoo2-net1:2888:3888|zoo2-net2:2889:3889server.3=zoo3-net1:2888:3888|zoo3-net2:2889:3889
注意
启用此功能后,仲裁协议(ZooKeeper Server-Server 协议)将发生变化。用户不会注意到这一点,当任何人使用新配置启动 ZooKeeper 集群时,一切都会正常工作。但是,如果旧的 ZooKeeper 群集不支持 multiAddress 功能(和新的仲裁协议),则无法在滚动升级期间启用此功能并指定多个地址。如果您需要此功能,但还需要从低于 3.6.0 的 ZooKeeper 集群执行滚动升级,则首先需要在不启用 MultiAddress 功能的情况下进行滚动升级,然后使用新配置进行单独的滚动重启,其中 multiAddress.enabled 设置为 true 并提供多个地址。
syncLimit :(无 Java 系统属性)允许关注者与 ZooKeeper 同步的时间量,以刻度为单位(参见 tickTime)。如果追随者落后于领导者太远,他们就会被放弃。
group.x=nnnnn[:nnnnn] : (无 Java 系统属性)启用分层仲裁构造。x“是组标识符,”=“符号后面的数字对应于服务器标识符。分配的左侧是以冒号分隔的服务器标识符列表。请注意,组必须是不相交的,并且所有组的并集必须是 ZooKeeper 集合。你可以在这里找到一个例子weight.x=nnnnn :(无 Java 系统属性)与“group”一起使用,在形成仲裁时为服务器分配权重。这样的值对应于投票时服务器的权重。ZooKeeper 有几个部分需要投票,例如领导者选举和原子广播协议。默认情况下,服务器的权重为 1。如果配置定义了组,但未定义权重,则值 1 将分配给所有服务器。你可以在这里找到一个例子
cnxTimeout :(Java 系统属性:zookeeper。cnxTimeout) 设置为领导者选举通知打开连接的超时值。仅当您使用 electionAlg 3 时才适用。默认值为 5 秒。
quorumCnxnTimeoutMs :(Java 系统属性:zookeeper。quorumCnxnTimeoutMs) 设置领导者选举通知的连接的读取超时值。仅当您使用 electionAlg 3 时才适用。默认值为 -1,然后使用 syncLimit * tickTime 作为超时。
standaloneEnabled :(无 Java 系统属性) 3.5.0 中的新增功能:设置为 false 时,可以在复制模式下启动单个服务器,单独的参与者可以使用观察者运行,并且集群可以重新配置到一个节点,然后从一个节点向上配置。默认值为 true,以实现向后兼容性。可以使用 QuorumPeerConfig 的 setStandaloneEnabled 方法或通过将“standaloneEnabled=false”或“standaloneEnabled=true”添加到服务器的配置文件来设置它。
reconfigEnabled :(无 Java 系统属性)3.5.3 中的新增功能:这控制动态重新配置功能的启用或禁用。启用该功能后,用户可以通过 ZooKeeper 客户端 API 或通过 ZooKeeper 命令行工具执行重新配置操作,前提是用户有权执行此类操作。禁用该功能后,任何用户(包括超级用户)都无法执行重新配置。任何重新配置的尝试都将返回错误。“reconfigEnabled”选项可以设置为服务器配置文件的“reconfigEnabled=false”或“reconfigEnabled=true”,或使用QuorumPeerConfig的setReconfigEnabled方法。默认值为 false。如果存在,则该值应在整个整体中的每个服务器上保持一致。在某些服务器上将该值设置为 true,在其他服务器上将该值设置为 false 将导致不一致的行为,具体取决于被选为领导者的服务器。如果领导者的设置为“reconfigEnabled=true”,则集合将启用重新配置功能。如果领导者的设置为“reconfigEnabled=false”,则集合将禁用重新配置功能。因此,建议在整体中的服务器之间使用一致的“reconfigEnabled”值。
4lw.commands.whitelist :(Java 系统属性:zookeeper.4lw.commands.whitelist) 3.5.3 中的新功能:用户想要使用的逗号分隔的四个字母单词命令列表。必须在此列表中放置有效的四个字母单词命令,否则 ZooKeeper 服务器将不会启用该命令。默认情况下,白名单仅包含 zkServer.sh 使用的“srvr”命令。默认情况下,其余的四个字母的单词命令处于禁用状态:尝试使用它们将获得响应“…未执行,因为它不在白名单中。下面是一个配置示例,该配置启用 stat、ruok、conf 和 isro 命令,同时禁用 Four Letter Words 命令的其余部分:
4lw.commands.whitelist=stat, ruok, conf, isro
zxid 由两部分组成:纪元和计数器。在我们的实现中,zxid 是一个 64 位数字。我们使用高阶 32 位作为纪元,使用低阶 32 位作为计数器。由于 zxid 由两部分组成,因此 zxid 既可以表示为数字,也可以表示为一对整数(epoch、count)。纪元数字代表领导层的变化。每当新领导人上台时,它都会有自己的纪元。我们有一个简单的算法来为提案分配一个唯一的 zxid:领导者只需递增 zxid 即可为每个提案获得一个唯一的 zxid。领导力激活将确保只有一个领导者使用给定的纪元,因此我们的简单算法可以保证每个提案都有一个唯一的 ID。
ZooKeeper 消息传递包括两个阶段:
领导者激活 :在此阶段,领导者建立系统的正确状态并准备开始提出建议。
主动消息传递 :在此阶段,领导者接受要建议的消息并协调消息传递。
如何查看 ZK 集群中的角色
./bin/zkServer.sh status conf/zoo.cfg
可以看到,其中节点 2 为 leader,其他的为 follower。但是如果你按照 zoo1.cfg,zoo2.cfg,zoo3.cfg 的顺序启动,无论你启动多少遍,节点 2 总是 leader,而这时如果把节点 2 关掉,进行查看角色,发现节点 3 成了 leader。
zk集群选举过程
zk 会进行多轮的投票,直到某一个节点的票数大于或等于半数以上,在 3 个节点中,总共会进行 2 轮的投票:
- 第一轮,每个节点启动时投票给自己,那这样 zk1,zk2,zk3 各有一票。
- 第二轮,每个节点投票给大于自己 myid,那这样 zk2 启动时又获得一票。加上自己给自己投的那一票。总共有 2 票。2 票大于了当前节点总数的半数,所以投票终止。zk2 当选 leader。
正常客户端数据提交流程
客户端写入数据提交流程大致为:leader 接受到客户端的写请求,然后同步给各个子节点:
客户端会和所有的节点建立链接,并且发起写入请求是挨个遍历节点进行的,比如第一次是节点 1,第二次是节点 2。
以此类推。
如果客户端正好链接的节点的角色是 leader,那就按照上面的流程走。
那如果链接的节点不是 leader,是 follower 呢,则有以下流程:
如果 Client 选择链接的节点是 Follower 的话,这个 Follower 会把请求转给当前 Leader,然后 Leader 会走蓝色的线把请求广播给所有的 Follower,每个节点同步完数据后会走绿色的线告诉 Leader 数据已经同步完成(但是还未提交)。
当 Leader 收到半数以上的节点 ACK 确认消息后,那么 Leader 就认为这个数据可以提交了,会广播给所有的 Follower 节点,所有的节点就可以提交数据。
整个同步工作就结束了。
ZK 中的过期数据选举
假设还是有一个 3 个节点的集群,zk2 为 Leader,这时候如果 zk2 挂了。zk3 当选 Leader,zk1 为 Follower。这时候如果更新集群中的一个数据。
然后把 zk1 和 zk3 都关闭。然后挨个再重启 zk1,zk2,zk3。这时候启动后,zk2 还能当选为 Leader 吗?
其实这个问题,换句话说就是:在挨个启动 zk 节点的时候,zk1 和 zk3 的数据为最新,而 zk2 的数据不是最新的,按照之前的选举规则的话,zk2 是否能顺利当选 Leader?
答案为否,最后当选的为 zk1。
这是为什么呢。
因为 zk2 的最新 ZXID 已经不是最新了,zk 的选举过程会优先考虑 ZXID 大的节点。这时 ZXID 最大的有 zk1 和 zk3,选举只会在这 2 个节点中产生,根据之前说的选举规则。在第一轮投票的时候,zk1 只要获得 1 票,就能达到半数了,就能顺利当选为 Leader 了。
参考文章
- zookeeper官网