Lazy loaded image
中间件
Lazy loaded imagezookeeper
字数 4903阅读时长 13 分钟
2025-3-20
2025-6-13
type
status
date
slug
summary
tags
category
icon
password

ZooKeeper介绍

简介

zookeeper是一个为分布式应用所设计的分布式的、开源的协调服务。它提供了一项基本服务:分布式锁服务,同时也提供了分布式应用数据的维护和管理机制,包括统一命名服务、状态同步服务、集群管理、分布式消息队列、分布式应用配置项的管理等。zookeeper支持独立安装以及集群部署
ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类 似的系统来进行分布式协调,但是这些系统往都存在分布式单点问题 ,所以, 雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
关于 ZooKeeper 这个项目的名字,其实也有一段趣闻 ,在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目 ),雅虎的工程 师希望给这个项目也取一动物的名字。时任研究院首席科学家 RaghuRamakrishnan(拉古·拉玛克里希南 )开玩笑地说:“在这样下去,我们这儿就变成动物园了! !”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起, 雅虎的整个分布式系统看上去就像一个大型的动物园了,而 Zookeeper 正好要用来进行分布式环境的协调 ,于是 Zookeeper 的名字也就由此诞生了。

分布式系统面临的问题

我们将分布式系统定义为:分布式系统是同时跨越多个物理主机,独立运行的多个软件所组成系统。类比一下,分布式系统就是一群人一起干活。人多力量大,每个服务器的算力是有限的,但是通过分布式系统,由N个服务器组成起来的集群,算力是可以无限扩张的。
优点显而易见,人多干活快,并且互为备份。但是缺点也很明显。我们可以想象一下,以一个小研发团队开发软件为例,假设我们有一个5人的项目组,要开始一个系统的开发,项目组将面临如下问题:
notion image
你一定在想,以上这些问题很简单啊,在我的日常工作中天天都在发生,并没感觉有什么复杂。是的,这是因为我们人类的大脑是个超级计算机,能够灵活应对这些问题,而且现实中信息的交换不依赖网络,不会因网络延迟或者中断,出现信息不对等。而且现实中对以上问题的处理其实并不严谨,从而也引发了很多问题。想一想,项目中是不是出现过沟通不畅造成任务分配有歧义?是否由于人员离职造成任务进行不下去,甚至要联系离职人员协助?是不是出现过任务分配不合理?类似这样的各种问题,肯定会发生于你的项目组中。在现实世界,我们可以人为去协调,即使出错了,人工去补错,加加班搞定就好。但在计算机的世界,这样做是行不通的,一切都要保证严谨,以上问题要做到尽可能不要发生。因此,分布式系统必须采用合理的方式解决掉以上的问题。
实际上要想解决这些问题并没有那么复杂,我们仅需要做一件事就可以万事无忧---让信息在项目组成员中同步。如果能做到信息同步,那么每个人在干什么,大家都是清楚的,干到什么程度也是清晰的,无论谁离职也不会产生问题。分配的工作,能够及时清晰的同步给每个组员,确保每个组员收到的任务分配没有冲突。
分布式系统的协调工作就是通过某种方式,让每个节点的信息能够同步和共享。这依赖于服务进程之间的通信。通信方式有两种: 1、通过网络进行信息共享:这就像现实世界,开发leader在会上把任务传达下去,组员通过听leader命令或者看leader的邮件知道自己要干什么。当任务分配有变化时,leader会单独告诉组员,或者再次召开会议。信息通过人与人之间的直接沟通,完成传递。 2、通过共享存储:这就好比开发leader按照约定的时间和路径,把任务分配表放到了svn上,组员每天去svn上拉取最新的任务分配表,然后干活。其中svn就是共享存储。更好一点的做法是,当svn文件版本更新时,触发邮件通知,每个组员再去拉取最新的任务分配表。这样做更好,因为每次更新,组员都能第一时间得到消息,从而让自己手中的任务分配表永远是最新的。此种方式依赖于中央存储。
ZooKeeper对分布式系统的协调,使用的是第二种方式,共享存储。其实共享存储,分布式应用也需要和存储进行网络通信。网络通信是分布式系统并发设计的基础。实际上,通过ZooKeeper实现分布式协同的原理,和项目组通过SVN同步工作任务的例子是一样的。ZooKeeper就像是svn,存储了任务的分配、完成情况等共享信息。每个分布式应用的节点就是组员,订阅这些共享信息。当主节点(组leader),对某个从节点的分工信息作出改变时,相关订阅的从节点得到zookeeper的通知,取得自己最新的任务分配。完成工作后,把完成情况存储到zookeeper。主节点订阅了该任务的完成情况信息,所以将得到zookeeper的完工的通知。
notion image
大多数分布式系统中出现的问题,都源于信息的共享出了问题。如果各个节点间信息不能及时共享和同步,那么就会在协作过程中产生各种问题。ZooKeeper解决协同问题的关键,在于保证分布式系统信息的一致性。

zookeeper概念

ZooKeeper并不直接暴露分布式服务所需要的原语及原语的调用方法。什么是原语?举个例子,比如说分布式锁机制是一个原语,它会暴露出创建、获取、释放三个调用方法。ZooKeeper以类似文件系统的方式存储数据,暴漏出调用这些数据的API。让应用通过ZooKeeper的机制和API,自己来实现分布式相关原语。我们若想让应用能够通过ZooKeeper实现分布式协同,那么第一件事就是了解ZooKeeper的特性及相关概念,另外熟悉它给我们提供了哪些API。

znode

Zookeeper会保存任务的分配、完成情况,等共享信息,那么ZooKeeper是如何保存的呢?在ZooKeeper中,这些信息被保存在一个个数据节点上,这些节点被称为znode。它采用了类似文件系统的层级树状结构进行管理。见下图示例:
notion image
根节点/包含4个子节点,其中三个拥有下一级节点。有的叶子节点存储了信息。节点上没有存储数据,也有着重要的含义。比如在主从模式中,当/master节点没有数据时,代表分布式应用的主节点还没有选举出来。
znode节点存储的数据为字节数组。存储数据的格式zookeeper不做限制,也不提供解析,需要应用自己实现。实际上图就是主从模式存储数据的示例,这里先简单讲解: /master:存储了当前主节点的信息 /workers:下面的每个子znode代表一个从节点,子znode上存储的数据,如“foo.com:2181”,代表从节点的信息 /tasks:下面的每个子znode代表一个任务,子znode上存储的信息如“run cmd”,代表该内务内容 /assign:下面每个子znode代表一个从节点的任务集合。如/assign/worker-1,代表worker-1这个从节点的任务集合。/assign/worker-1下的每个子znode代表分配给worker-1的一个任务

半数机制

zookeeper服务器运行于两种模式:独立模式和集群模式。集群模式下,会复制所有服务器的数据树。但如果让客户端等待所有复制完成,延迟太高。这里引入法定人数概念,指为了使zookeeper集群正常工作,必须有效运行的服务器数量。同时也是服务器通知客户端保存成功前,必须保存数据的服务器最小数。
法定人数(Quorum):在Zab协议中,对于任何变更(如领导者选举、数据更新等)的有效性,需要集群中的大多数节点(即超过一半的节点)达成一致。这个大多数就称为法定人数。法定人数确保了即使在部分节点失效的情况下,集群依然能够正常运作,并保持数据的一致性。
法定人数的计算公式是 (总节点数 / 2) + 1,这意味着,在给定节点总数的情况下,奇数个节点的配置比相邻的偶数节点配置能多容忍一个节点的失败。例如,一个3节点的集群可以容忍1个节点失败,而一个4节点的集群也只能容忍1个节点失败。因此,增加到4个节点并没有提高容错能力,但是增加到5个节点的集群可以容忍2个节点失败。
对于3个节点的集群,法定人数是 (3/2) + 1 = 2。可以容忍1个节点失败,因为即使1个节点失效,仍然有2个节点在运行,满足了法定人数2的要求,可以继续进行领导者选举和数据更新的决策。 对于4个节点的集群,法定人数也是 (4/2) + 1 = 3。理论上增加了一个节点,看似提高了容错性。但实际上,它仍然只能容忍1个节点失败。因为一旦有2个节点失效,剩下的2个节点无法满足法定人数3的要求,集群就无法做出有效的决策了。
因此,从3个节点增加到4个节点,并没有提高集群容忍节点失败的能力。所以,在设计高可用系统时,选择奇数个节点的配置是为了最大化容错能力,同时避免不必要的资源浪费。

zk命令

创建一个名为/path的znode,数据为data
删除名为/path的znode。
检查是否存在名为/path的znode
设置名为/path的znode的数据为data
返回名为/path的znode的数据
返回所有/path节点的所有子节点列表

部署ZooKeeper

由于ZooKeeper是用Java写的,所以必须安装JDK才能正常运行,JDK部署请看本人JDK部署章节
修改主机名
配置hosts文件
zookeeper下载地址:https://archive.apache.org/dist/zookeeper,我这里部署的是 3.9.1
解压zk到data目录
创建数据目录和日志目录

单机版

修改zk配置文件
启动zookeeper
查看节点状态,可以看到是单机版
notion image
通过客户端连接ZooKeeper
创建znode,名为/mytest,携带数据test
查看znode信息

集群版

我们这里zk集群版本使用的是三个节点,zk节点越多会导致写性能下降,因为zk是往leader写入数据,让后同步给从节点,服务器数量越多,则表示同步从节点的数量也就越多,耗时就越长。读的会性能上升,写性能下降
修改配置文件。集群模式中,集群中的每台机器都需要感知其它机器,在zoo.cfg配置文件中,可以按照如下格式进行配置:server.id=host:port:port,其中id即server id,用于标识服务器在集群中的序号。三台节点都需要修改
创建myid文件,每台zk服务器上,都需要在数据目录下创建一个myid文件,该文件中只有一行内容,即对应于每台服务器的server id。我这里node1节点的myid值为1,node2节点的myid值为2,node3节点的myid值为3
启动 zookeeper 服务,三台节点都执行
验证各节点 zookeeper 启动状态 zk节点1验证zookeeper
zk节点2验证zookeeper
zk节点3验证zookeeper
2888端口是这个服务器Follower与集群中的Leader服务器交换信息的端口,只有leader进程才有2888端口,从节点会长连接这个端口进行数据同步 3888端口是如果集群中的Leader服务器宕机,集群节点通过该端口重新进行选举,选出一个新的Leader,这个端口用来执行选举时服务器相互通信的端口。
可以使用以下命令来连接zk集群,进行测试
创建znode,名为/mytest,携带数据test
查看znode信息

选举过程

当集群中的 zookeeper 节点启动以后,会根据配置文件中指定的 zookeeper 节点地址进行 leader 选择操作,过程如下: 1、每个zookeeper 都会发出投票,由于是第一次选举leader,因此每个节点都会把自己当做leader 角色进行选举,每个zookeeper 的投票中都会包含自己的 myidzxid,此时 zookeeper 1 的投票为 myid 为1,初始 zxid 有一个初始值,后期会随着数据更新而自动变化,zookeeper 2 的投票为 myid 为 2,初始 zxid 为初始生成的值。 2、每个节点接受并检查对方的投票信息,比如投票时间、是否状态为LOOKING状态的投票。 3、对比投票,优先检查xvid,如果xvid 不一样则 xvid 大的为leader,如果xvid相同则继续对比myid,myid 大的一方为 leader
节点角色状态: LOOKING:寻找Leader 状态,处于该状态需要进入选举流程 LEADING:领导者状态,处于该状态的节点说明是角色已经是Leader FOLLOWING:跟随者状态,表示Leader 已经选举出来,当前节点角色是follower OBSERVER:观察者状态,表明当前节点角色是observer
选举ID: ZXID(zookeeper transaction id):每个改变 Zookeeper 状态的操作都会形成一个对应的zxid myid:服务器的唯一标识(SID),通过配置myid 文件指定,集群中唯一
心跳机制:Leader 与 Follower 利用PING 来感知对方的是否存活,当Leader 无法响应PING 时,将重新发起Leader 选举。
成为Leader 的必要条件: Leader 要具有最高的zxid;当集群的规模是 n 时,集群中大多数的机器(至少n/2+1)得到响应并 follow 选出的Leader。

选举日志

Zookeeper1 的投票日志:
notion image
Zookeeper2 的投票日志:
notion image
Zookeeper3 的投票日志:
notion image
在全新的机器上第一次启动zookeeper,会根据myid进行选举,运行一段时间后根据zxid选举。现在我们将leader节点停掉,此时leader为node3。在事务一致的情况下,myid高的为leader
查看现在leader节点,发现是node3节点
 
上一篇
redis持久化
下一篇
kafka