什么是ZooKeeper?ZooKeeper是Apache的顶级项目,为分布式应用程序提供高效、高可用的分布式协调服务。分布式协调/通知、分布式锁等分布式基础服务。ZooKeeper由于使用方便、性能优异、稳定性好,被广泛应用于Hadoop、HBase、Kafka、Dubbo等大型分布式系统中。ZooKeeper有三种运行模式:单机模式、伪集群模式和集群模式。单机模式:这种模式一般适用于开发和测试环境。一方面,我们没有那么多的机器资源,另一方面,平时的开发调试对稳定性要求不高。集群模式:一个ZooKeeper集群通常由一组机器组成,一般3台以上的机器可以组成一个可用的ZooKeeper集群。组成ZooKeeper集群的每台机器都在内存中维护当前服务器状态,并且每台机器都保持相互通信。伪集群模式:这是一种特殊的集群模式,即集群的所有服务器都部署在一台机器上。当你手头有更好的机器时,如果以单机模式部署,会造成资源浪费。在这种情况下,ZooKeeper允许您通过启动不同的端口在一台机器上启动多个ZooKeeper服务实例。这是为了对外提供服务,具有集群的特点。ZooKeeper相关知识ZooKeeper中的角色leader:负责投票的发起和决策,更新系统状态Follower:用于接收客户端请求,并将结果返回给客户端。投票观察者(observer):可以接受客户端连接,将写请求转发给leader,但观察者不参与投票过程,只是为了扩展系统和提高读取速度。ZooKeeper中的作用ZooKeeper的数据模型具有层次化的目录结构,命名符合常规的文件系统规范,类似于Linux。每个节点在ZooKeeper中称为Znode,它有一个唯一的路径来标识该节点。Znode可以包含数据和子节点,但是EPHEMERAL类型的节点不能有子节点。Znodes中的数据可以有多个版本。比如某个路径下存放了多个数据版本,那么查询这个路径下的数据就需要带上版本。可以在节点上设置客户端应用程序。监控节点不支持部分读写,而是完全读写ZooKeeper数据模型ZooKeeper节点特性ZooKeeper节点有一个生命周期,这取决于节点的类型。在ZooKeeper中,节点按持续时间可分为持久节点(PERSISTENT)、临时节点(EPHEMERAL),按是否有序可分为顺序节点(SEQUENTIAL)和无序节点(默认为无序)。持久化节点一旦被创建,除非被主动移除(不会因为创建该节点的客户端会话失败而消失),它将一直保留在ZooKeeper中,一个临时节点。ZooKeeper的应用场景ZooKeeper是一个高可用的分布式数据管理和系统协调框架。该框架基于Paxos算法的实现,保证了分布式环境下数据的强一致性。ZooKeeper也正是基于这个特性,解决了很多分布式问题。值得注意的是,ZooKeeper本身并不是为这些应用场景设计的。它是许多开发者根据其框架的特点,使用其提供的一系列API接口(或称为原语集)进行探索的典型用途。方法。数据发布订阅(配置中心)发布订阅模型,所谓配置中心,顾名思义就是发布者将数据发布到ZooKeeper节点,供订阅者动态获取数据,从而实现配置信息的集中管理和动态更新。比如全局配置信息,service服务框架的服务地址列表等,都非常适合使用。应用中使用的一些配置信息放在ZK上进行集中管理。这种场景通常是这样的:应用在启动的时候会主动获取一个配置,同时在节点上注册一个Watcher,这样以后每次更新配置的时候都会通知订阅者客户端实时。永远不能达到获取最新配置信息的目的。在分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存储在ZooKeeper的一些指定节点中,供各个客户端订阅。分布式日志收集系统。这个系统的核心工作是收集分布在不同机器上的日志。采集器通常会根据应用分配采集任务单元,因此需要在ZooKeeper上创建一个以应用名为路径的节点P,并将本应用的所有机器IP以子节点的形式注册到节点P上,这样当机器发生变化时,可以实时通知收集器调整任务分配。系统中有些信息需要动态获取,手动修改这些信息会有问题。通常会暴露一个接口,比如JMX接口,来获取一些运行时信息。引入ZooKeeper后,不需要自己去实现一套方案,只要将信息存储在指定的ZooKeeper节点上即可。注意:在上面提到的应用场景中,有一个默认的前提:数据量小,但数据更新可能更快。负载均衡这里所说的负载均衡指的是软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或者同一个服务提供者会部署多个副本来实现对等服务。消费者需要在这些对等服务器中选择一个来执行相关的业务逻辑,通常是消息中间件中的生产者和消费者负载均衡。命名服务命名服务也是分布式系统中常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用程序可以根据指定的名称获取资源或服务的地址、提供者等信息。命名实体通常可以是集群中的一台机器、提供的服务地址、远程对象等——我们可以将它们统称为名称(Name)。比较常见的是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,可以很容易的创建一个全局唯一的路径,可以作为名字。阿里巴巴集团的开源分布式服务框架Dubbo使用ZooKeeper作为其命名服务,维护一个全局的服务地址列表。Dubbo实现中:服务提供者启动时,将自己的URL地址写入ZooKeeper上指定节点/dubbo/${serviceName}/providers目录,此操作完成服务的发布。服务消费者启动时,订阅/dubbo/${serviceName}/providers目录下的providerURL地址,将自己的URL地址写入/dubbo/${serviceName}/consumers目录。注意所有注册到ZooKeeper的地址都是临时节点,这样服务提供者和消费者可以自动感知资源变化。另外,Dubbo还通过订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息来监控服务粒度。分布式通知/协调ZooKeeper中独特的watcher注册和异步通知机制可以很好地实现分布式环境下不同系统之间的通知和协调,实现数据变化的实时处理。使用方法通常是不同的系统在ZooKeeper上注册同一个Znode,监听Znode的变化(包括Znode自身及其子节点的内容),其中一个系统更新Znode,另一个系统就可以接收到通知并作出相应处理。另一种心跳检测机制:检测系统和被检测系统不直接关联,而是通过zk上的一个节点关联,大大降低了系统耦合度。另一种系统调度方式:一个系统由控制台和推送系统组成,控制台的职责是控制推送系统进行相应的推送工作。manager在控制台进行的一些操作实际上会修改ZooKeeper上某些节点的状态,ZooKeeper将这些变化通知给注册Watcher的客户端,即推送系统,然后做相应的推送任务。另一种工作报告模式:有些类似于任务分发系统。子任务启动后,会向ZooKeeper注册一个临时节点,并定期汇报自己的进度(将进度回写到这个临时节点),这样任务管理器就能够实时知道任务的进度。分布式锁分布式锁,这主要是ZooKeeper为我们保证了数据的强一致性。锁服务可以分为两类,一类是保持独占,一类是控制时序。所谓独占性,就是在所有试图获取锁的客户端中,最终只有一个能够成功获取到锁。通常的做法是将ZooKeeper上的一个Znode看成一把锁,通过创建znode来实现。所有客户端创建一个/distribute_lock节点,创建成功的客户端最终拥有锁。控制时序是指所有查看锁获取锁的client最后都会被调度执行,但是有一个全局的时序。做法和上面基本类似,只不过/distribute_lock已经提前存在,客户端在其下创建一个临时有序节点(这个可以通过节点的属性:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。ZooKeeper的父节点(/distribute_lock)维护一个序列来保证子节点创建的时序,从而形成每个client的全局时序。由于同一节点下的子节点名称不能相同,所以只要在某个节点下创建了一个Znode,创建成功就意味着加锁成功。注册一个监听器来监听这个Znode,只要这个Znode被删除,就会通知其他客户端锁定它。创建临时顺序节点:在某个节点下创建一个节点,当有请求来的时候再创建一个节点。因为是顺序的,所以序号最小的会获得锁。当锁被释放时,通知下一个序号获得锁。对于分布式队列来说,简单来说就是两种队列,一种是常规的先进先出队列,一种是等待队列成员聚集后再按顺序执行。第一类队列的基本原理与前面提到的分布式锁服务中的时序控制场景相同,这里不再赘述。第二种队列其实是在FIFO队列基础上的增强。通常,可以在/queueZnode下预先建立一个/queue/num节点,并赋值为n(或直接赋值给/queuen),表示队列的大小。每个队列成员加入后,判断是否到达队列的大小决定是否可以开始执行。这种用法的典型场景是,在分布式环境中,在许多子任务完成之前,需要先完成(或条件准备好)一个大任务TaskA。此时,如果其中一个子任务完成(就绪),则去/taskList创建自己的临时计时节点(CreateMode.EPHEMERAL_SEQUENTIAL)。当/taskList发现你下面的子节点个数满足指定个数时,你就可以依次进行下一步处理。使用dokcer-compose搭建集群上面我们介绍了ZooKeeper的应用场景非常多,那么我们就先学习如何搭建ZooKeeper集群,然后对上面的应用场景进行实践。文件目录结构如下:├──docker-compose.yml编写docker-compose.yml文件docker-compose.yml文件内容如下:version:'3.4'services:zoo1:image:zookeeperrestart:alwayshostname:zoo1ports:-2181:2181environment:ZOO_MY_ID:1ZOO_SERVERS:server.1=0.0.0.0:2888:3888;2181server.2=zoo2:2888:3888;2181server.3=zoo3:2888:3888;2181zoo2:图片:zookeeperrestart:alwayshostname:zoo2ports:-218:2181environment:ZOO_MY_ID:2ZOO_SERVERS:server.1=zoo1:2888:3888;2181server.2=0.0.0.0:2888:3888;2181server.3=zoo3:2888:3888;2181zoo3:image:zookeeperrestart:alwayshostname:zoo3ports:-2183:2181environment:ZOO_MY_ID:3ZOO_SERVERS:server.1=zoo1:2888:3888;2181server.2=zoo2:2888:3888;2181server.3=0.0在此配置文件中,Docker运行3ZooKeeper镜像,通过ports字段将本地的2181、2182、2183端口绑定到对应容器的2181端口。ZOO_MY_ID和ZOO_SERVERS是构建ZooKeeper集群所需的两个环境变量。ZOO_MY_ID标识服务的id,是1-255之间的整数,在集群中必须是唯一的。ZOO_SERVERS是集群中主机的列表。在docker-compose.yml所在目录执行docker-composeup,可以看到启动日志。连接ZooKeeper启动集群后,我们就可以连接ZooKeeper对其进行节点相关的操作。首先我们需要下载ZooKeeper。ZooKeeper下载地址。解压到它的conf目录下,把zoo_sample.cfg改成zoo.cfg配置文件说明:#Thenumberofmillisecondssofeachtick#tickTime:CS通信心跳#ZooKeeper服务器之间或者客户端和服务器之间保持心跳的时间间隔,也就是发送一次心跳每个滴答时间。tickTime以毫秒为单位。tickTime=2000#Thenumberofticksthattheinitial#synchronizationphasecantake#initLimit:LF初始通信时限#follower服务器(F)和leader服务器(L)初始连接时可以容忍的最大心跳次数(tickTime的次数)集群。initLimit=5#Thenumberofticksthatcanpassbetween#sendingarequestandgettinganacknowledgement#syncLimit:LF同步通信时间限制#集群中follower服务器和leader服务器之间请求和响应之间可以容忍的最大心跳次数(tickTime的次数)。syncLimit=2#thedirectorywherethesnapshotiststored.#donotuse/tmpforstorage,/tmphereisjust#examplesakes.#dataDir:数据文件目录#ZooKeeper保存数据的目录,默认情况下,ZooKeeper也会将写入数据的日志文件保存在该目录下。dataDir=/data/soft/zookeeper-3.4.12/data#dataLogDir:日志文件目录#ZooKeeper保存日志文件的目录。dataLogDir=/data/soft/zookeeper-3.4.12/logs#theportatwhichtheclientswillconnect#clientPort:客户端连接端口#客户端连接ZooKeeper服务器的端口。ZooKeeper会监听这个端口,接受客户端的访问请求。clientPort=2181#themaximumnumberofclientconnections.#increasethisifyouneedtohandlemoreclients#maxClientCnxns=60##Besuretoreadthemaintenancesectionofthe#administratorguidebeforeturningonautopurge.##http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance##ThenumberofsnapshotstoretainindataDir#autopurge3#innapPurges3#Setto"0"todisableautopurgefeature#autopurge.purgeInterval=1#服务器名称和地址:集群信息(服务器编号、服务器地址、LF通信端口、选举端口)#这个配置项的写法比较特殊,规则是如下:#server.N=YYY:A:B#其中N代表服务器编号,YYY代表服务器IP地址,A为LF通信端口,表示集群中服务器和leader不需要修改zoo.cfg,默认配置就可以了,然后解压后在.bin目录下执行命令./zkCli.sh-server127.0.0.1:2181。欢迎使用ZooKeeper!2020-06-0115:03:52,512[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@1025]-Openingsocketconnectiontoserverlocalhost/127.0.0.1:2181.WillnotattempttoauthenticateusingLinesup0or20115,576:5[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@879]-Socketconnectionestablishedtolocalhost/127.0.0.1:2181,initiatingsession2020-06-0115:03:52,599[myid:]-INFO[main-SendThread(localhost:2181):ClientCnxn$SendThread@1299]-Sessionestablishmentcompleteonserverlocalhost/127.0.0.1:2181,sessionid=0x100001140080000,negotiatedtimeout=30000WATCHER::WatchedEventstate:SyncConnectedtype:Nonepath:null[zk:127.0.0.0](1:218)1接下来我们就可以使用命令查看节点了。使用ls命令查看ZooKeeper中当前的内容。命令:ls/[zk:127.0.0.1:2181(CONNECTED)10]ls/[zookeeper]创建一个新的Znode节点“zk”和与之关联的字符串。命令:create/zkmyData[zk:127.0.0.1:2181(CONNECTED)11]create/zkmyDataCreated/zk[zk:127.0.0.1:2181(CONNECTED)12]ls/[zk,zookeeper][zk:127.0.0.1:2181(CONNECTED)13]获取Znode节点zk.命令:get/zk[zk:127.0.0.1:2181(CONNECTED)13]get/zkmyDatacZxid=0x400000008ctime=MonJun0115:07:50CST2020mZxid=0x400000008mZxid=0x400000008m20m1time=MonZ0xid0x400000008mZxid=0x400000008m1time=0x400000008m20m1time=MonZ0xid200x400000008cversion=0dataVersion=0aclVersion=0ephemeralOwner=0x0dataLength=6numChildren=0删除Znode节点zk。命令:delete/zk[zk:127.0.0.1:2181(CONNECTED)14]delete/zk[zk:127.0.0.1:2181(CONNECTED)15]ls/[zookeeper]由于篇幅有限,下一篇将以就上面提到的ZooKeeper应用场景一一用代码来实现。
