1. ZooKeeper工作原理
1.1 ZooKeeper角色
-
领导者(Leader):在Zookeeper集群中,Leader是负责管理集群事务的节点。它负责处理所有的写请求,并将这些请求转化为事务,并提交事务日志。Leader节点还负责发起和决议投票过程,以及更新系统状态。
-
学习者(Learner):学习者是Zookeeper中用于接收客户端请求并返回结果的节点。学习者分为两种类型:
-
跟随者(Follower):跟随者是学习者的一种,它们参与到Zookeeper的投票过程中,帮助选举出Leader,并且同步Leader的状态。跟随者也可以处理客户端的读请求。
-
观察者(Observer):观察者也是学习者的一种,但它们不参与投票过程。观察者的主要作用是接收客户端的连接,并将写请求转发给Leader。它们只同步Leader的状态,不参与决策过程,这样可以提高系统的读取性能。
-
-
客户端(Client):客户端是请求发起方,它们与Zookeeper集群交互,发起读写请求。客户端可以是任何需要与Zookeeper交互的应用程序或服务。
1.2 ZooKeeper核心机制
Zookeeper的核心机制是原子广播,它确保了分布式系统中各个服务器(Server)之间的状态同步。实现这一机制的协议是Zab(Zookeeper Atomic Broadcast)协议。
1.2.1 Zab协议
Zab协议定义了两种模式:
-
恢复模式(选主):当服务启动或者当前领导者崩溃后,Zab进入恢复模式。在这个阶段,系统的主要任务是选举出一个新的领导者。
-
广播模式(同步):一旦领导者被选举出来,并且大多数服务器与领导者完成了状态同步,系统就从恢复模式切换到广播模式。在广播模式下,领导者负责处理客户端的写请求,并将这些请求广播给所有服务器,以确保数据的一致性。
1.2.2 事务ID(Zxid)
为了保证事务的顺序一致性,Zookeeper使用递增的事务ID号(zxid)来标识每个事务。Zxid是一个64位的数字,分为两部分:
-
高32位(Epoch):用于标识领导者关系是否改变。每当一个新的领导者被选举出来,Epoch值会增加,表示进入了一个新的领导者统治时期。
-
低32位:用于递增计数,确保每个事务都有一个唯一的标识。
-
Zookeeper中的Zxid是一个64位的数字,用于确保事务的顺序一致性。Zxid由两部分组成:高32位是epoch,用于标识Leader关系是否改变,每次选出新的Leader时,都会有一个新的epoch;低32位是递增计数,用于记录事务的顺序。当Zxid的低32位达到最大值(即2^32-1)时会发生溢出,此时会触发集群重新选举,然后Zxid会变为0,日志中会记录相应的信息,例如“zxid lower 32 bits have rolled over, forcing re-election, and therefore new epoch start”。Zxid达到最大值后,集群会强制进行选主,并重置Zxid低32位的计数值,高32位变为新Leader的周期,低32位变为0。目前,没有避免Zxid溢出的方法,但业务侧可以提前规避相关风险
1.2.3 服务器状态
每个服务器在工作过程中可能处于以下三种状态之一:
-
LOOKING:服务器当前不知道领导者是谁,正在寻找领导者。
-
LEADING:服务器当前被选举为领导者,负责处理事务和与跟随者同步状态。
-
FOLLOWING:领导者已经选举出来,服务器与之同步,接收并应用领导者广播的事务。
通过这些机制,Zookeeper能够确保分布式系统中的数据一致性和高可用性,即使在领导者崩溃的情况下也能快速恢复并继续提供服务。
1.2.4 ZooKeeper读写机制
-
集群结构:Zookeeper由多个服务器(Server)组成的集群,这些服务器可以分布在不同的物理位置。
-
领导者(Leader)和跟随者(Follower):集群中有一个领导者(Leader),负责处理所有的写请求。跟随者(Follower)是集群中的其他服务器,它们主要负责处理读请求和同步领导者的状态。
-
数据副本:每个服务器都保存着一份数据的副本。这些副本在正常情况下应该是完全一致的,以保证数据的全局一致性。
-
分布式读写:Zookeeper支持分布式读写操作。客户端可以向任何一个服务器发起读写请求。
-
更新请求转发:当客户端向跟随者发起写请求时,这个请求会被转发到领导者。领导者负责实施这些更新操作。
-
事务处理:领导者使用Zab协议来处理事务。写请求被转化为事务,并分配一个唯一的事务ID(zxid)。领导者将事务日志广播给所有跟随者。
-
数据同步:跟随者接收到领导者广播的事务日志后,会更新自己的数据副本,以确保与领导者的数据一致。
-
读请求处理:对于读请求,跟随者可以直接处理,因为它们的数据副本是与领导者同步的。这样可以提高系统的读取性能,因为读操作不需要经过领导者。
-
故障恢复:如果领导者发生故障,Zookeeper会通过Zab协议的恢复模式选举出新的领导者,并确保所有跟随者与新领导者的数据副本同步。
1.2.5 Zookeeper节点数据操作流程![](https://i-blog.csdnimg.cn/direct/a241ced7efc546f29f965f69354d7336.png)
-
客户端请求写操作:Client 发出一个写请求到集群中的任意一个 Follower。
-
Follower 转发请求:收到写请求的 Follower 将请求转发给当前的 Leader。
-
Leader 发起投票:Leader 收到写请求后,会创建一个事务提案(Proposal),并为其分配一个唯一的 ZXID。然后,Leader 将这个提案发送给所有 Follower,请求它们进行投票。
-
Follower 发送投票结果:Follower 收到提案后,会根据自身的数据状态和配置对提案进行投票,并将投票结果发送回 Leader。
-
Leader 汇总投票结果:Leader 等待收到超过半数的 Follower 的投票响应。如果提案获得多数 Follower 的同意,Leader 决定将提案进行提交。
-
Leader 写入并提交事务:Leader 在本地将提案写入事务日志,并更新内存中的数据状态,然后向所有 Follower 发送提交(Commit)指令。
-
Follower 应用事务:Follower 收到 Commit 指令后,也会将该提案写入本地事务日志,并更新内存中的数据状态,以保证数据的一致性。
-
Follower 返回结果给 Client:一旦 Follower 完成写入操作,它会将写操作的结果返回给原始请求的 Client。
-
Client 接收响应:Client 接收到来自 Follower 的写操作结果,整个写请求流程完成。
1.2.6 Follower的主要职责
-
发送请求给Leader:Follower定期向Leader发送PING消息以确认连接的有效性,发送REQUEST消息请求投票或同步,以及发送ACK消息确认接收到的提案或命令。
-
接收并处理Leader的消息:Follower接收来自Leader的指令和提案,并据此更新自己的状态和数据。
-
转发Client写请求:对于客户端的写请求,Follower将其转发给Leader,由Leader来协调整个集群的数据一致性。
-
返回结果给Client:在处理完来自客户端的请求后,无论是读请求还是转发的写请求的结果,Follower都会将结果返回给客户端。
1.2.7 Follower处理的来自Leader的消息类型
-
PING消息:这是心跳消息,用于维持Follower与Leader之间的连接,确保Follower知道Leader仍然可用。
-
PROPOSAL消息:当Leader需要对某个事务进行投票时,会向所有Follower发送PROPOSAL消息,Follower收到后需要对提案进行投票。
-
COMMIT消息:一旦提案获得多数Follower的同意,Leader会发送COMMIT消息给所有Follower,指示它们将提案正式提交到本地日志并更新状态。
-
UPTODATE消息:这表明Follower与Leader的数据已经同步完成,Follower是最新的。
-
REVALIDATE消息:这是Leader对Follower上的某个session进行验证的结果,可能要求Follower关闭该session或允许其继续接收消息。
-
SYNC消息:这是对客户端发起的SYNC请求的响应,客户端可能需要确保从Follower获取到最新的数据更新。
1.2.8 Zookeeper leader选举
-
半数通过原则:在Zookeeper集群中,确实需要超过半数的服务器同意才能选出一个新的Leader。对于3台服务器的集群,需要至少2台同意;对于4台服务器的集群,需要至少3台同意。
-
3台机器 挂一台 2>3/2
-
4台机器 挂2台 2!>4/2
-
-
选举过程:
-
初始时,所有服务器都处于LOOKING状态,即它们都在寻找Leader。
-
每个服务器都发出一个提议(Proposal),提议自己成为Leader,并开始接收其他服务器的投票。
-
-
投票:
-
服务器之间相互投票,每个服务器会收到来自其他服务器的提议,并根据自己的逻辑时钟(ZXID)和服务器ID(myid)来决定是否同意这个提议。
-
ZXID较大的提议会优先获得投票,如果XZID相同,则会选择myid较大的服务器。
-
-
超过半数同意:
-
一旦某个服务器获得了超过半数的投票,它就认为自己被选举为Leader,并开始向其他服务器发送确认消息。
-
-
结束选举:
-
当一个服务器获得足够的投票成为Leader后,选举结束。其他服务器在收到足够多的确认消息后,会停止自己的选举过程,并进入FOLLOWING状态,服从新Leader的命令。
-
-
提案的无效性:
-
如果一个服务器在选举过程中收到了另一个服务器已经成为Leader的确认消息,它会停止自己的选举过程,并接受这个事实,即使自己可能已经开始了一个新的提案。
-
-
启动顺序:
-
谁先启动并不能决定谁成为Leader。Leader的选举是基于投票和确认消息的,而不是简单地基于启动顺序。
-
-
ZXID和myid的重要性:
-
ZXID和myid是选举过程中的关键因素。ZXID用于确保事务的全局顺序性,而myid是服务器的唯一标识符,它们共同决定了选举的结果。
-
1.2.9 ZooKeeper仲裁区
-
Zookeeper的仲裁区(Quorum)是指在Zookeeper的仲裁模式(Quorum Mode)下,由多台服务器组成的集群,这些服务器协同工作,同时服务客户端的请求。仲裁模式是Zookeeper的默认运行模式,也是最常用的一种模式。在仲裁模式下,为了保证系统的可用性和可靠性,需要至少三台服务器组成一个奇数个节点的集群,这样可以确保在有服务器宕机的情况下,仍然能够选出一个Leader,继续提供服务。
-
在仲裁模式中,Zookeeper集群中的每个服务器都会保存整个数据集的副本,客户端连接到集群中的任一服务器进行读写操作。集群中的服务器通过投票机制来达成一致性,选出一个Leader服务器,由Leader负责处理写请求,而读请求可以在任何一个服务器上执行。
-
搭建Zookeeper仲裁模式的步骤主要包括:准备服务器、配置服务器、启动服务器和创建数据节点。在配置服务器时,需要在ZooKeeper的配置文件(zoo.cfg)中指定每个服务器的ID和地址,以及指定集群中的其他服务器。例如,可以设置
server.1=zoo1:2888:3888
,其中server.ID=HOST:PORT1:PORT2
,ID是服务器的唯一标识,HOST是服务器的主机名或IP地址,PORT1用于服务器之间的数据同步,PORT2用于Leader选举。 -
在仲裁模式下,Zookeeper集群能够提供高可用性和强一致性的服务,适用于生产环境和分布式系统的协调管理。
1.2.10 选举过程
-
A提案说,我要选自己,B你同意吗?C你同意吗?B说,我同意选A;C说,我同意选A。注意,这里超过半数了,其实在现实世界选举已经成功了。但是计算机世界是很严格,另外要理解算法,要继续模拟下去。)
-
接着B提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;C说,A已经超半数同意当选,B提案无效。
-
接着C提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;B说,A已经超半数同意当选,C的提案无效。
-
选举已经产生了Leader,后面的都是follower,只能服从Leader的命令。而且这里还有个小细节,就是其实谁先启动谁当头。
1.3 ZooKeeper集群数目
Zookeeper集群的数目通常选择为奇数个,这主要是基于以下几个原因:
-
基于Paxos协议的Leader选举:Zookeeper使用基于Paxos协议的变种ZAB(Zookeeper Atomic Broadcast)协议来实现Leader选举。Paxos协议的核心思想是多数写入即成功,即如果集群中的多数节点(超过半数)能够写入某个值,则认为这个操作成功。
-
容错能力:在一个由奇数个节点组成的Zookeeper集群中,如果集群中的一半以上节点存活,集群就能正常工作。例如,一个3节点的集群可以容忍1个节点的故障,而4节点的集群也只能容忍1个节点的故障。因此,从容错角度来看,3节点和4节点的容错能力相同。
-
避免脑裂:如果集群中的节点数量是偶数,那么在网络分区或其他故障情况下,可能会发生所谓的"脑裂"现象,即集群分裂成两个独立的小组,每个小组都认为自己是集群的合法领导者。奇数个节点可以减少这种情况的发生,因为不可能有两个相同的多数。
-
资源利用效率:由于3节点和4节点的容错能力相同,选择3节点可以节省服务器资源。如果选择5节点,虽然可以容忍2个节点的故障,但在大多数情况下,这可能是一种资源的过度配置。
-
投票效率:在奇数个节点的集群中,当进行Leader选举或其他需要多数同意的操作时,可以更快地得出结果,因为奇数的一半总是一个整数,而偶数的一半是一个小数,需要向上取整,这可能会导致更多的投票轮次。
1.4 ZooKeeper 的节点
Zookeeper的节点,也称为znode,是Zookeeper中数据结构的基本单元,类似于文件系统中的文件和目录。每个znode都包含了数据和相关的状态信息,并且可以有子节点。以下是Zookeeper中znode的四种类型:
-
持久化节点 (PERSISTENT):
-
这类节点一旦被创建,就会永久保存在Zookeeper中,直到被客户端显式地删除。
-
即使创建它的客户端会话结束,持久化节点也会继续存在。
-
-
短暂节点 (EPHEMERAL):
-
短暂节点与客户端会话绑定。如果客户端会话结束(无论是因为网络问题还是客户端崩溃),Zookeeper将自动删除这个短暂节点。
-
短暂节点不允许有子节点,这确保了当客户端会话结束时,不会有遗留的子节点。
-
-
持久化顺序编号节点 (PERSISTENT_SEQUENTIAL):
-
这种类型的节点具有持久化节点的所有特性,但增加了顺序编号的元素。
-
当创建一个持久化顺序编号节点时,Zookeeper会根据当前已存在的顺序编号最大的节点,递增编号来创建新节点。
-
这种类型的节点常用于需要有序列表的场景。
-
-
短暂顺序编号节点 (EPHEMERAL_SEQUENTIAL):
-
短暂顺序编号节点结合了短暂节点和顺序编号的特性。
-
这类节点在客户端会话结束时会被删除,同时它们也会被赋予一个唯一的顺序编号。
-
这适用于实现需要临时有序集合的场景。
-