Zookeeper篇
ZooKeeper是什么?
ZooKeeper是一个分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态,根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。
Zookeeper保证了如下分布式一致性特性:
- 顺序一致性
- 原子性
- 单一视图
- 可靠性
- 实时性(最终一致性)
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。
Zookeeper一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心,服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据,示例图如下:
了解Zookeeper的系统架构吗?
(1)ZooKeeper分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper服务的任意服务器上(除非 leaderServes 参数被显式设置, leader 不允许接受客户端连接)。
(2)客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送心跳。如果这个 TCP 连接中断,客户端将自动尝试连接到另外的 ZooKeeper服务器。客户端第一次连接到 ZooKeeper服务时,接受这个连接的 ZooKeeper服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。
(3)上图中每一个Server代表一个安装Zookeeper服务的机器,即是整个提供Zookeeper服务的集群(或者是由伪集群组成);
(4)组成ZooKeeper服务的服务器必须彼此了解。它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照, 只要大多数服务器可用,ZooKeeper服务就可用;
(5)ZooKeeper 启动时,将从实例中选举一个 leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据。每个Server 在内存中存储了一份数据。
(6)Zookeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;
(7)Zab协议包含两个阶段:leader election阶段和Atomic Brodcast阶段(选主和原子广播)。
- 集群中将选举出一个leader,其他的机器则称为follower,所有的写操作都被传送给leader,并通过brodcast将所有的更新告诉给follower。
- 当leader崩溃或者leader失去大多数的follower时,需要重新选举出一个新的leader,让所有的服务器都恢复到一个正确的状态。
- 当leader被选举出来,且大多数服务器完成了 和leader的状态同步后,leadder election 的过程就结束了,就将会进入到Atomic brodcast的过程。
- Atomic Brodcast同步leader和follower之间的信息,保证leader和follower具有形同的系统状态。
能说说Zookeeper的工作原理?
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。
Zab协议有两种模式,它们 分别是恢复模式(选主)和广播模式(同步)。
Zab协议 的全称是 Zookeeper Atomic Broadcast(Zookeeper原子广播)。
Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加 上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一 个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
epoch:可以理解为皇帝的年号,当新的皇帝leader产生后,将有一个新的epoch年号。
服务器具有四种状态,分别是LOOKING、FOLLOWING、LEADING、OBSERVING。
- LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有Leader,因此需要进入Leader选举状态。
- FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
- LEADING:领导者状态。表明当前服务器角色是Leader。
- OBSERVING:观察者状态。表明当前服务器角色是Observer。
分布式集群中为什么会有Master?
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,于是就需要进行leader选举。
Zookeeper的数据同步是怎样的?
参考:数据同步
集群最少要几台机器,集群规则是怎样的?
集群规则为2N+1台,N>0,即3台。(过半数)
集群支持动态添加机器吗?
其实就是水平扩容了,Zookeeper在这方面不太好。两种方式:
- 全部重启:关闭所有Zookeeper服务,修改配置之后启动。不影响之前客户端的会话。
- 逐个重启:在过半存活即可用的原则下,一台机器重启不影响整个集群对外提供服务。这是比较常用的方式。
3.5版本开始支持动态扩容。
Zookeeper为什么要这么设计?
ZooKeeper设计的目的是提供高性能、高可用、顺序一致性的分布式协调服务、保证数据最终一致性。
高性能(简单的数据模型)
- 采用树形结构组织数据节点;
- 全量数据节点,都存储在内存中;
- Follower 和 Observer 直接处理非事务请求;
高可用(构建集群)
- 半数以上机器存活,服务就能正常运行
- 自动进行 Leader 选举
顺序一致性(事务操作的顺序)
- 每个事务请求,都会转发给 Leader 处理
- 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)
最终一致性
- 通过提议投票方式,保证事务提交的可靠性
- 提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据
你知道Zookeeper中有哪些角色?
- 领导者(leader):Leader服务器为客户端提供读服务和写服务。负责进行投票的发起和决议,更新系统状态。
- 学习者(learner)
- 跟随者(follower) Follower服务器为客户端提供读服务,参与Leader选举过程,参与写操作“过半写成功”策略。
- 观察者(observer) Observer服务器为客户端提供读服务,不参与Leader选举过程,不参与写操作“过半写成功”策略。用于在不影响写性能的前提下提升集群的读性能。
- 客户端(client):服务请求发起方(通常是我们的应用程序)。
你熟悉Zookeeper节点ZNode和相关属性吗?
Znode两种类型:
- 持久的(persistent):客户端和服务器端断开连接后,创建的节点不删除(默认)。
- 短暂的(ephemeral):客户端和服务器端断开连接后,创建的节点自己删除。
Znode有四种形式:
- 持久化节点(PERSISTENT):客户端与Zookeeper断开连接后,该节点依旧存在;
- 持久化顺序编号节点(PERSISTENT_SEQUENTIAL):客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号;
- 临时节点(EPHEMERAL):客户端与Zookeeper断开连接后,该节点被删除;
- 临时顺序编号节点(EPHEMERAL_SEQUENTIAL):客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号;
创建ZNode时设置顺序标识,ZNode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。
请简述Zookeeper的选主流程
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。leader选举是保证分布式数据一致性的关键。
出现选举主要是两种场景:初始化、leader不可用。
当zk集群中的一台服务器出现以下两种情况之一时,就会开始leader选举:
(1)服务器初始化启动。
(2)服务器运行期间无法和leader保持连接。
而当一台机器进入leader选举流程时,当前集群也可能处于以下两种状态:
(1)集群中本来就已经存在一个leader。
(2)集群中确实不存在leader。
首先第一种情况,通常是集群中某一台机器启动比较晚,在它启动之前,集群已经正常工作,即已经存在一台leader服务器。当该机器试图去选举leader时,会被告知当前服务器的leader信息,它仅仅需要和leader机器建立连接,并进行状态同步即可。
重点是leader不可用了,此时的选主制度。
投票信息中包含两个最基本的信息:
- sid:即server id,用来标识该机器在集群中的机器序号。
- zxid:即zookeeper事务id号。ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id,,该id称为zxid.,由于zxid的递增性质, 如果zxid1小于zxid2,,那么zxid1肯定先于zxid2发生。创建任意节点,或者更新任意节点的数据, 或者删除任意节点,都会导致Zookeeper状态发生改变,从而导致zxid的值增加。
以(sid,zxid)的形式来标识一次投票信息。
例如:如果当前服务器要推举sid为1,zxid为8的服务器成为leader,那么投票信息可以表示为(1,8)。
集群中的每台机器发出自己的投票后,也会接收来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,以此来决定是否需要变更自己的投票。
规则如下:
- 初始阶段,都会给自己投票。
- 当接收到来自其他服务器的投票时,都需要将别人的投票和自己的投票进行pk
- 优先检查zxid。zxid比较大的服务器优先作为leader。如果zxid相同的话,就比较sid,sid比较大的服务器作为leader。
有了解过watch机制吗?
简单地说:client端会对某个znode 注册一个watcher事件,当该znode发生变化时,这些client会收到ZooKeeper的通知,然后client可以根据znode变化来做出业务上的改变等。
经典使用场景:zookeeper为dubbo提供服务的注册与发现,作为注册中心,但是大家有没有想过zookeeper为啥能够实现服务的注册与发现吗?
这就不得不说一下zookeeper的灵魂 Watcher(监听者)。
watcher 是zooKeeper中一个非常核心功能 ,客户端watcher 可以监控节点的数据变化以及它子节点的变化,一旦这些状态发生变化,zooKeeper服务端就会通知所有在这个节点上设置过watcher的客户端 ,从而每个客户端都很快感知,它所监听的节点状态发生变化,而做出对应的逻辑处理。
那么我们来分析一下,zookeeper是如何实现服务的注册与发现。zookeeper的服务注册与发现,主要应用的是zookeeper的znode节点数据模型和watcher机制,大致的流程如下:
- 服务注册:服务提供者(Provider)启动时,会向zookeeper服务端注册服务信息,也就是创建一个节点,例如:用户注册服务com.xxx.user.register,并在节点上存储服务的相关数据(如服务提供者的ip地址、端口等)。
- 服务发现:服务消费者(Consumer)启动时,根据自身配置的依赖服务信息,向zookeeper服务端获取注册的服务信息并设置watch监听,获取到注册的服务信息之后,将服务提供者的信息缓存在本地,并进行服务的调用。
- 服务通知:一旦服务提供者因某种原因宕机不再提供服务之后,客户端与zookeeper服务端断开连接,zookeeper服务端上服务提供者对应服务节点会被删除(例如:用户注册服务com.xxx.user.register),随后zookeeper服务端会异步向所有消费用户注册服务com.xxx.user.register,且设置了watch监听的服务消费者发出节点被删除的通知,消费者根据收到的通知拉取最新服务列表,更新本地缓存的服务列表。
上边的过程就是zookeeper可以实现服务注册与发现的大致原理。
watcher有哪些类型?
znode节点可以设置两类watch:
- DataWatches,基于znode节点的数据变更从而触发 watch 事件,触发条件getData()、exists()、setData()、 create()。
- Child Watches,基于znode的孩子节点发生变更触发的watch事件,触发条件 getChildren()、 create()。
在调用 delete() 方法删除znode时,则会同时触发Data Watches和Child Watches,如果被删除的节点还有父节点,则父节点会触发一个Child Watches。
watcher有什么特性?
watch对节点的监听事件是一次性的!客户端在指定的节点设置了监听watch,一旦该节点数据发生变更通知一次客户端后,客户端对该节点的监听事件就失效了。
如果还要继续监听这个节点,就需要我们在客户端的监听回调中,再次对节点的监听watch事件设置为True。否则客户端只能接收到一次该节点的变更通知。
当一个客户端连接到一个新的服务器上时,watch将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到watch的。而当client重新连接时,如果需要的话,所有先前注册过的watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,watch可能会丢失:对于一个未创建的znode的exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个watch事件可能会被丢失。
那你说说Zookeeper有哪些应用场景?
数据发布与订阅
发布与订阅即所谓的配置管理,顾名思义就是将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。
数据发布/订阅的一个常见的场景是配置中心,发布者把数据发布到 ZooKeeper 的一个或一系列的节点上,供订阅者进行数据订阅,达到动态获取数据的目的。
Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限为 1M。
配置信息一般有几个特点:
- 数据量小的KV
- 数据内容在运行时会发生动态变化
- 集群机器共享,配置一致
ZooKeeper 采用的是推拉结合的方式。
- 推:服务端会推给注册了监控节点的客户端 Wathcer 事件通知
- 拉:客户端获得通知后,然后主动到服务端拉取最新的数据
命名服务
作为分布式命名服务,命名服务是指通过指定的名字来获取资源或者服务的地址,利用ZooKeeper创建一个全局的路径,这个路径就可以作为一个名字,指向集群中的集群,提供的服务的地址,或者一个远程的对象等等。
统一命名服务的命名结构图如下所示:
- 在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。
- 类似于域名与IP之间对应关系,IP不容易记住,而域名容易记住。
- 通过名称来获取资源或服务的地址,提供者等信息。
- 按照层次结构组织服务/应用名称。
- 可将服务名称以及地址信息写到ZooKeeper上,客户端通过ZooKeeper获取可用服务列表类。
集群管理
所谓集群管理就是:是否有机器退出和加入、选举master。
集群管理主要指集群监控和集群控制两个方面。前者侧重于集群运行时的状态的收集,后者则是对集群进行操作与控制。
开发和运维中,面对集群,经常有如下需求:
- 希望知道集群中究竟有多少机器在工作
- 对集群中的每台机器的运行时状态进行数据收集
- 对集群中机器进行上下线的操作
集群管理结构图如下所示:
分布式通知与协调
- 分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态。
- NameNode需知道各个Datanode的状态。
- JobTracker需知道各个TaskTracker的状态。
- 心跳检测机制可通过ZooKeeper来实现。
- 信息推送可由ZooKeeper来实现,ZooKeeper相当于一个发布/订阅系统。
分布式锁
处于不同节点上不同的服务,它们可能需要顺序的访问一些资源,这里需要一把分布式的锁。
分布式锁具有以下特性:写锁、读锁、时序锁。
- 写锁:在zk上创建的一个临时的无编号的节点。由于是无序编号,在创建时不会自动编号,导致只能有一个客户端得到锁,然后进行写入。
- 读锁:在zk上创建一个临时的有编号的节点,这样即使下次有客户端加入是同时创建相同的节点时,它也会自动编号,也可以获得锁对象,然后对其进行读取。
- 时序锁:在zk上创建的一个临时的有编号的节点根据编号的大小控制锁。
分布式队列
分布式队列分为两种:
- 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
- 一个job由多个task组成,只有所有任务完成后,job才运行完成。
- 可为job创建一个/job目录,然后在该目录下,为每个完成的task创建一个临时的Znode,一旦临时节点数目达到task总数,则表明job运行完成。
- 队列按照FIFO方式进行入队和出队操作,例如实现生产者和消费者模型。
知道监听器的原理吗?

- 创建一个Main()线程;
- 在Main()线程中创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener);
- 通过connect线程将注册的监听事件发送给Zookeeper;
- 将注册的监听事件添加到Zookeeper的注册监听器列表中;
- Zookeeper监听到有数据或路径发生变化时,把这条消息发送给Listener线程;
- Listener线程内部调用process()方法
为什么Zookeeper集群的数目,一般为奇数个?
首先需要明确zookeeper选举的规则:leader选举,要求可用节点数量 > 总节点数量/2。
比如:标记一个写是否成功是要在超过一半节点发送写请求成功时才认为有效。同样,Zookeeper选择领导者节点也是在超过一半节点同意时才有效。最后,Zookeeper是否正常是要根据是否超过一半的节点正常才算正常。这是基于CAP的一致性原理。
zookeeper有这样一个特性:集群中只要有过半的机器是正常工作的,那么整个集群对外就是可用的。
也就是说如果有2个zookeeper,那么只要有1个死了zookeeper就不能用了,因为1没有过半,所以2个zookeeper的死亡容忍度为0;
同理,要是有3个zookeeper,一个死了,还剩下2个正常的,过半了,所以3个zookeeper的容忍度为1;
2->0;两个zookeeper,最多0个zookeeper可以不可用。
3->1;三个zookeeper,最多1个zookeeper可以不可用。
4->1;四个zookeeper,最多1个zookeeper可以不可用。
5->2;五个zookeeper,最多2个zookeeper可以不可用。
6->2;两个zookeeper,最多0个zookeeper可以不可用。
....
会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,所以为了更加高效,何必增加那一个不必要的zookeeper呢。
zookeeper的选举策略也是需要半数以上的节点同意才能当选leader,如果是偶数节点可能导致票数相同的情况。
Zookeeper的java客户端都有哪些?
zk自带的zkclient及Apache开源的Curator。
zookeeper负载均衡和nginx负载均衡区别
zk的负载均衡是可以调控,nginx只是能调权重,其他需要可控的都需要自己写插件;但是nginx的吞吐量比zk大很多,应该说按业务选择用哪种方式。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!