背景
架构图
集群模式详解
客户端连接到单个ZooKeeper服务器。客户端维护一个TCP连接,通过该连接发送请求、获取响应、获取监视事件和发送检测信号。如果与服务器的TCP连接中断,客户端将连接到其他服务器。
订购了ZooKeeper。ZooKeeper在每次更新时都会使用一个数字来标记,该数字反映了所有ZooKeeper事务的顺序。后续操作可以使用该顺序来实现更高级别的抽象,例如同步基元。
ZooKeeper速度很快。在“读取主导”工作负载中,它的速度尤其快。ZooKeeper应用程序在数千台机器上运行,在读取比写入更常见的情况下,它的性能最佳,比率约为10:1。
保证
ZooKeeper非常快速且非常简单。但是,由于它的目标是成为构建更复杂服务(例如同步)的基础,因此它提供了一组保证。这些是:
顺序一致性-来自客户端的更新将按发送顺序应用。
原子性-更新成功或失败。没有部分结果。
单个系统映像-无论客户端连接到哪个服务器,它都将看到相同的服务视图。也就是说,即使客户端故障转移到具有相同会话的其他服务器,客户端也永远不会看到系统的旧视图。
可靠性-应用更新后,它将从那时起一直存在,直到客户端覆盖更新。
及时性-保证在一定时间范围内,客户端对系统的看法是最新的。
简单的API
ZooKeeper的设计目标之一是提供一个非常简单的编程接口。因此,它仅支持以下操作:
创建:在树中的某个位置创建一个节点
delete:删除节点
exists:测试某个位置是否存在节点
获取数据:从节点读取数据
setdata:将数据写入节点
GetChildren:检索节点的子节点列表
sync:等待数据传播
跨计算机要求
要使 ZooKeeper 服务处于活动状态,必须有大多数可以相互通信的非故障计算机。对于具有 N 台服务器的 ZooKeeper 集成,如果 N 为奇数,则集成能够容忍多达 N/2 个服务器故障,而不会丢失任何 znode 数据;如果 N 为偶数,则集成最多能够容忍 N/2-1 个服务器故障。
群集选项
本节中的选项设计用于服务器集合,即在部署服务器群集时。
-
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:3889
server.2=zoo2-net1:2888:3888|zoo2-net2:2889:3889
server.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官网